Qu'Est-ce que c'est?
Cette exception signifie que vous essayez d'accéder à un élément de collection par index, en utilisant un index non valide. Un index n'est pas valide lorsqu'il est inférieur à la limite inférieure de la collection ou supérieur ou égal au nombre d'éléments qu'il contient.
Quand il est jeté
Étant donné un tableau déclaré comme:
byte[] array = new byte[4];
Vous pouvez accéder à ce tableau de 0 à 3, les valeurs en dehors de cette plage entraîneront IndexOutOfRangeException
la levée. Souvenez-vous de cela lorsque vous créez et accédez à un tableau.
Longueur du tableau
En C #, généralement, les tableaux sont basés sur 0. Cela signifie que le premier élément a l'index 0 et le dernier élément a l'index Length - 1
(où Length
est le nombre total d'éléments dans le tableau) donc ce code ne fonctionne pas:
array[array.Length] = 0;
De plus, veuillez noter que si vous avez un tableau multidimensionnel, vous ne pouvez pas utiliser Array.Length
pour les deux dimensions, vous devez utiliser Array.GetLength()
:
int[,] data = new int[10, 5];
for (int i=0; i < data.GetLength(0); ++i) {
for (int j=0; j < data.GetLength(1); ++j) {
data[i, j] = 1;
}
}
La limite supérieure n'est pas inclusive
Dans l'exemple suivant, nous créons un tableau bidimensionnel brut de Color
. Chaque élément représente un pixel, les indices vont de (0, 0)
à (imageWidth - 1, imageHeight - 1)
.
Color[,] pixels = new Color[imageWidth, imageHeight];
for (int x = 0; x <= imageWidth; ++x) {
for (int y = 0; y <= imageHeight; ++y) {
pixels[x, y] = backgroundColor;
}
}
Ce code échouera alors car le tableau est basé sur 0 et le dernier pixel (en bas à droite) de l'image est pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
Dans un autre scénario, vous pouvez obtenir ArgumentOutOfRangeException
ce code (par exemple, si vous utilisez une GetPixel
méthode sur une Bitmap
classe).
Les tableaux ne grandissent pas
Un tableau est rapide. Recherche linéaire très rapide par rapport à toutes les autres collections. C'est parce que les éléments sont contigus dans la mémoire afin que l'adresse mémoire puisse être calculée (et l'incrémentation n'est qu'un ajout). Pas besoin de suivre une liste de nœuds, des maths simples! Vous payez cela avec une limitation: ils ne peuvent pas grandir, si vous avez besoin de plus d'éléments, vous devez réallouer ce tableau (cela peut prendre un temps relativement long si les anciens éléments doivent être copiés dans un nouveau bloc). Vous les redimensionnez avec Array.Resize<T>()
, cet exemple ajoute une nouvelle entrée à un tableau existant:
Array.Resize(ref array, array.Length + 1);
N'oubliez pas que les indices valides vont de 0
à Length - 1
. Si vous essayez simplement d'attribuer un élément à Length
ce que vous obtiendrez IndexOutOfRangeException
(ce comportement peut vous dérouter si vous pensez qu'ils peuvent augmenter avec une syntaxe similaire à la Insert
méthode d'autres collections).
Tableaux spéciaux avec limite inférieure personnalisée Le
premier élément des tableaux a toujours l'index 0 . Ce n'est pas toujours vrai car vous pouvez créer un tableau avec une limite inférieure personnalisée:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
Dans cet exemple, les indices de tableau sont valides de 1 à 4. Bien entendu, la limite supérieure ne peut pas être modifiée.
Arguments incorrects
Si vous accédez à un tableau en utilisant des arguments non validés (à partir de l'entrée utilisateur ou de l'utilisateur de la fonction), vous pouvez obtenir cette erreur:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
Résultats inattendus
Cette exception peut être lancée pour une autre raison également: par convention, de nombreuses fonctions de recherche renverront -1 (nullables a été introduit avec .NET 2.0 et de toute façon c'est aussi une convention bien connue en usage depuis de nombreuses années) si elles ne l'ont pas fait ' rien trouver. Imaginons que vous ayez un tableau d'objets comparable à une chaîne. Vous pourriez penser à écrire ce code:
// Items comparable with a string
Console.WriteLine("First item equals to 'Debug' is '{0}'.",
myArray[Array.IndexOf(myArray, "Debug")]);
// Arbitrary objects
Console.WriteLine("First item equals to 'Debug' is '{0}'.",
myArray[Array.FindIndex(myArray, x => x.Type == "Debug")]);
Cela échouera si aucun élément dans myArray
ne satisfait la condition de recherche car Array.IndexOf()
retournera -1 et l'accès au tableau sera renvoyé.
L'exemple suivant est un exemple naïf pour calculer les occurrences d'un ensemble donné de nombres (connaissant le nombre maximum et renvoyant un tableau où l'élément à l'index 0 représente le numéro 0, les éléments à l'index 1 représentent le numéro 1 et ainsi de suite):
static int[] CountOccurences(int maximum, IEnumerable<int> numbers) {
int[] result = new int[maximum + 1]; // Includes 0
foreach (int number in numbers)
++result[number];
return result;
}
Bien sûr, c'est une implémentation assez terrible, mais ce que je veux montrer, c'est qu'elle échouera pour les nombres négatifs et les nombres ci-dessus maximum
.
Comment cela s'applique-t-il List<T>
?
Mêmes cas que tableau - plage d'index valides - 0 ( List
les index commencent toujours par 0) pour list.Count
- accéder aux éléments en dehors de cette plage provoquera l'exception.
Notez que List<T>
lance ArgumentOutOfRangeException
pour les mêmes cas où les tableaux utilisent IndexOutOfRangeException
.
Contrairement aux tableaux, List<T>
commence vide - donc essayer d'accéder aux éléments de la liste qui vient d'être créée conduit à cette exception.
var list = new List<int>();
Le cas courant est de remplir la liste avec une indexation (similaire à Dictionary<int, T>
) provoquera une exception:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader et colonnes
Imaginez que vous essayez de lire des données à partir d'une base de données avec ce code:
using (var connection = CreateConnection()) {
using (var command = connection.CreateCommand()) {
command.CommandText = "SELECT MyColumn1, MyColumn2 FROM MyTable";
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
ProcessData(reader.GetString(2)); // Throws!
}
}
}
}
GetString()
lancera IndexOutOfRangeException
parce que votre ensemble de données n'a que deux colonnes mais que vous essayez d'obtenir une valeur à partir de la troisième (les indices sont toujours basés sur 0).
S'il vous plaît noter que ce comportement est partagé avec la plupart des IDataReader
implémentations ( SqlDataReader
, OleDbDataReader
etc.).
Vous pouvez également obtenir la même exception si vous utilisez la surcharge IDataReader de l'opérateur d'indexeur qui prend un nom de colonne et transmet un nom de colonne non valide.
Supposons par exemple que vous ayez récupéré une colonne nommée Colonne1 mais que vous essayez ensuite de récupérer la valeur de ce champ avec
var data = dr["Colum1"]; // Missing the n in Column1.
Cela se produit car l'opérateur d'indexation est implémenté en essayant de récupérer l'index d'un champ Colum1 qui n'existe pas. La méthode GetOrdinal lèvera cette exception lorsque son code d'assistance interne renvoie un -1 comme index de "Colum1".
Autres
Il existe un autre cas (documenté) lorsque cette exception est levée: si, dans DataView
, le nom de la colonne de données fourni à la DataViewSort
propriété n'est pas valide.
Comment éviter
Dans cet exemple, laissez-moi supposer, pour simplifier, que les tableaux sont toujours monodimensionnels et basés sur 0. Si vous voulez être strict (ou que vous développez une bibliothèque), vous devrez peut-être remplacer 0
par GetLowerBound(0)
et .Length
par GetUpperBound(0)
(bien sûr, si vous avez des paramètres de type System.Arra
y, cela ne s'applique pas T[]
). Veuillez noter que dans ce cas, la limite supérieure est inclusive alors ce code:
for (int i=0; i < array.Length; ++i) { }
Devrait être réécrit comme ceci:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
Veuillez noter que cela n'est pas autorisé (cela lancera InvalidCastException
), c'est pourquoi si vos paramètres sont T[]
sûrs, vous êtes en sécurité avec les tableaux de limites inférieures personnalisés:
void foo<T>(T[] array) { }
void test() {
// This will throw InvalidCastException, cannot convert Int32[] to Int32[*]
foo((int)Array.CreateInstance(typeof(int), new int[] { 1 }, new int[] { 1 }));
}
Valider les paramètres
Si l'index provient d'un paramètre, vous devez toujours les valider (en lançant approprié ArgumentException
ou ArgumentOutOfRangeException
). Dans l'exemple suivant, des paramètres incorrects peuvent provoquer IndexOutOfRangeException
, les utilisateurs de cette fonction peuvent s'y attendre car ils passent un tableau, mais ce n'est pas toujours aussi évident. Je suggère de toujours valider les paramètres pour les fonctions publiques:
static void SetRange<T>(T[] array, int from, int length, Func<i, T> function)
{
if (from < 0 || from>= array.Length)
throw new ArgumentOutOfRangeException("from");
if (length < 0)
throw new ArgumentOutOfRangeException("length");
if (from + length > array.Length)
throw new ArgumentException("...");
for (int i=from; i < from + length; ++i)
array[i] = function(i);
}
Si la fonction est privée, vous pouvez simplement remplacer la if
logique par Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
Vérifier l'état de l'objet L'
index du tableau peut ne pas provenir directement d'un paramètre. Cela peut faire partie de l'état de l'objet. En général, il est toujours recommandé de valider l'état de l'objet (seul et avec des paramètres de fonction, si nécessaire). Vous pouvez utiliser Debug.Assert()
, lancer une exception appropriée (plus descriptive sur le problème) ou gérer cela comme dans cet exemple:
class Table {
public int SelectedIndex { get; set; }
public Row[] Rows { get; set; }
public Row SelectedRow {
get {
if (Rows == null)
throw new InvalidOperationException("...");
// No or wrong selection, here we just return null for
// this case (it may be the reason we use this property
// instead of direct access)
if (SelectedIndex < 0 || SelectedIndex >= Rows.Length)
return null;
return Rows[SelectedIndex];
}
}
Valider les valeurs de retour
Dans l'un des exemples précédents, nous avons utilisé directement la Array.IndexOf()
valeur de retour. Si nous savons que cela peut échouer, il est préférable de gérer ce cas:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
Comment déboguer
À mon avis, la plupart des questions, ici sur SO, à propos de cette erreur peuvent être simplement évitées. Le temps que vous passez à écrire une question appropriée (avec un petit exemple de travail et une petite explication) pourrait facilement beaucoup plus que le temps dont vous aurez besoin pour déboguer votre code. Tout d'abord, lisez ce billet de blog d'Eric Lippert sur le débogage de petits programmes , je ne répéterai pas ses mots ici mais c'est absolument à lire .
Vous avez le code source, vous avez un message d'exception avec une trace de pile. Allez-y, choisissez le bon numéro de ligne et vous verrez:
array[index] = newValue;
Vous avez trouvé votre erreur, vérifiez comment index
augmente. Est ce juste? Vérifiez comment le tableau est alloué, est cohérent avec la façon dont les index
augmentations? Est-ce conforme à vos spécifications? Si vous répondez oui à toutes ces questions, vous trouverez une bonne aide ici sur StackOverflow, mais veuillez d'abord vérifier cela par vous-même. Vous économiserez votre temps!
Un bon point de départ est de toujours utiliser des assertions et de valider les entrées. Vous pouvez même utiliser des contrats de code. Quand quelque chose ne va pas et que vous ne pouvez pas comprendre ce qui se passe avec un rapide coup d'œil à votre code, vous devez recourir à un vieil ami: le débogueur . Exécutez simplement votre application en mode débogage dans Visual Studio (ou votre IDE préféré), vous verrez exactement quelle ligne lève cette exception, quel tableau est impliqué et quel index vous essayez d'utiliser. Vraiment, 99% des fois, vous le résoudrez vous-même en quelques minutes.
Si cela se produit en production, vous feriez mieux d'ajouter des assertions dans le code incriminé, nous ne verrons probablement pas dans votre code ce que vous ne pouvez pas voir par vous-même (mais vous pouvez toujours parier).
Le côté VB.NET de l'histoire
Tout ce que nous avons dit dans la réponse C # est valide pour VB.NET avec les différences de syntaxe évidentes, mais il y a un point important à considérer lorsque vous traitez avec des tableaux VB.NET.
Dans VB.NET, les tableaux sont déclarés en définissant la valeur d'index valide maximale pour le tableau. Ce n'est pas le nombre d'éléments que nous voulons stocker dans le tableau.
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
Donc, cette boucle remplira le tableau avec 5 entiers sans provoquer d' exception IndexOutOfRangeException
For i As Integer = 0 To 4
myArray(i) = i
Next
La règle VB.NET
Cette exception signifie que vous essayez d'accéder à un élément de collection par index, en utilisant un index non valide. Un index n'est pas valide lorsqu'il est inférieur à la limite inférieure de la collection ou supérieur àégal au nombre d'éléments qu'il contient. l'index maximum autorisé défini dans la déclaration du tableau