Qu'est-ce qu'une exception IndexOutOfRangeException / ArgumentOutOfRangeException et comment la corriger?

191

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

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

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

Selon les classes utilisées, il peut également être 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: l'index était hors limites. Doit être non négatif et inférieur à la taille de la collection.

Adriano Repetti
la source
Dans votre collection si vous n'avez que 4 éléments, mais que le code a essayé d'obtenir un élément dans l'index 5. Cela lèvera IndexOutOfRangeException. Indice de contrôle = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy le

Réponses:

232

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 IndexOutOfRangeExceptionla 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ù Lengthest 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.Lengthpour 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 ArgumentOutOfRangeExceptionce code (par exemple, si vous utilisez une GetPixelméthode sur une Bitmapclasse).

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 à Lengthce que vous obtiendrez IndexOutOfRangeException(ce comportement peut vous dérouter si vous pensez qu'ils peuvent augmenter avec une syntaxe similaire à la Insertmé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 myArrayne 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 ( Listles 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 ArgumentOutOfRangeExceptionpour 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 IndexOutOfRangeExceptionparce 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 IDataReaderimplémentations ( SqlDataReader, OleDbDataReaderetc.).

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 DataViewSortproprié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 0par GetLowerBound(0)et .Lengthpar GetUpperBound(0)(bien sûr, si vous avez des paramètres de type System.Array, 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é ArgumentExceptionou 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 iflogique 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 indexaugmente. Est ce juste? Vérifiez comment le tableau est alloué, est cohérent avec la façon dont les indexaugmentations? 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

Adriano Repetti
la source
19

Explication simple sur ce qu'est une exception Index hors limite:

Il suffit de penser qu'un train est là, ses compartiments sont D1, D2, D3. Un passager est venu entrer dans le train et il a le billet pour le D4. maintenant ce qui va se passer. le passager veut entrer dans un compartiment qui n'existe pas, donc évidemment un problème se posera.

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 dans le tableau. array[0]et array[1]existent. Si nous essayons d'accéder array[3], ce n'est pas là en fait, donc un index hors limite se produira.

Lijo
la source
10

Pour comprendre facilement le problème, imaginez que nous ayons écrit 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 de 3 (indices 0, 1 et 2), mais la boucle for boucle 4 fois (0, 1, 2 et 3).
Ainsi, quand il essaie d'accéder en dehors des limites avec (3), il lève l'exception.

Snr
la source
1

À côté de la très longue réponse complète acceptée, il y a un point important à faire par IndexOutOfRangeExceptionrapport à de nombreux autres types d'exceptions, à savoir:

Il y a souvent un état de programme complexe sur lequel il est peut-être difficile de contrôler à un moment donné du code, par exemple une connexion de base de données tombe en panne, de sorte que les données d'une entrée ne peuvent pas être récupérées, etc. Ce type de problème entraîne souvent une exception qui doit remonter à un niveau supérieur parce que là où cela se produit n'a aucun moyen de le gérer à ce moment-là.

IndexOutOfRangeExceptionest généralement différent en ce que dans la plupart des cas, il est assez simple de vérifier au moment où l'exception est levée. Généralement, ce type d'exception est levé par un code qui pourrait très facilement résoudre le problème à l'endroit où il se produit - simplement en vérifiant la longueur réelle du tableau. Vous ne voulez pas «corriger» cela en gérant cette exception plus haut - mais plutôt en vous assurant qu'elle ne soit pas lancée en première instance - ce qui est dans la plupart des cas facile à faire en vérifiant la longueur du tableau.

Une autre façon d'exprimer cela est que d'autres exceptions peuvent survenir en raison d'un véritable manque de contrôle sur l'entrée ou l'état du programme MAIS le IndexOutOfRangeExceptionplus souvent, il s'agit simplement d'une erreur du pilote (programmeur).

Ricibob
la source