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

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

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

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

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

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

Ответ 1

Что это?

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

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

Учитывая массив, объявленный как:

byte[] array = new byte[4];

Вы можете получить доступ к этому массиву от 0 до 3, значения вне этого диапазона вызовут IndexOutOfRangeException. Помните об этом при создании и доступе к массиву.

Длина массива
В С#, как правило, массивы основаны на 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).

Массивы не растут
Массив быстро. Очень быстрый в линейном поиске по сравнению с любой другой коллекцией. Это связано с тем, что элементы смежны в памяти, поэтому адрес памяти можно вычислить (а приращение - просто дополнение). Не нужно следовать списку node, простой математике! Вы платите это с ограничением: они не могут расти, если вам нужно больше элементов, вам нужно перераспределить этот массив (это может быть большим, если старые элементы должны быть скопированы в новый блок). Вы изменяете их размер с помощью 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".

Другие
Существует другой (документированный) случай, когда это исключение выбрано: if, in 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, об этой ошибке можно просто избежать. Время, которое вы потратили, чтобы написать правильный вопрос (с небольшим рабочим примером и небольшим объяснением), может быть намного больше, чем время, которое вам нужно будет отладить ваш код. Прежде всего прочитайте это сообщение в блоге Eric Lippert о отладке небольших программ, я не буду повторять его слова здесь, но это абсолютно необходимо прочитать.

У вас есть исходный код, у вас есть сообщение об исключении со стеком. Идите туда, выберите номер правой линии, и вы увидите:

array[index] = newValue;

Вы нашли свою ошибку, проверьте, как index увеличивается. Это правильно? Проверьте, как распределяется массив, соответствует ли значение index? Правильно ли это в соответствии с вашей спецификацией? Если вы ответите "да" на все эти вопросы, вы найдете здесь полезную помощь в StackOverflow, но сначала проверьте это самостоятельно. Вы сэкономите свое время!

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

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

Ответ 2

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

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

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

Ответ 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), но при попытке доступа за пределы с помощью (3) он выдает исключение.