Co to jest IndexOutOfRangeException / ArgumentOutOfRangeException i jak to naprawić?


191

Mam trochę kodu, a kiedy się wykonuje, rzuca IndexOutOfRangeException, mówiąc:

Indeks znajdował się poza granicami tablicy.

Co to znaczy i co mogę z tym zrobić?

W zależności od użytych klas może być również ArgumentOutOfRangeException

W mscorlib.dll wystąpił wyjątek typu „System.ArgumentOutOfRangeException”, ale nie został obsłużony w kodzie użytkownika. Informacje dodatkowe: Indeks był poza zakresem. Musi być nieujemny i mniejszy niż rozmiar kolekcji.


W twojej kolekcji, jeśli masz tylko 4 elementy, ale kod próbował uzyskać pozycję w indeksie 5. Spowoduje to wygenerowanie wyjątku IndexOutOfRangeException. Sprawdź indeks = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy

Odpowiedzi:


232

Co to jest?

Ten wyjątek oznacza, że ​​próbujesz uzyskać dostęp do elementu kolekcji według indeksu, używając nieprawidłowego indeksu. Indeks jest nieprawidłowy, gdy jest niższy niż dolna granica kolekcji lub większy lub równy liczbie elementów w nim zawartych.

Kiedy zostanie rzucony

Biorąc pod uwagę tablicę zadeklarowaną jako:

byte[] array = new byte[4];

Możesz uzyskać dostęp do tej tablicy od 0 do 3, wartości poza tym zakresem spowodują IndexOutOfRangeExceptionwyrzucenie. Pamiętaj o tym podczas tworzenia i uzyskiwania dostępu do tablicy.

Długość tablicy
W języku C # zazwyczaj tablice są oparte na 0. Oznacza to, że pierwszy element ma indeks 0, a ostatni element ma indeks Length - 1(gdzie Lengthjest łączna liczba elementów w tablicy), więc ten kod nie działa:

array[array.Length] = 0;

Ponadto należy pamiętać, że jeśli masz tablicę wielowymiarową, to nie możesz użyć Array.Lengthdla obu wymiarów, musisz użyć 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;
    }
}

Górna granica nie jest włączająca
W poniższym przykładzie tworzymy surową tablicę dwuwymiarową Color. Każdy element reprezentuje piksel, indeksy są od (0, 0)do (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;
    }
}

Ten kod nie powiedzie się, ponieważ tablica jest oparta na 0, a ostatni (prawy dolny) piksel na obrazie to pixels[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

W innym scenariuszu możesz otrzymać ArgumentOutOfRangeExceptionten kod (na przykład, jeśli używasz GetPixelmetody na Bitmapklasie).

Tablice nie rosną
Tablica jest szybka. Bardzo szybki w wyszukiwaniu liniowym w porównaniu do każdej innej kolekcji. Dzieje się tak, ponieważ elementy są ciągłe w pamięci, dzięki czemu można obliczyć adres pamięci (a przyrost jest tylko dodatkiem). Nie musisz śledzić listy węzłów, prosta matematyka! Płacisz to z ograniczeniem: nie mogą rosnąć, jeśli potrzebujesz więcej elementów, musisz ponownie przydzielić tę tablicę (może to zająć stosunkowo dużo czasu, jeśli stare elementy muszą zostać skopiowane do nowego bloku). Zmieniasz ich rozmiar za pomocą Array.Resize<T>(), ten przykład dodaje nowy wpis do istniejącej tablicy:

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

Nie zapominaj, że prawidłowe indeksy są od 0do Length - 1. Jeśli po prostu spróbujesz przypisać element Length, otrzymasz IndexOutOfRangeException(to zachowanie może Cię pomylić, jeśli uważasz, że może wzrosnąć przy składni podobnej do Insertmetody z innych kolekcji).

Tablice specjalne z niestandardową dolną granicą
Pierwszy element w tablicach ma zawsze indeks 0 . Nie zawsze jest to prawdą, ponieważ możesz utworzyć tablicę z niestandardową dolną granicą:

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

W tym przykładzie indeksy tablic są prawidłowe od 1 do 4. Oczywiście górnej granicy nie można zmienić.

Błędne argumenty
Jeśli uzyskujesz dostęp do tablicy za pomocą nieważnych argumentów (z danych wprowadzonych przez użytkownika lub od funkcji użytkownika), możesz otrzymać ten błąd:

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

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

Nieoczekiwane wyniki
Ten wyjątek może zostać zgłoszony również z innego powodu: zgodnie z konwencją wiele funkcji wyszukiwania zwróci -1 (wprowadzono wartości zerowe w .NET 2.0, a zresztą jest to również znana konwencja używana od wielu lat), jeśli nie nic znaleźć. Wyobraźmy sobie, że masz szereg obiektów porównywalnych z ciągiem znaków. Możesz napisać ten kod:

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

Nie powiedzie się, jeśli żaden element myArraynie spełni warunku wyszukiwania, ponieważArray.IndexOf() zwróci -1, a następnie dostęp do tablicy zostanie wygenerowany.

Następny przykład to naiwny przykład obliczania wystąpień danego zestawu liczb (znajomość liczby maksymalnej i zwrócenie tablicy, w której element o indeksie 0 reprezentuje liczbę 0, elementy o indeksie 1 reprezentuje liczbę 1 itd.):

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

Oczywiście jest to dość okropna implementacja, ale chcę pokazać, że zawiedzie dla liczb ujemnych i liczb powyżej maximum.

Jak to dotyczy List<T>?

Te same przypadki, co w tablicy - zakres prawidłowych indeksów - 0 ( Listindeksy zawsze zaczynają się od 0), aby uzyskać list.Countdostęp do elementów poza tym zakresem spowoduje wyjątek.

Zauważ, że List<T>rzuca ArgumentOutOfRangeExceptiondla tych samych przypadków, w których używane są tablice IndexOutOfRangeException.

W przeciwieństwie do tablic, List<T>zaczyna się pusty - więc próba dostępu do elementów właśnie utworzonej listy prowadzi do tego wyjątku.

var list = new List<int>();

Częstym przypadkiem jest zapełnienie listy indeksowaniem (podobnie do Dictionary<int, T>) spowoduje wyjątek:

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

IDataReader i kolumny
Wyobraź sobie, że próbujesz odczytać dane z bazy danych za pomocą tego kodu:

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()wyrzuci, IndexOutOfRangeExceptionponieważ twój zestaw danych ma tylko dwie kolumny, ale próbujesz uzyskać wartość z trzeciej (indeksy są zawsze oparte na 0).

Należy pamiętać, że takie zachowanie jest wspólne z większości IDataReaderwdrożeń ( SqlDataReader, OleDbDataReaderi tak dalej).

Ten sam wyjątek można uzyskać również w przypadku przeciążenia IDataReader operatora indeksującego, który przyjmuje nazwę kolumny i przekazuje niepoprawną nazwę kolumny.
Załóżmy na przykład, że pobrałeś kolumnę o nazwie Kolumna1, ale następnie próbujesz pobrać wartość tego pola za pomocą

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

Dzieje się tak, ponieważ operator indeksatora jest zaimplementowany, próbując pobrać indeks pola Colum1 , który nie istnieje. Metoda GetOrdinal zgłosi ten wyjątek, gdy wewnętrzny kod pomocnika zwróci -1 jako indeks „Colum1”.

Inne
Istnieje inny (udokumentowany) przypadek, w którym zgłoszony jest ten wyjątek: jeśli, w DataView, nazwa kolumny danych podawana do DataViewSortwłaściwości jest nieprawidłowa.

Jak ominąć

W tym przykładzie przyjmijmy dla uproszczenia, że ​​tablice są zawsze jednowymiarowe i oparte na 0. Jeśli chcesz być surowe (lub jesteś tworzy bibliotekę), może trzeba wymienić 0z GetLowerBound(0)i .Lengthz GetUpperBound(0)(oczywiście jeśli masz parametry typu System.ArraY, to nie dotyczy T[]). Należy pamiętać, że w tym przypadku górna granica jest włącznie, a następnie ten kod:

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

Powinny zostać przepisane w następujący sposób:

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

Pamiętaj, że jest to niedozwolone (wyrzuci InvalidCastException), dlatego jeśli twoje parametry są T[]bezpieczne w przypadku niestandardowych tablic dolnych granic:

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

Sprawdź parametry
Jeśli indeks pochodzi z parametru, powinieneś go zawsze zweryfikować (rzucając odpowiedni ArgumentExceptionlub ArgumentOutOfRangeException). W następnym przykładzie mogą wystąpić niepoprawne parametry IndexOutOfRangeException, użytkownicy tej funkcji mogą się tego spodziewać, ponieważ przekazują tablicę, ale nie zawsze jest to tak oczywiste. Proponuję zawsze sprawdzać poprawność parametrów funkcji publicznych:

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

Jeśli funkcja jest prywatna, możesz po prostu zastąpić iflogikę Debug.Assert():

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

Sprawdź
indeks tablicy stanu obiektu może nie pochodzić bezpośrednio z parametru. Może być częścią stanu obiektu. Zasadniczo zawsze dobrą praktyką jest sprawdzanie stanu obiektu (samo z parametrami funkcji, jeśli to konieczne). Możesz użyć Debug.Assert(), rzucić odpowiedni wyjątek (bardziej opisowy na temat problemu) lub obsłużyć go jak w tym przykładzie:

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

Zweryfikuj wartości zwracane
W jednym z poprzednich przykładów bezpośrednio użyliśmy Array.IndexOf()wartości zwracanej. Jeśli wiemy, że może się nie powieść, lepiej poradzić sobie z tą sprawą:

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

Jak debugować

Moim zdaniem, większości pytań tutaj, na SO, można po prostu uniknąć. Czas, który poświęcasz na napisanie właściwego pytania (z małym działającym przykładem i małym wyjaśnieniem) może z łatwością znacznie więcej niż czas potrzebny na debugowanie kodu. Przede wszystkim przeczytaj ten post na blogu Erica Lipperta o debugowaniu małych programów. Nie powtórzę tutaj jego słów, ale jest to absolutnie konieczne .

Masz kod źródłowy, masz komunikat wyjątku ze śledzeniem stosu. Idź tam, wybierz odpowiedni numer linii, a zobaczysz:

array[index] = newValue;

Znalazłeś błąd, sprawdź, jak indexrośnie. Czy to jest poprawne? Sprawdź, w jaki sposób alokowana jest tablica, czy jest spójna ze indexwzrostem? Czy to zgodne z twoimi specyfikacjami? Jeśli odpowiesz tak na wszystkie te pytania, znajdziesz tutaj pomoc na StackOverflow, ale najpierw sprawdź ją samodzielnie. Zaoszczędzisz swój czas!

Dobrym punktem wyjścia jest zawsze stosowanie asercji i sprawdzanie poprawności danych wejściowych. Możesz nawet chcieć skorzystać z umów kodowych. Kiedy coś poszło nie tak i nie możesz dowiedzieć się, co dzieje się z szybkim spojrzeniem na swój kod, musisz skorzystać ze starego znajomego: debuggera . Wystarczy uruchomić aplikację podczas debugowania w programie Visual Studio (lub ulubionym środowisku IDE), zobaczysz dokładnie, która linia generuje ten wyjątek, która tablica jest zaangażowana i jakiego indeksu próbujesz użyć. Naprawdę, 99% razy rozwiążesz go samodzielnie w ciągu kilku minut.

Jeśli tak się stanie w produkcji, lepiej dodaj twierdzenia do obciążonego kodu, prawdopodobnie nie zobaczymy w twoim kodzie tego, czego sam nie widzisz (ale zawsze możesz postawić).

Strona VB.NET tej historii

Wszystko, co powiedzieliśmy w odpowiedzi w języku C #, jest ważne dla VB.NET z oczywistymi różnicami w składni, ale należy wziąć pod uwagę ważną kwestię, mając do czynienia z tablicami VB.NET.

W VB.NET tablice deklarują ustawienie maksymalnej poprawnej wartości indeksu dla tablicy. To nie jest liczba elementów, które chcemy przechowywać w tablicy.

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

Zatem ta pętla wypełni tablicę 5 liczbami całkowitymi bez powodowania żadnego wyjątku IndexOutOfRangeException

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

Reguła VB.NET

Ten wyjątek oznacza, że ​​próbujesz uzyskać dostęp do elementu kolekcji według indeksu, używając nieprawidłowego indeksu. Indeks jest nieprawidłowy, gdy jest niższy niż dolna granica kolekcji lub większy niżrówna liczbie zawartych w nim elementów. maksymalny dozwolony indeks zdefiniowany w deklaracji tablicowej


19

Proste wyjaśnienie, czym jest wyjątek poza ograniczonym indeksem:

Pomyśl tylko o jednym pociągu, którego przedziały to D1, D2, D3. Jeden pasażer przyszedł, aby wsiąść do pociągu i ma bilet na D4. teraz co się stanie. pasażer chce wejść do przedziału, który nie istnieje, więc oczywiście pojawią się problemy.

Ten sam scenariusz: ilekroć próbujemy uzyskać dostęp do listy tablic itp., Możemy uzyskać dostęp tylko do istniejących indeksów w tablicy. array[0]i array[1]istnieją. Jeśli spróbujemy uzyskać dostęp array[3], nie ma go tak naprawdę, więc powstanie indeks poza ograniczonym wyjątkiem.


10

Aby łatwo zrozumieć problem, wyobraź sobie, że napisaliśmy ten kod:

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

Wynik będzie:

hello1
hello2
hello3

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

Rozmiar tablicy wynosi 3 (indeksy 0, 1 i 2), ale pętla for-loop 4 razy (0, 1, 2 i 3).
Kiedy więc próbuje uzyskać dostęp poza granicami za pomocą (3), zgłasza wyjątek.


1

Oprócz bardzo długiej, kompletnej, zaakceptowanej odpowiedzi należy zwrócić uwagę na IndexOutOfRangeExceptionwiele innych typów wyjątków, a mianowicie:

Często występuje złożony stan programu, który może być trudny do kontrolowania w określonym punkcie kodu, np. Połączenie DB zostaje przerwane, więc danych wejściowych nie można odzyskać itp. Tego rodzaju problem często powoduje wyjątek, który musi podnieść się na wyższy poziom, ponieważ tam, gdzie ma to miejsce, nie ma sposobu na poradzenie sobie z tym w tym momencie.

IndexOutOfRangeExceptionogólnie różni się tym, że w większości przypadków sprawdzanie w momencie zgłaszania wyjątku jest dość trywialne. Zasadniczo ten wyjątek generowany jest przez jakiś kod, który bardzo łatwo poradziłby sobie z problemem w miejscu, w którym się pojawia - po prostu sprawdzając rzeczywistą długość tablicy. Nie chcesz tego „naprawiać”, obsługując ten wyjątek wyżej - ale zamiast tego upewniając się, że nie zostanie zgłoszony w pierwszej instancji - co w większości przypadków jest łatwe do wykonania przez sprawdzenie długości tablicy.

Innym sposobem na określenie tego jest to, że mogą wystąpić inne wyjątki z powodu rzeczywistego braku kontroli nad wejściem lub stanem programu, ALE IndexOutOfRangeExceptionczęściej niż po prostu błąd pilota (programisty).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.