Що це?
Цей виняток означає, що ви намагаєтесь отримати доступ до елемента колекції за індексом, використовуючи недійсний індекс. Індекс недійсний, коли він нижчий від нижньої межі колекції або більший або дорівнює кількості елементів, які він містить.
Коли воно кинеться
Дано масив, оголошений як:
byte[] array = new byte[4];
Ви можете отримати доступ до цього масиву від 0 до 3, значення поза цим діапазоном призведуть IndexOutOfRangeException
до викидання. Пам'ятайте про це, коли ви створюєте та отримуєте доступ до масиву.
Довжина масиву
в C #, як правило, масиви базуються на 0. Це означає, що перший елемент має індекс 0, а останній - індекс Length - 1
(де Length
загальна кількість елементів у масиві), тому цей код не працює:
array[array.Length] = 0;
Крім того, зверніть увагу, що якщо у вас багатовимірний масив, ви не можете використовувати Array.Length
обидва виміри, ви повинні використовувати 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;
}
}
Верхня межа не включена
У наступному прикладі ми створюємо необмежений двовимірний масив Color
. Кожен елемент представляє піксель, індекси від (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;
}
}
Потім цей код не вдасться, оскільки масив базується на 0, а останній (нижній правий) піксель на зображенні pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
В іншому випадку ви можете отримати ArgumentOutOfRangeException
цей код (наприклад, якщо ви використовуєте GetPixel
метод на Bitmap
уроці).
Масиви не ростуть
Масив швидко. Дуже швидкий у лінійному пошуку порівняно з усіма іншими колекціями. Це тому, що елементи є суміжними в пам'яті, тому адресу пам'яті можна обчислити (а приріст - лише доповнення). Не потрібно слідувати списку вузлів, проста математика! Ви сплачуєте це з обмеженням: вони не можуть рости, якщо вам потрібно більше елементів, вам потрібно перерозподілити цей масив (це може зайняти відносно довгий час, якщо старі елементи потрібно скопіювати в новий блок). Ви зміните їх розмір за допомогою Array.Resize<T>()
цього прикладу додає новий запис до існуючого масиву:
Array.Resize(ref array, array.Length + 1);
Не забувайте , що дійсні індекси від 0
до Length - 1
. Якщо ви просто спробуєте призначити елемент, який Length
ви отримаєте IndexOutOfRangeException
(така поведінка може вас збентежити, якщо ви думаєте, що вони можуть збільшитись із синтаксисом, подібним до Insert
методу інших колекцій).
Спеціальні масиви зі спеціальними нижчими межами
Перший елемент масивів завжди має індекс 0 . Це не завжди відповідає дійсності, оскільки ви можете створити масив із власною нижньою межею:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
У цьому прикладі індекси масиву дійсні від 1 до 4. Звичайно, верхня межа не може бути змінена.
Неправильні аргументи
Якщо ви отримуєте доступ до масиву за допомогою неправільних аргументів (з вводу користувача або від користувача функції), ви можете отримати цю помилку:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
Неочікувані результати
Цей виняток може бути викинутий і з іншої причини: за умовою, багато пошукових функцій повертаються -1 (нульові показники були введені за допомогою .NET 2.0, і все-таки це також добре відома конвенція, яка використовується вже багато років), якщо вони не були ' не знайти нічого. Давайте уявимо, у вас є масив об'єктів, порівнянний із рядком. Ви можете подумати, щоб написати цей код:
// 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")]);
Це не вдасться, якщо жоден елемент не myArray
буде задовольняти умову пошуку, оскільки Array.IndexOf()
повернеться -1 і тоді доступ до масиву буде кинутий.
Наступний приклад - наївний приклад для обчислення входжень заданого набору чисел (знаючи максимальну кількість та повертаючи масив, де елемент в індексі 0 представляє число 0, елементи в індексі 1 представляють число 1 тощо):
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;
}
Звичайно, це досить жахлива реалізація, але те, що я хочу показати, це те, що вона не зможе отримати негативні числа та цифри вище maximum
.
Як це стосується List<T>
?
Виняток становлять ті самі випадки, що і масив - діапазон дійсних індексів - 0 ( List
індекси завжди починаються з 0) до list.Count
- доступу до елементів поза цим діапазоном.
Зауважте, що List<T>
кидки ArgumentOutOfRangeException
для тих же випадків, де використовуються масиви IndexOutOfRangeException
.
На відміну від масивів, він List<T>
починається порожнім - тому спроби отримати доступ до елементів щойно створеного списку призводять до цього винятку.
var list = new List<int>();
Поширений випадок - заповнення списку індексуванням (подібним до Dictionary<int, T>
) спричинить виняток:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader та стовпці
Уявіть, що ви намагаєтеся прочитати дані з бази даних із цим кодом:
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()
буде кинутий, IndexOutOfRangeException
тому що у вашого набору даних є лише два стовпці, але ви намагаєтеся отримати значення з 3-го (індекси завжди базуються на 0).
Зверніть увагу , що така поведінка є спільною для більшості IDataReader
реалізацій ( SqlDataReader
, OleDbDataReader
і так далі).
Ви можете отримати той самий виняток також, якщо використовувати перевантаження IDataReader оператора індексатора, який приймає ім'я стовпця і передає недійсне ім’я стовпця.
Припустимо, наприклад, що ви отримали стовпець під назвою Column1, але потім спробуйте отримати значення цього поля за допомогою
var data = dr["Colum1"]; // Missing the n in Column1.
Це відбувається тому, що оператор індексатора реалізований, намагаючись отримати індекс поля Colum1 , яке не існує. Метод GetOrdinal викине цей виняток, коли його внутрішній хелперний код поверне -1 як індекс "Colum1".
Інші
Існує ще один (задокументований) випадок, коли цей виняток скинуто: якщо, наприклад DataView
, ім'я стовпця даних, яке надається у DataViewSort
властивість, недійсне.
Як уникнути
У цьому прикладі дозвольте для простоти припустити, що масиви завжди є одновимірними та на основі 0. Якщо ви хочете бути суворими (або ви розробляєте бібліотеку), можливо, вам знадобиться замінити 0
на GetLowerBound(0)
і .Length
з GetUpperBound(0)
(звичайно, якщо у вас є параметри типу System.Arra
y, це не застосовується T[]
). Зверніть увагу, що в цьому випадку верхня межа включно, ніж цей код:
for (int i=0; i < array.Length; ++i) { }
Слід переписати так:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
Зауважте, що це заборонено (це буде кидати InvalidCastException
), тому якщо ваші параметри T[]
безпечні щодо користувацьких масивів нижньої межі:
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 }));
}
Параметри перевірки
Якщо індекс походить від параметра, ви завжди повинні їх перевірити (кидаючи відповідне ArgumentException
або ArgumentOutOfRangeException
). У наступному прикладі можуть виникнути неправильні параметри IndexOutOfRangeException
, користувачі цієї функції можуть очікувати цього, оскільки вони передають масив, але це не завжди так очевидно. Я пропоную завжди перевіряти параметри для публічних функцій:
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);
}
Якщо функція приватна, ви можете просто замінити if
логіку на Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
Перевірка
індексу масиву стану об'єкта може не надходити безпосередньо з параметра. Це може бути частиною стану об'єкта. Взагалі завжди є хорошою практикою перевірити стан об'єкта (сам по собі та з параметрами функції, якщо потрібно). Ви можете використовувати Debug.Assert()
, викинути належний виняток (більш описовий про проблему) або обробити це, як у цьому прикладі:
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];
}
}
Перевірка зворотних значень
В одному з попередніх прикладів ми безпосередньо використовували Array.IndexOf()
повернене значення. Якщо ми знаємо, що це може бути невдалим, тоді краще впоратися з цим випадком:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
Як налагодити
На мою думку, більшості запитань про цю помилку тут, на SO, можна просто уникнути. Час, який ви витратили на написання належного запитання (з невеликим робочим прикладом та невеликим поясненням), може бути набагато більшим, ніж час, який вам знадобиться для налагодження коду. Перш за все, прочитайте цю статтю в блозі Еріка Ліпперта про налагодження невеликих програм , я не повторюю його слова тут, але це абсолютно обов’язково читати .
У вас вихідний код, у вас є повідомлення про виключення зі слідом стека. Зайдіть туди, виберіть потрібний номер рядка і побачите:
array[index] = newValue;
Ви виявили свою помилку, перевірте, як index
збільшується. Це право? Перевірте, як розподіляється масив, чи узгоджується він із тим, як index
збільшується? Чи правильно це відповідно до ваших специфікацій? Якщо ви відповісте " так" на всі ці запитання, то ви знайдете хорошу допомогу тут на StackOverflow, але спочатку перевірте це самостійно. Ви заощадите власний час!
Хорошим початковим моментом є завжди використовувати твердження та перевіряти дані. Ви навіть можете скористатися кодовими контрактами. Коли щось пішло не так, і ви не можете зрозуміти, що відбувається при швидкому перегляді вашого коду, тоді вам доведеться вдатися до старого друга: налагоджувача . Просто запустіть додаток у налагодженні у Visual Studio (або улюбленому IDE), ви точно побачите, який рядок містить цей виняток, який масив залучений та який індекс ви намагаєтесь використовувати. Дійсно, у 99% разів ви вирішите це самостійно за кілька хвилин.
Якщо це трапляється у виробництві, тоді вам краще додати твердження у інкримінованому коді, ймовірно, ми не побачимо у вашому коді те, що ви не можете бачити самостійно (але ви завжди можете зробити ставку).
Сторона VB.NET розповіді
Все, що ми говорили у відповіді C #, є дійсним для VB.NET з очевидними синтаксичними відмінностями, але є важливий момент, який слід врахувати, коли ви маєте справу з масивами VB.NET.
У VB.NET масиви оголошуються, встановлюючи максимально допустиме значення індексу для масиву. Зберігати в масиві ми не можемо кількість елементів.
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
Таким чином, цей цикл заповнить масив 5 цілими числами, не викликаючи жодного IndexOutOfRangeException
For i As Integer = 0 To 4
myArray(i) = i
Next
Правило VB.NET
Цей виняток означає, що ви намагаєтесь отримати доступ до елемента колекції за індексом, використовуючи недійсний індекс. Індекс недійсний, якщо він нижчий від нижньої межі колекції або перевищуєдорівнює кількості елементів, які вона містить. максимально дозволений індекс, визначений у декларації масиву