¿Qué es?
Esta excepción significa que está intentando acceder a un elemento de colección por índice, utilizando un índice no válido. Un índice no es válido cuando es inferior al límite inferior de la colección o mayor o igual que el número de elementos que contiene.
Cuando es lanzado
Dado un conjunto declarado como:
byte[] array = new byte[4];
Puede acceder a esta matriz de 0 a 3, los valores fuera de este rango harán IndexOutOfRangeException
que se arrojen. Recuerde esto cuando cree y acceda a una matriz.
Longitud de matriz
En C #, generalmente, las matrices están basadas en 0. Significa que el primer elemento tiene índice 0 y el último elemento tiene índice Length - 1
(dondeLength
es el número total de elementos en la matriz), por lo que este código no funciona:
array[array.Length] = 0;
Además, tenga en cuenta que si tiene una matriz multidimensional, entonces no puede usar Array.Length
para ambas dimensiones, debe usarArray.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;
}
}
Upper Bound no es inclusivo
En el siguiente ejemplo creamos una matriz bidimensional cruda de Color
. Cada elemento representa un píxel, los índices son de (0, 0)
a(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;
}
}
Este código fallará porque la matriz está basada en 0 y el último píxel (inferior derecho) de la imagen es pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
En otro escenario, puede obtener ArgumentOutOfRangeException
este código (por ejemplo, si está utilizandoGetPixel
método en una Bitmap
clase).
Las matrices no crecen
Una matriz es rápida. Muy rápido en búsqueda lineal en comparación con cualquier otra colección. Esto se debe a que los elementos son contiguos en la memoria, por lo que se puede calcular la dirección de la memoria (y el incremento es solo una adición). No es necesario seguir una lista de nodos, ¡simple matemática! Paga esto con una limitación: no pueden crecer, si necesita más elementos necesita reasignar esa matriz (esto puede llevar un tiempo relativamente largo si los elementos antiguos deben copiarse en un nuevo bloque). Los redimensiona con Array.Resize<T>()
, este ejemplo agrega una nueva entrada a una matriz existente:
Array.Resize(ref array, array.Length + 1);
No olvide que los índices válidos son de 0
a Length - 1
. Si simplemente intentas asignar un elemento a Length
, obtendrásIndexOutOfRangeException
(este comportamiento puede confundirlo si cree que puede aumentar con una sintaxis similar al Insert
método de otras colecciones).
Arreglos especiales con límite inferior personalizado El
primer elemento en los arreglos siempre tiene un índice 0 . Esto no siempre es cierto porque puede crear una matriz con un límite inferior personalizado:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
En ese ejemplo, los índices de matriz son válidos de 1 a 4. Por supuesto, el límite superior no se puede cambiar.
Argumentos incorrectos
Si accede a una matriz utilizando argumentos no validados (de la entrada del usuario o del usuario de la función), puede obtener este error:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
Resultados inesperados
Esta excepción también puede producirse por otra razón: por convención, muchas funciones de búsqueda devolverán -1 (se han introducido valores anulables con .NET 2.0 y, de todos modos, también es una convención bien conocida en uso desde hace muchos años) si no No encuentro nada. Imaginemos que tiene una matriz de objetos comparables con una cadena. Puede pensar en escribir este código:
// 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")]);
Esto fallará si ningún elemento en myArray
satisface la condición de búsqueda porqueArray.IndexOf()
devolverá -1 y luego se lanzará el acceso a la matriz.
El siguiente ejemplo es un ejemplo ingenuo para calcular las ocurrencias de un conjunto de números dado (conocer el número máximo y devolver una matriz donde el elemento en el índice 0 representa el número 0, los elementos en el índice 1 representan el número 1 y así sucesivamente):
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;
}
Por supuesto, es una implementación bastante terrible, pero lo que quiero mostrar es que fallará para los números negativos y los números anteriores maximum
.
Cómo se aplica a List<T>
?
Los mismos casos que la matriz - rango de índices válidos - 0 ( List
los índices siempre comienzan con 0) alist.Count
- acceder a elementos fuera de este rango causará la excepción.
Tenga en cuenta que List<T>
arroja ArgumentOutOfRangeException
para los mismos casos donde las matrices usanIndexOutOfRangeException
.
A diferencia de las matrices, List<T>
comienza vacío, por lo que intentar acceder a los elementos de la lista recién creada conduce a esta excepción.
var list = new List<int>();
El caso común es llenar la lista con indexación (similar a Dictionary<int, T>
) causará una excepción:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader y columnas
Imagine que está intentando leer datos de una base de datos con este código:
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()
arrojará IndexOutOfRangeException
porque su conjunto de datos tiene solo dos columnas, pero está tratando de obtener un valor de la tercera (los índices son siempre basados en 0).
Tenga en cuenta que este comportamiento se comparte con la mayoría de las IDataReader
implementaciones ( SqlDataReader
,OleDbDataReader
y así sucesivamente).
También puede obtener la misma excepción si usa la sobrecarga IDataReader del operador del indexador que toma un nombre de columna y pasa un nombre de columna no válido.
Supongamos, por ejemplo, que ha recuperado una columna llamada Columna1 pero luego intenta recuperar el valor de ese campo con
var data = dr["Colum1"]; // Missing the n in Column1.
Esto sucede porque el operador del indexador se implementa tratando de recuperar el índice de un Colum1 campo que no existe. El método GetOrdinal generará esta excepción cuando su código auxiliar interno devuelva un -1 como índice de "Colum1".
Otros
Hay otro caso (documentado) cuando se lanza esta excepción: si, en DataView
, el nombre de la columna de datos que se proporciona a la DataViewSort
propiedad no es válido.
Como evitar
En este ejemplo, permítanme asumir, por simplicidad, que las matrices son siempre monodimensionales y basadas en 0. Si desea ser estricto (o está desarrollando una biblioteca), es posible que deba reemplazar 0
con GetLowerBound(0)
y .Length
con GetUpperBound(0)
(por supuesto, si tiene parámetros de tipo System.Arra
y, no se aplica T[]
). Tenga en cuenta que en este caso, el límite superior incluye este código:
for (int i=0; i < array.Length; ++i) { }
Debería reescribirse así:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
Tenga en cuenta que esto no está permitido (arrojará InvalidCastException
), es por eso que si sus parámetros están T[]
seguros con respecto a las matrices de límite inferior personalizadas:
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 }));
}
Validar parámetros
Si el índice proviene de un parámetro, siempre debe validarlos (arrojando el apropiado ArgumentException
o ArgumentOutOfRangeException
). En el siguiente ejemplo, pueden causar parámetros incorrectos IndexOutOfRangeException
, los usuarios de esta función pueden esperar esto porque están pasando una matriz, pero no siempre es tan obvio. Sugeriría validar siempre los parámetros para las funciones públicas:
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);
}
Si la función es privada, simplemente puede reemplazar la if
lógica con Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
El
índice Check Object Array puede no venir directamente de un parámetro. Puede ser parte del estado del objeto. En general, siempre es una buena práctica validar el estado del objeto (solo y con parámetros de función, si es necesario). Puede usar Debug.Assert()
, lanzar una excepción adecuada (más descriptiva sobre el problema) o manejar eso como en este ejemplo:
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];
}
}
Validar valores de retorno
En uno de los ejemplos anteriores, utilizamos directamente el Array.IndexOf()
valor de retorno. Si sabemos que puede fallar, entonces es mejor manejar ese caso:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
Cómo depurar
En mi opinión, la mayoría de las preguntas, aquí en SO, sobre este error pueden simplemente evitarse. El tiempo que dedica a escribir una pregunta adecuada (con un pequeño ejemplo de trabajo y una pequeña explicación) podría ser mucho más que el tiempo que necesitará para depurar su código. En primer lugar, lea esta publicación de blog de Eric Lippert sobre la depuración de pequeños programas , no repetiré sus palabras aquí, pero es absolutamente una lectura obligada .
Tiene código fuente, tiene un mensaje de excepción con un seguimiento de pila. Vaya allí, elija el número de línea correcto y verá:
array[index] = newValue;
Encontraste tu error, comprueba cómo index
aumenta. ¿Es correcto? Compruebe cómo se asigna la matriz, ¿es coherente con cómo index
aumenta? ¿Es correcto de acuerdo con sus especificaciones? Si tu respondes si a todas estas preguntas, encontrará buena ayuda aquí en StackOverflow, pero primero verifíquelo usted mismo. ¡Ahorrarás tu propio tiempo!
Un buen punto de partida es usar siempre aserciones y validar entradas. Es posible que incluso desee utilizar contratos de código. Cuando algo salió mal y no puedes descubrir qué sucede con un vistazo rápido a tu código, entonces tienes que recurrir a un viejo amigo: depurador . Simplemente ejecute su aplicación en depuración dentro de Visual Studio (o su IDE favorito), verá exactamente qué línea arroja esta excepción, qué matriz está involucrada y qué índice está tratando de usar. Realmente, el 99% de las veces lo resolverá usted mismo en unos minutos.
Si esto sucede en producción, será mejor que agregue aserciones en el código incriminado, probablemente no veremos en su código lo que no puede ver usted mismo (pero siempre puede apostar).
El lado VB.NET de la historia
Todo lo que hemos dicho en la respuesta de C # es válido para VB.NET con las diferencias de sintaxis obvias, pero hay un punto importante a tener en cuenta al tratar con matrices de VB.NET.
En VB.NET, las matrices se declaran configurando el valor de índice válido máximo para la matriz. No es el recuento de los elementos que queremos almacenar en la matriz.
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
Entonces, este bucle llenará la matriz con 5 enteros sin causar ninguna excepción IndexOutOfRangeException
For i As Integer = 0 To 4
myArray(i) = i
Next
La regla de VB.NET
Esta excepción significa que está intentando acceder a un elemento de colección por índice, utilizando un índice no válido. Un índice no es válido cuando es menor que el límite inferior de la colección o mayor queigual al número de elementos que contiene. El índice máximo permitido definido en la declaración de matriz