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

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

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

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

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

исключение типа System.ArgumentOutOfRangeException ' произошло в mscorlib.DLL но не было обработано в коде пользователя дополнительные сведения: индекс находился вне допустимого диапазона. Должен быть неотрицательным и меньшим, чем размер коллекции.

3 ответов


Что Это?

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

Когда Он Брошен

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

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) to (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 to Length - 1. Если вы просто попытаетесь назначить элемент в Length вы получаете IndexOutOfRangeException (это поведение может запутать вас, если вы думаете, что они могут увеличиться с синтаксисом, подобным Insert метод других коллекций).

специальные Массивы С Пользовательским Нижним Связаны
первый элемент массива всегда имеет индекс 0. Это не всегда верно, потому что вы можете создать массив с пользовательским нижняя граница:

var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });

в этом примере индексы массива действительны от 1 до 4. Конечно, верхняя граница не может быть изменена.

Неправильные Аргументы
Если вы обращаетесь к массиву с использованием невалидированных аргументов (из пользовательского ввода или из функции user) , вы можете получить это ошибка:

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 потому что у вас dataset есть только два столбца, но вы пытаетесь получить значение из 3-го (индексы всегда 0-based).

обратите внимание, что это поведение разделяется с большинством IDataReader реализаций (SqlDataReader, OleDbDataReader и так далее).

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

 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.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 { }

отладка

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

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

array[index] = newValue;

вы нашел свою ошибку, проверьте, как index увеличивается. Правильно ли это? Проверьте, как выделяется массив, согласован с how index увеличивается? Разве это правильно по вашему specifiation? Если вы ответите да на все эти вопросы, то вы найдете хорошую помощь здесь на StackOverflow, но, пожалуйста, сначала проверьте это самостоятельно. Вы сэкономите свое время!

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

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


простое объяснение о том, что индекс вне границ исключение:

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