Что это?
Это исключение означает, что вы пытаетесь получить доступ к элементу коллекции по индексу, используя недопустимый индекс. Индекс недопустим, если он ниже нижней границы коллекции или больше или равен количеству элементов, которые он содержит.
Когда это брошено
Данный массив объявлен как:
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.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, об этой ошибке можно просто избежать. Время, которое вы тратите на написание правильного вопроса (с небольшим рабочим примером и небольшим объяснением), может легко превысить время, необходимое для отладки кода. Прежде всего, прочитайте этот пост Эрика Липперта в блоге об отладке небольших программ , я не буду повторять его слова здесь, но это абсолютно необходимо прочитать .
У вас есть исходный код, у вас есть сообщение об исключении с трассировкой стека. Идите туда, выберите правильный номер строки, и вы увидите:
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
Это исключение означает, что вы пытаетесь получить доступ к элементу коллекции по индексу, используя недопустимый индекс. Индекс недопустим, если он ниже нижней границы коллекции или большеравно количеству элементов, которые оно содержит. максимально допустимый индекс, определенный в объявлении массива