Что такое исключение IndexOutOfRangeException / ArgumentOutOfRangeException и как его исправить?

191

У меня есть некоторый код, и когда он выполняется, он бросает IndexOutOfRangeException, говоря,

Индекс находился вне границ массива.

Что это значит, и что я могу с этим поделать?

В зависимости от используемых классов это также может быть ArgumentOutOfRangeException

Исключение типа «System.ArgumentOutOfRangeException» возникло в mscorlib.dll, но не было обработано в коде пользователя. Дополнительная информация: индекс находится вне диапазона. Должен быть неотрицательным и меньшим, чем размер коллекции.

Адриано Репетти
источник
В вашей коллекции, если у вас есть только 4 элемента, но код пытался получить элемент в индексе 5. Это вызовет исключение IndexOutOfRangeException. Проверить индекс = 5; if (items.Length> = index) 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 (nullables была введена в .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 and Columns
Представьте, что вы пытаетесь прочитать данные из базы данных с помощью следующего кода:

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потому что у вашего набора данных есть только два столбца, но вы пытаетесь получить значение от третьего (индексы всегда основаны на 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

Это исключение означает, что вы пытаетесь получить доступ к элементу коллекции по индексу, используя недопустимый индекс. Индекс недопустим, если он ниже нижней границы коллекции или большеравно количеству элементов, которые оно содержит. максимально допустимый индекс, определенный в объявлении массива

Adriano Repetti
источник
19

Простое объяснение того, что такое исключение Index вне привязки:

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

Тот же сценарий: всякий раз, когда мы пытаемся получить доступ к списку массивов и т. Д., Мы можем получить доступ только к существующим индексам в массиве. array[0]и array[1]существуют. Если мы попытаемся получить доступ array[3], его там на самом деле нет, поэтому возникнет исключение из индекса.

Lijo
источник
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), но цикл for повторяется 4 раза (0, 1, 2 и 3).
Поэтому, когда он пытается получить доступ за пределами (3), он генерирует исключение.

Snr
источник
1

Помимо очень длинного полного принятого ответа, есть важный момент, который нужно IndexOutOfRangeExceptionсравнить со многими другими типами исключений, а именно:

Часто существует сложное состояние программы, которое может быть трудно контролировать в определенной точке кода, например, соединение с БД прерывается, поэтому данные для входа не могут быть извлечены и т. Д. ... Этот тип проблемы часто приводит к исключению некоторого вида, которое должен подняться на более высокий уровень, потому что там, где это происходит, нет никакого способа справиться с этим на этом этапе.

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

Другой способ выразить это заключается в том, что другие исключения могут возникать из-за подлинного отсутствия контроля над вводом или состоянием программы, НО IndexOutOfRangeExceptionчаще всего это просто ошибка пилота (программиста).

Ricibob
источник