web-dev-qa-db-fra.com

Qu'est-ce qu'une exception IndexOutOfRangeException / ArgumentOutOfRangeException et comment y remédier?

J'ai du code et quand il s'exécute, il jette un IndexOutOfRangeException en disant:

L'index était en dehors des limites du tableau.

Qu'est-ce que cela signifie et que puis-je faire à ce sujet?

Selon les classes utilisées, il peut également s'agir de ArgumentOutOfRangeException

Une exception de type 'System.ArgumentOutOfRangeException' s'est produite dans mscorlib.dll mais n'a pas été gérée dans le code utilisateur. Informations supplémentaires: Index était hors de portée. Doit être non négatif et inférieur à la taille de la collection.

174
Adriano Repetti

Qu'Est-ce que c'est?

Cette exception signifie que vous essayez d'accéder à un élément de la 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'elle 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 situées en dehors de cette plage provoqueront le transfert de IndexOutOfRangeException. Rappelez-vous ceci 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, notez 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 array 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 pour ce code (par exemple, si vous utilisez la méthode GetPixel sur une classe Bitmap.).

Les tableaux ne se développent pas
Un tableau est rapide. Très rapide en recherche linéaire par rapport à toutes les autres collections. C’est parce que les éléments sont contigus en mémoire afin que l’adresse mémoire puisse être calculée (et l’incrément n’est qu’un ajout). Pas besoin de suivre une liste de nœuds, un calcul simple! Vous payez ceci avec une limitation: ils ne peuvent pas croître, si vous avez besoin de plus d'éléments, vous aurez besoin de réaffecter ce tableau (cela peut être expansif 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 index valides vont de _0_ à _Length - 1_. Si vous essayez simplement d'attribuer un élément à Length, vous obtiendrez IndexOutOfRangeException (ce comportement peut vous induire en erreur si vous pensez qu'il peut augmenter avec une syntaxe similaire à la méthode Insert d'autres collections) .

Spécial Tableaux avec limite inférieure personnalisée
Le ​​premier élément des tableaux a toujours l'indice. 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 erronés
Si vous accédez à un tableau en utilisant des arguments non validés (à partir d'une entrée utilisateur ou d'un utilisateur de fonction), vous risquez d'obtenir l'erreur suivante:

_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 aussi être levée pour une autre raison: par convention, beaucoup fonctions de recherche renverra -1 (nullables a été introduit avec .NET 2.0 et, de toute façon, il s'agit également d'une convention bien connue dans utiliser depuis de nombreuses années) s'ils n'ont rien trouvé. Imaginons que vous disposiez d'un tableau d'objets comparable à une chaîne. Vous pensez peut-être é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 remplira les conditions de recherche, car Array.IndexOf() renverra -1 et l'accès au tableau sera lancé.

L'exemple suivant est un exemple naïf permettant de calculer les occurrences d'un ensemble de nombres donné (connaître le nombre maximal et renvoyer un tableau où l'item à l'index 0 représente le n ° 0, les éléments à l'index 1 le n ° 1, etc.)

_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 plutôt terrible, mais ce que je veux montrer, c'est que les nombres négatifs et les nombres supérieurs à maximum échoueront.

Comment s'applique-t-il à List<T> ?

Même cas que le tableau - plage d'index valides - 0 (les index de List commencent toujours par 0) à _list.Count_ - l'accès aux éléments situés en dehors de cette plage provoquera l'exception.

Notez que _List<T>_ jette ArgumentOutOfRangeException pour les mêmes cas où les tableaux utilisent IndexOutOfRangeException.

À la différence des tableaux, _List<T>_ commence vide - par conséquent, toute tentative d'accès aux éléments de la liste que vous venez de créer entraîne cette exception.

_var list = new List<int>();
_

Le cas habituel est de remplir une liste avec 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 les données d’une base de données avec le code suivant:

_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 jeu de données ne contient que deux colonnes mais que vous essayez d'obtenir une valeur de la 3ème (les index sont toujours basés sur 0).

Veuillez noter que ce comportement est partagé avec la plupart des implémentations IDataReader (SqlDataReader, OleDbDataReader et ainsi de suite).

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 passe un nom de colonne non valide.
Supposons par exemple que vous ayez récupéré une colonne nommée Column1 mais que vous essayiez ensuite de récupérer la valeur de ce champ avec

_ var data = dr["Colum1"];  // Missing the n in Column1.
_

Ceci est dû au fait que 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ève cette exception lorsque son code d'assistance interne renvoie -1 en tant qu'index de "Colum1".

Autres
Il y a un autre cas (documenté) lorsque cette exception est levée: si, dans DataView, le nom de la colonne de données fourni à la propriété DataViewSort n'est pas valide.

Comment éviter

Dans ces exemples, supposons que, par souci de simplicité, les tableaux soient toujours monodimensionnels et basés sur 0. Si vous voulez être strict (ou si 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, il 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 ceci n’est pas autorisé (cela jettera InvalidCastException), c’est pourquoi, si vos paramètres sont _T[]_, vous êtes en sécurité en ce qui concerne les tableaux à la limite inférieure personnalisée:

_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 index provient d'un paramètre, vous devez toujours le valider (en lançant le ArgumentException ou ArgumentOutOfRangeException approprié). 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érerais 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 function est privé, vous pouvez simplement remplacer la logique if par Debug.Assert():

_Debug.Assert(from >= 0 && from < array.Length);
_

Etat de l'objet à vérifier
L'index de 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(), émettre une exception appropriée (plus descriptive du problème) ou gérer celle-ci, 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 directement utilisé Array.IndexOf() valeur de retour. Si nous savons que cela peut échouer, il est préférable de traiter ce cas:

_int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
_

Comment déboguer

A mon avis, la plupart des questions, ici sur SO, sur cette erreur peuvent être simplement évitées. Le temps que vous passerez à rédiger une question appropriée (avec un petit exemple de travail et une petite explication) pourrait facilement prendre beaucoup plus que le temps dont vous aurez besoin pour déboguer votre code. Tout d’abord, lisez ce billet du blog d’Eric Lippert à propos de débogage de petits programmes , je ne répéterai pas ses mots ici, mais c’est absolument un doit lire.

Vous avez le code source, vous avez un message d'exception avec 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 bien? Vérifiez comment le tableau est alloué, est-il cohérent avec l'augmentation de index? Est-ce juste selon votre spécifiation? Si vous répondez oui à toutes ces questions, alors vous trouverez une aide précieuse 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 voudrez peut-être même utiliser des contrats de code. Quand quelque chose ne va pas et vous ne pouvez pas comprendre ce qui se passe avec un rapide coup d'oeil à votre code, vous devez alors recourir à un vieil ami: 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 allez le résoudre vous-même en quelques minutes.

Si cela se produit en production, il est préférable d’ajouter des assertions dans le code incriminé. Nous ne verrons probablement pas dans votre code ce que vous ne pouvez pas voir 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 valable pour VB.NET avec les différences de syntaxe évidentes, mais il y a un point important à prendre en compte lorsque vous utilisez 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 causer aucune 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 la 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 de tableau

213
Adriano Repetti

Explication simple sur ce qu'est une exception hors index liée:

Imaginez un train, ses compartiments sont les suivants: D1, D2, D3. Un passager est entré dans le train et il a le billet pour D4. Maintenant, ce qui va se passer. Le passager vouloir entrer dans un compartiment qui n’existe pas, il est évident qu’un problème va survenir.

Même scénario: chaque fois que nous essayons d'accéder à une liste de tableaux, etc., nous ne pouvons accéder qu'aux index existants du tableau. array[0] et array[1] sont existants. Si nous essayons d'accéder à array[3], il n'y en a pas réellement, une exception d'index hors limite se produira.

17
Lijo

Pour comprendre facilement le problème, imaginons que nous écrivions ce code:

static void Main(string[] args)
{
    string[] test = new string[3];
    test[0]= "hello1";
    test[1]= "hello2";
    test[2]= "hello3";

    for (int i = 0; i <= 3; i++)
    {
        Console.WriteLine(test[i].ToString());
    }
}

Le résultat sera:

hello1
hello2
hello3

Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

La taille du tableau est 3 (indices 0, 1 et 2), mais la boucle for boucle en boucle 4 fois (0, 1, 2 et 3).
Ainsi, lorsqu'il tente d'accéder en dehors des limites avec (3), il lève l'exception.

8
Snr

Un côté de la très longue réponse acceptée complète, il y a un point important à dire à propos de IndexOutOfRangeException par rapport à beaucoup d'autres types d'exceptions, à savoir:

Il existe souvent des états de programme complexes qu'il peut être difficile de contrôler à un moment donné du code, par exemple, une connexion à une base de données est interrompue et les données d'une entrée ne peuvent pas être récupérées, etc. doit monter à un niveau plus élevé, car là où il se produit n’a aucun moyen de le gérer à ce stade.

IndexOutOfRangeException est généralement différent en ce sens que dans la plupart des cas, il est assez facile de vérifier au moment où l'exception est levée. Généralement, ce type d’exception est généré par un code qui pourrait très facilement traiter le problème à l’endroit où il se produit - en vérifiant simplement la longueur réelle du tableau. Vous ne voulez pas résoudre ce problème en traitant cette exception plus haut - mais en vous assurant qu'elle ne sera pas levée en premier lieu - ce qui est facile à faire dans la plupart des cas en vérifiant la longueur du tableau.

Une autre façon de le dire est que d’autres exceptions peuvent survenir en raison d’un manque réel de contrôle sur l’entrée ou l’état du programme. MAIS IndexOutOfRangeException n’est le plus souvent qu’une simple erreur de pilote (programmeur).

0
Ricibob