Що таке IndexOutOfRangeException / ArgumentOutOfRangeException і як це виправити?


191

У мене є якийсь код, і коли він виконується, він кидає IndexOutOfRangeException, кажучи:

Список був за межами масиву.

Що це означає, і що я можу з цим зробити?

Залежно від класів, які використовуються, це також може бути ArgumentOutOfRangeException

Виняток типу "System.ArgumentOutOfRangeException" стався в mscorlib.dll, але він не оброблявся в коді користувача Додаткова інформація: Індекс був поза діапазоном. Повинно бути невід’ємним та меншим за розмір колекції.


У вашій колекції, якщо у вас є лише 4 елементи, але код намагався отримати елемент в індексі 5. Це призведе до виведення IndexOutOfRangeException. Перевірте індекс = 5; if (items.Length> = індекс) Console.WriteLine (intems [index]);
Бабу Кумарасамі

Відповіді:


232

Що це?

Цей виняток означає, що ви намагаєтесь отримати доступ до елемента колекції за індексом, використовуючи недійсний індекс. Індекс недійсний, коли він нижчий від нижньої межі колекції або більший або дорівнює кількості елементів, які він містить.

Коли воно кинеться

Дано масив, оголошений як:

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.Array, це не застосовується 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

Цей виняток означає, що ви намагаєтесь отримати доступ до елемента колекції за індексом, використовуючи недійсний індекс. Індекс недійсний, якщо він нижчий від нижньої межі колекції або перевищуєдорівнює кількості елементів, які вона містить. максимально дозволений індекс, визначений у декларації масиву


19

Просте пояснення щодо того, що таке індекс поза обмеженим винятком:

Подумайте лише, що один поїзд є там, де його купе - D1, D2, D3. Один пасажир зайшов у поїзд, і він має квиток на D4. тепер що буде. пасажир хоче зайти в купе, яке не існує, тому очевидно виникне проблема.

Той самий сценарій: коли ми намагаємося отримати доступ до списку масивів тощо, ми можемо отримати доступ лише до наявних індексів у масиві. array[0]і array[1]існують. Якщо ми спробуємо отримати доступ array[3], насправді його немає, тож виникне індекс поза обмеженим винятком.


10

Щоб легко зрозуміти проблему, уявіть, що ми написали цей код:

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

Результатом буде:

hello1
hello2
hello3

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

Розмір масиву - 3 (індекси 0, 1 і 2), але петлі для циклу 4 рази (0, 1, 2 і 3).
Отже, коли він намагається отримати доступ за межі (3), він викидає виняток.


1

Сторона дуже довго прийнятої відповіді є важливим моментом, який слід зробити IndexOutOfRangeExceptionпорівняно з багатьма іншими видами винятків, і це:

Часто існує складний стан програми, який, можливо, важко мати контроль над певною точкою коду, наприклад, з'єднання БД знижується, тому дані для введення неможливо отримати тощо. Цей вид проблеми часто призводить до винятків, які має піднятися на більш високий рівень, тому що там, де це відбувається, немає можливості впоратися з цим у той момент.

IndexOutOfRangeExceptionяк правило, відрізняється тим, що це в більшості випадків досить тривіально перевіряти в тому місці, де відбувається виняток. Як правило, на цей виняток потрапляє якийсь код, який міг би дуже легко впоратися з проблемою в тому місці, де воно відбувається - лише перевіривши фактичну довжину масиву. Ви не хочете «виправляти» це, обробляючи цей виняток вище, а натомість, гарантуючи, що він не викидається в першу чергу, - що в більшості випадків легко зробити, перевіривши довжину масиву.

Іншим способом цього є те, що інші винятки можуть виникати через справжній відсутність контролю над входом або станом програми, але IndexOutOfRangeExceptionНЕ частіше, ніж просто помилка пілота (програміста).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.