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

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

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

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

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

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

5 ответов

Решение

Что это?

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

Когда это брошено

Данный массив объявлен как:

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 и столбцы
Представьте, что вы пытаетесь прочитать данные из базы данных с помощью этого кода:

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.Arraу, это не относится к 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 увеличивается? Правильно ли это по вашей спецификации? Если вы ответите утвердительно на все эти вопросы, вы найдете здесь полезную помощь в Stackru, но сначала проверьте это самостоятельно. Вы сэкономите свое время!

Хорошая отправная точка - всегда использовать утверждения и проверять вводимые данные. Вы можете даже хотеть использовать кодовые контракты. Если что-то пошло не так, и вы не можете понять, что происходит, быстро взглянув на свой код, вам придется прибегнуть к помощи старого друга: отладчика. Просто запустите ваше приложение в режиме отладки внутри Visual Studio (или вашей любимой IDE), и вы увидите, какая именно строка выдает это исключение, какой массив задействован и какой индекс вы пытаетесь использовать. Действительно, 99% раз вы решите это самостоятельно за несколько минут.

Если это произойдет на производстве, вам лучше добавить утверждения в инкриминируемый код, возможно, мы не увидим в вашем коде то, что вы не видите сами (но вы всегда можете поспорить).

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

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

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

Чтобы легко понять проблему, представьте, что мы написали этот код:

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), он генерирует исключение.

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

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

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

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

Эти два исключения распространены в различных языках программирования, и, как говорили другие, это когда вы получаете доступ к элементу с индексом, превышающим размер массива. Например:

      var array = [1,2,3];
/* var lastElement = array[3] this will throw an exception, because indices 
start from zero, length of the array is 3, but its last index is 2. */ 

Основная причина этого в том, что компиляторы обычно не проверяют этот материал, поэтому они будут выражать себя только во время выполнения.

Другие вопросы по тегам