Was ist eine IndexOutOfRangeException / ArgumentOutOfRangeException und wie behebe ich sie?

190

Ich habe einen Code und wenn er ausgeführt wird, wirft er ein IndexOutOfRangeExceptionSprichwort:

Index war außerhalb der Grenzen des Arrays.

Was bedeutet das und was kann ich dagegen tun?

Abhängig von den verwendeten Klassen kann es auch sein ArgumentOutOfRangeException

Eine Ausnahme vom Typ 'System.ArgumentOutOfRangeException' ist in mscorlib.dll aufgetreten, wurde jedoch nicht im Benutzercode behandelt. Zusätzliche Informationen: Der Index lag außerhalb des Bereichs. Muss nicht negativ sein und kleiner als die Größe der Sammlung.

Adriano Repetti
quelle
Wenn Sie in Ihrer Sammlung nur 4 Elemente haben, der Code jedoch versucht hat, ein Element in den Index 5 aufzunehmen, wird IndexOutOfRangeException ausgelöst. Index prüfen = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy vor

Antworten:

230

Was ist es?

Diese Ausnahme bedeutet, dass Sie versuchen, mit einem ungültigen Index indexweise auf ein Sammlungselement zuzugreifen. Ein Index ist ungültig, wenn er niedriger als die Untergrenze der Sammlung oder größer oder gleich der Anzahl der darin enthaltenen Elemente ist.

Wenn es geworfen wird

Gegeben ein Array deklariert als:

byte[] array = new byte[4];

Sie können von 0 bis 3 auf dieses Array zugreifen. Werte außerhalb dieses Bereichs IndexOutOfRangeExceptionwerden ausgelöst. Denken Sie daran, wenn Sie ein Array erstellen und darauf zugreifen.

Array-Länge
In C # basieren Arrays normalerweise auf 0. Dies bedeutet, dass das erste Element den Index 0 und das letzte Element den Index hat Length - 1(wobei Lengthdie Gesamtzahl der Elemente im Array angegeben ist), sodass dieser Code nicht funktioniert:

array[array.Length] = 0;

Beachten Sie außerdem, dass Sie bei Verwendung eines mehrdimensionalen Arrays, das Sie nicht Array.Lengthfür beide Dimensionen verwenden können, Folgendes verwenden müssen 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;
    }
}


Obergrenze ist nicht inklusive Im folgenden Beispiel erstellen wir ein zweidimensionales Roharray von Color. Jedes Element stellt ein Pixel dar, die Indizes sind von (0, 0)bis (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;
    }
}

Dieser Code schlägt dann fehl, da das Array auf 0 basiert und das letzte Pixel (unten rechts) im Bild wie folgt lautet pixels[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

In einem anderen Szenario erhalten Sie möglicherweise ArgumentOutOfRangeExceptiondiesen Code (z. B. wenn Sie eine GetPixelMethode für eine BitmapKlasse verwenden).

Arrays wachsen nicht
Ein Array ist schnell. Sehr schnell in der linearen Suche im Vergleich zu jeder anderen Sammlung. Dies liegt daran, dass Elemente im Speicher zusammenhängend sind, sodass die Speicheradresse berechnet werden kann (und das Inkrement nur eine Ergänzung ist). Keine Notwendigkeit, einer Knotenliste zu folgen, einfache Mathematik! Sie zahlen dies mit einer Einschränkung: Sie können nicht wachsen. Wenn Sie mehr Elemente benötigen, müssen Sie dieses Array neu zuweisen (dies kann relativ lange dauern, wenn alte Elemente in einen neuen Block kopiert werden müssen). Sie ändern die Größe mit Array.Resize<T>(). In diesem Beispiel wird einem vorhandenen Array ein neuer Eintrag hinzugefügt:

Array.Resize(ref array, array.Length + 1);

Vergessen Sie nicht, dass gültige Indizes von 0bis sind Length - 1. Wenn Sie einfach versuchen, ein Element zuzuweisen, erhalten LengthSie IndexOutOfRangeException(dieses Verhalten kann Sie verwirren, wenn Sie glauben, dass es mit einer Syntax zunimmt, die der InsertMethode anderer Sammlungen ähnelt ).

Spezielle Arrays mit benutzerdefinierter Untergrenze Das
erste Element in Arrays hat immer den Index 0 . Dies ist nicht immer der Fall, da Sie ein Array mit einer benutzerdefinierten Untergrenze erstellen können:

var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });

In diesem Beispiel sind Array-Indizes von 1 bis 4 gültig. Natürlich kann die Obergrenze nicht geändert werden.

Falsche Argumente
Wenn Sie mit nicht validierten Argumenten (von Benutzereingaben oder vom Funktionsbenutzer) auf ein Array zugreifen, wird möglicherweise folgende Fehlermeldung angezeigt:

private static string[] RomanNumbers =
    new string[] { "I", "II", "III", "IV", "V" };

public static string Romanize(int number)
{
    return RomanNumbers[number];
}

Unerwartete Ergebnisse
Diese Ausnahme kann auch aus einem anderen Grund ausgelöst werden: Konventionell geben viele Suchfunktionen -1 zurück (nullables wurde mit .NET 2.0 eingeführt und es ist auch eine bekannte Konvention, die seit vielen Jahren verwendet wird), wenn dies nicht der Fall ist. nichts finden. Stellen Sie sich vor, Sie haben eine Reihe von Objekten, die mit einer Zeichenfolge vergleichbar sind. Sie könnten denken, diesen Code zu schreiben:

// 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")]);

Dies schlägt fehl, wenn keine Elemente in der myArraySuchbedingung erfüllt sind, da Array.IndexOf()-1 zurückgegeben wird und der Array-Zugriff ausgelöst wird.

Das nächste Beispiel ist ein naives Beispiel für die Berechnung des Auftretens eines bestimmten Satzes von Zahlen (Kenntnis der maximalen Anzahl und Rückgabe eines Arrays, bei dem Element bei Index 0 für Nummer 0 steht, Elemente bei Index 1 für Nummer 1 usw.):

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;
}

Natürlich ist es eine ziemlich schreckliche Implementierung, aber was ich zeigen möchte, ist, dass es bei negativen Zahlen und Zahlen oben fehlschlägt maximum .

Wie es gilt List<T> ?

Gleiche Fälle wie Array - Bereich gültiger Indizes - 0 (List die Indizes beginnen immer mit 0) bis list.Count- Zugriff auf Elemente außerhalb dieses Bereichs verursachen die Ausnahme.

Beachten Sie, dass List<T>wirftArgumentOutOfRangeException für dieselben Fälle gelten, in denen Arrays verwendet werden IndexOutOfRangeException.

Im Gegensatz zu Arrays wird der List<T>Start leer. Der Versuch, auf Elemente der gerade erstellten Liste zuzugreifen, führt zu dieser Ausnahme.

var list = new List<int>();

Häufig führt das Auffüllen einer Liste mit Indizierung (ähnlich wie Dictionary<int, T>) zu einer Ausnahme:

list[0] = 42; // exception
list.Add(42); // correct

IDataReader und Spalten
Stellen Sie sich vor, Sie versuchen, Daten aus einer Datenbank mit diesem Code zu lesen:

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()wird werfen, IndexOutOfRangeExceptionweil Ihr Datensatz nur zwei Spalten hat, aber Sie versuchen, einen Wert von der dritten zu erhalten (Indizes sind immer auf 0).

Bitte beachten Sie, dass dieses Verhalten mit den meisten IDataReaderImplementierungen geteilt wird ( SqlDataReader,OleDbDataReader usw.).

Sie können dieselbe Ausnahme auch erhalten, wenn Sie die IDataReader-Überladung des Indexeroperators verwenden, der einen Spaltennamen verwendet und einen ungültigen Spaltennamen übergibt.
Angenommen, Sie haben eine Spalte mit dem Namen Column1 abgerufen, versuchen dann aber, den Wert dieses Felds mit abzurufen

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

Dies liegt daran, dass der Indexeroperator implementiert ist und versucht, den Index eines Colum1 abzurufen dass nicht vorhandenen Felds . Die GetOrdinal-Methode löst diese Ausnahme aus, wenn ihr interner Hilfscode -1 als Index für "Colum1" zurückgibt.

Andere
Es gibt einen anderen (dokumentierten) Fall, in dem diese Ausnahme ausgelöst wird: Wenn der DataViewName der Datenspalte an dieDataViewSort Eigenschaft übergeben wird, ungültig ist.

Wie zu vermeiden

Lassen Sie mich in diesem Beispiel der Einfachheit halber annehmen, dass Arrays immer monodimensional und 0-basiert sind. Wenn Sie streng sein wollen (oder Sie entwickeln eine Bibliothek), müssen Sie ersetzen 0mit GetLowerBound(0)und .Lengthmit GetUpperBound(0)(natürlich nur, wenn Sie die Parameter des Typs haben System.Array, gilt sie nicht für T[]). Bitte beachten Sie, dass in diesem Fall die Obergrenze den folgenden Code enthält:

for (int i=0; i < array.Length; ++i) { }

Sollte folgendermaßen umgeschrieben werden:

for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }

Bitte beachten Sie, dass dies nicht zulässig ist (es wird geworfen InvalidCastException). Wenn Ihre Parameter T[]sicher sind, können Sie daher benutzerdefinierte Arrays mit niedrigeren Grenzen verwenden:

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 }));
}

Parameter validieren
Wenn der Index von einem Parameter stammt, sollten Sie diese immer validieren (entsprechend werfen ArgumentExceptionoder ArgumentOutOfRangeException). Im nächsten Beispiel können falsche Parameter dazu führen IndexOutOfRangeException, dass Benutzer dieser Funktion dies erwarten, weil sie ein Array übergeben, dies jedoch nicht immer so offensichtlich ist. Ich würde vorschlagen, immer Parameter für öffentliche Funktionen zu validieren:

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);
}

Wenn die Funktion privat ist, können Sie die ifLogik einfach durch Folgendes ersetzen Debug.Assert():

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

Check Object State
Array-Index stammt möglicherweise nicht direkt von einem Parameter. Es kann Teil des Objektzustands sein. Im Allgemeinen ist es immer eine gute Praxis, den Objektstatus zu validieren (für sich und bei Bedarf mit Funktionsparametern). Sie können Debug.Assert()eine geeignete Ausnahme verwenden (das Problem genauer beschreiben) oder wie in diesem Beispiel behandeln:

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];
        }
}

Rückgabewerte validieren
In einem der vorherigen Beispiele haben wir den Array.IndexOf()Rückgabewert direkt verwendet . Wenn wir wissen, dass es fehlschlagen kann, ist es besser, diesen Fall zu behandeln:

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

So debuggen Sie

Meiner Meinung nach können die meisten Fragen hier auf SO zu diesem Fehler einfach vermieden werden. Die Zeit, die Sie zum Schreiben einer richtigen Frage (mit einem kleinen Arbeitsbeispiel und einer kleinen Erklärung) aufwenden, kann leicht viel mehr sein als die Zeit, die Sie zum Debuggen Ihres Codes benötigen. Lesen Sie zunächst den Blog-Beitrag von Eric Lippert über das Debuggen kleiner Programme . Ich werde seine Worte hier nicht wiederholen, aber es ist absolut ein Muss .

Sie haben Quellcode, Sie haben eine Ausnahmemeldung mit einem Stack-Trace. Gehen Sie dorthin, wählen Sie die richtige Zeilennummer und Sie werden sehen:

array[index] = newValue;

Sie haben Ihren Fehler gefunden, überprüfen Sie, wie sich indexerhöht. Ist es richtig? Überprüfen Sie, wie das Array zugewiesen wird. Stimmt dies mit den indexErhöhungen überein? Ist es richtig nach Ihren Vorgaben? Wenn Sie mit Ja antworten all diese Fragen mit beantworten, finden Sie hier auf StackOverflow gute Hilfe. Überprüfen Sie dies jedoch zunächst selbst. Sie sparen Ihre eigene Zeit!

Ein guter Ausgangspunkt ist, immer Zusicherungen zu verwenden und Eingaben zu validieren. Möglicherweise möchten Sie sogar Codeverträge verwenden. Wenn etwas schief gelaufen ist und Sie mit einem kurzen Blick auf Ihren Code nicht herausfinden können, was passiert, müssen Sie auf einen alten Freund zurückgreifen: den Debugger . Führen Sie einfach Ihre Anwendung im Debug in Visual Studio (oder Ihrer bevorzugten IDE) aus. Sie sehen genau, welche Zeile diese Ausnahme auslöst, welches Array betroffen ist und welchen Index Sie verwenden möchten. Wirklich, 99% der Fälle lösen Sie es in wenigen Minuten selbst.

Wenn dies in der Produktion passiert, sollten Sie Behauptungen in belasteten Code einfügen. Wahrscheinlich sehen wir in Ihrem Code nicht, was Sie selbst nicht sehen können (aber Sie können immer wetten).

Die VB.NET-Seite der Geschichte

Alles, was wir in der C # -Antwort gesagt haben, gilt für VB.NET mit den offensichtlichen Syntaxunterschieden. Es ist jedoch ein wichtiger Punkt zu beachten, wenn Sie mit VB.NET-Arrays arbeiten.

In VB.NET werden Arrays deklariert, die den maximal gültigen Indexwert für das Array festlegen. Es ist nicht die Anzahl der Elemente, die wir im Array speichern möchten.

' declares an array with space for 5 integer 
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer

Diese Schleife füllt das Array also mit 5 Ganzzahlen, ohne eine IndexOutOfRangeException zu verursachen

For i As Integer = 0 To 4
    myArray(i) = i
Next

Die VB.NET-Regel

Diese Ausnahme bedeutet, dass Sie versuchen, mit einem ungültigen Index indexweise auf ein Sammlungselement zuzugreifen. Ein Index ist ungültig, wenn er niedriger als die Untergrenze der Sammlung oder größer als istgleich der Anzahl der darin enthaltenen Elemente. Der maximal zulässige Index, der in der Array-Deklaration definiert ist

Adriano Repetti
quelle
19

Einfache Erklärung, was ein Index außerhalb der gebundenen Ausnahme ist:

Denken Sie nur, ein Zug ist da, seine Abteile sind D1, D2, D3. Ein Passagier stieg in den Zug ein und hat das Ticket für D4. Was wird jetzt passieren? Der Passagier möchte ein Abteil betreten, das nicht existiert, so dass offensichtlich ein Problem auftritt.

Gleiches Szenario: Wenn wir versuchen, auf eine Array-Liste usw. zuzugreifen, können wir nur auf die vorhandenen Indizes im Array zugreifen. array[0]und array[1]sind vorhanden. Wenn wir versuchen, darauf zuzugreifen array[3], ist es tatsächlich nicht vorhanden, sodass ein Index außerhalb der gebundenen Ausnahme entsteht.

Lijo
quelle
10

Stellen Sie sich vor, wir haben diesen Code geschrieben, um das Problem leicht zu verstehen:

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());
    }
}

Ergebnis wird sein:

hello1
hello2
hello3

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

Die Größe des Arrays beträgt 3 (Indizes 0, 1 und 2), aber die for-Schleife wiederholt sich viermal (0, 1, 2 und 3).
Wenn also versucht wird, mit (3) außerhalb der Grenzen zuzugreifen, wird die Ausnahme ausgelöst.

Snr
quelle
1

Neben der sehr langen, vollständig akzeptierten Antwort ist im IndexOutOfRangeExceptionVergleich zu vielen anderen Ausnahmetypen ein wichtiger Punkt zu beachten, und zwar:

Oft gibt es einen komplexen Programmstatus, über den an einem bestimmten Punkt im Code möglicherweise nur schwer die Kontrolle zu haben ist, z. B. wenn eine DB-Verbindung ausfällt, sodass Daten für eine Eingabe nicht abgerufen werden können usw. Diese Art von Problem führt häufig zu einer Ausnahme muss auf eine höhere Ebene aufsteigen, weil es an diesem Punkt keine Möglichkeit gibt, damit umzugehen, wo es auftritt.

IndexOutOfRangeExceptionist im Allgemeinen insofern anders, als es in den meisten Fällen ziemlich trivial ist, an dem Punkt zu prüfen, an dem die Ausnahme ausgelöst wird. Im Allgemeinen wird diese Art von Ausnahme durch einen Code ausgelöst, der das Problem an der Stelle, an der es auftritt, sehr leicht beheben kann - nur durch Überprüfen der tatsächlichen Länge des Arrays. Sie möchten dies nicht beheben, indem Sie diese Ausnahme höher behandeln, sondern sicherstellen, dass sie nicht in erster Linie ausgelöst wird. Dies ist in den meisten Fällen einfach, indem Sie die Array-Länge überprüfen.

Eine andere Möglichkeit, dies auszudrücken, besteht darin, dass andere Ausnahmen aufgrund eines echten Mangels an Kontrolle über die Eingabe oder den Programmstatus auftreten können, ABER IndexOutOfRangeExceptionmeistens ist es einfach nur ein Pilotfehler (Programmierer).

Ricibob
quelle