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ą IndexOutOfRangeException
wyrzucenie. 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 Length
jest łą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.Length
dla 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ć ArgumentOutOfRangeException
ten kod (na przykład, jeśli używasz GetPixel
metody na Bitmap
klasie).
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 0
do 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 Insert
metody 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 myArray
nie 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 ( List
indeksy zawsze zaczynają się od 0), aby uzyskać list.Count
dostęp do elementów poza tym zakresem spowoduje wyjątek.
Zauważ, że List<T>
rzuca ArgumentOutOfRangeException
dla 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, IndexOutOfRangeException
ponieważ 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 IDataReader
wdrożeń ( SqlDataReader
, OleDbDataReader
i 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 DataViewSort
wł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ć 0
z GetLowerBound(0)
i .Length
z GetUpperBound(0)
(oczywiście jeśli masz parametry typu System.Arra
Y, 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 ArgumentException
lub 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ć if
logikę 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 index
rośnie. Czy to jest poprawne? Sprawdź, w jaki sposób alokowana jest tablica, czy jest spójna ze index
wzrostem? 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