¿Qué es?
Esta excepción significa que estás intentando (directa o indirectamente) acceder (para leer o escribir) a un Array usando un índice inválido. El índice es inválido cuando es menor que el límite inferior del Array o mayor o igual que el número de elementos que contiene.
Cuando es lanzado
Dado que Array se declaró como:
byte[] array = new byte[4];
Puedes acceder a este Array de 0 a 3, los valores fuera de este rango causarán IndexOutOfRangeException
para ser lanzada. Recuerden esto cuando creen y accedan a una matriz.
Longitud de la matriz
En C#, por lo general, los arreglos son de base 0. Significa que el primer elemento tiene el índice 0 y el último elemento tiene el índice Length - 1
(donde Length
es el número total de artículos en la matriz) así que este código no funciona:
array[array.Length] = 0;
Además, tened en cuenta que si tenéis una matriz multidimensional, no podéis usar Array.Length
para ambas dimensiones, tienes que usar 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;
}
}
El límite superior no es inclusivo
En el siguiente ejemplo creamos una matriz bidimensional en bruto 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á entonces porque el Array es de base 0 y el último píxel (abajo a la derecha) de la imagen es pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
En otro escenario se puede obtener ArgumentOutOfRangeException
para este código (por ejemplo si estás usando GetPixel
método en un Bitmap
clase).
Las matrices no crecen
Un Array es rápido. Muy rápida en la búsqueda lineal comparada con todas las demás colecciones. Es porque los elementos son contiguos en la memoria, por lo que la dirección de la memoria puede ser calculada (y el incremento es sólo una adición). No hay necesidad de seguir una lista de nodos, ¡simple matemática! Pagas esto con una limitación: no pueden crecer, si necesitas más elementos tienes que reasignar ese Array (esto puede ser expansivo si los elementos antiguos deben ser copiados a un nuevo bloque). Los redimensionas con Array.Resize<T>()
este ejemplo añade una nueva entrada a una matriz existente:
Array.Resize(ref array, array.Length + 1);
No olvides que los índices válidos son de 0
a Length - 1
. Si simplemente intentas asignar un elemento en Length
tendrás IndexOutOfRangeException
(este comportamiento puede confundirte si piensas que pueden aumentar con una sintaxis similar a Insert
método de otras colecciones).
Especial Arreglos con un límite inferior personalizado
El primer elemento de las matrices siempre tiene un índice de 0 . Esto no siempre es cierto, porque puedes 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 la matriz son válidos del 1 al 4. Por supuesto, el límite superior no puede ser cambiado.
Argumentos equivocados
Si se accede a un Array utilizando argumentos no validados (de la entrada de usuario o de la función de usuario) se 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 puede ser lanzada por otra razón también: por convención muchos funciones de búsqueda devolverá -1 (nulos ha sido introducido con .NET 2.0 y de todos modos es también una convención bien conocida en uso desde hace muchos años) si no encontraron nada. Imaginemos que tienes un conjunto de objetos comparables a una cadena. Se 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 no hay artículos en myArray
satisfará la condición de búsqueda porque Array.IndexOf()
regresará -1 y luego el acceso a la matriz se lanzará.
El siguiente ejemplo es un ejemplo ingenuo para calcular las ocurrencias de un determinado conjunto de números (conociendo el número máximo y devolviendo una matriz donde el artículo en el índice 0 representa el número 0, los artículos 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 que es una implementación bastante terrible, pero lo que quiero mostrar es que fallará para los números negativos y los números por encima de maximum
.
Cómo se aplica a List<T>
?
Los mismos casos que el Array - rango de índices válidos - 0 ( List
los índices de la empresa siempre empiezan con 0) a list.Count
- El acceso a elementos fuera de este rango causará la excepción.
A diferencia de las matrices, List<T>
comienza vacío - así que tratar de acceder a los elementos de la lista recién creada conduce a esta excepción.
var list = new List<int>();
El caso más común es poblar la lista con un índice (similar a Dictionary<int, T>
) causará una excepción:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader y Columnas
Imagina que estás tratando de 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()
lanzará IndexOutOfRangeException
porque su conjunto de datos sólo tiene dos columnas pero está tratando de obtener un valor del 3er. siempre 0-based).
Tenga en cuenta que este comportamiento es compartido con la mayoría IDataReader
implementaciones ( SqlDataReader
, OleDbDataReader
y así sucesivamente).
Otros
Hay otro caso (documentado) en el que se lanza esta excepción: si, en DataView
, el nombre de la columna de datos que se suministra a la DataViewSort
la propiedad no es válida.
Cómo evitar
En estos ejemplos, permítanme asumir, para simplificar, que las matrices son siempre monodimensionales y de base 0. Si quieres ser estricto (o estás desarrollando una biblioteca) puedes necesitar reemplazar 0
con GetLowerBound(0)
y .Length
con GetUpperBound(0)
(por supuesto si tiene parámetros de tipo System.Arra
y, no se aplica a T[]
). Por favor, 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 ser reescrito así:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
Por favor, tened en cuenta que esto no está permitido (se tirará InvalidCastException
), por eso si tus parámetros son T[]
estás a salvo de las matrices personalizadas de límite inferior:
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 los parámetros
Si los índices provienen de un parámetro, siempre debes validarlos (lanzando el apropiado ArgumentException
o ArgumentOutOfRangeException
). En el siguiente ejemplo los parámetros incorrectos pueden causar IndexOutOfRangeException
los usuarios de esta función pueden esperar esto porque están pasando por un Array, pero no siempre es tan obvio. Sugiero que siempre se validen los parámetros de 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, puede simplemente reemplazar if
la lógica con Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
Comprobar el estado del objeto
El índice de la matriz puede no provenir directamente de un parámetro. Puede ser parte del estado del objeto. En general, siempre es una buena práctica validar el estado de los objetos (por sí mismo y con parámetros de función, si es necesario). Se puede utilizar Debug.Assert()
lanzar una excepción adecuada (más descriptiva del 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 los valores de retorno
En uno de los ejemplos anteriores utilizamos directamente 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 ser simplemente evitadas. El tiempo que dedique a escribir una pregunta adecuada (con un pequeño ejemplo de trabajo y una pequeña explicación) podría fácilmente ser mucho más que el tiempo que necesitará para depurar su código. En primer lugar, lea esta entrada del blog de Eric Lippert sobre depuración de pequeños programas No voy a repetir sus palabras aquí, pero es absolutamente un debe leer .
Tienes el código fuente, tienes el mensaje de excepción con el rastro de la pila. Ve allí, escoge el número de línea correcto y verás:
array[index] = newValue;
Encontraste tu error, comprueba cómo index
aumenta. ¿Es correcto? Comprueba cómo se asigna el Array, es coherente con cómo index
aumentos? ¿Es correcto de acuerdo con su especificación? Si usted responde sí a todas estas preguntas entonces encontrarás buena ayuda aquí en StackOverflow pero por favor primero compruébalo tú mismo. ¡Ahorrarás tu propio tiempo!
Un buen punto de partida es usar siempre afirmaciones y validar las entradas. Puede que incluso quieras usar contratos de código. Cuando algo sale mal y no puedes entender lo que pasa con un vistazo rápido a tu código, entonces tienes que recurrir a un viejo amigo: depurador . Simplemente ejecuta tu aplicación en depuración dentro de Visual Studio (o tu IDE favorito), verás exactamente qué línea lanza esta excepción, qué Array está involucrado y qué índice estás tratando de usar. En realidad, el 99% de las veces lo resolverás tú mismo en pocos minutos.
Si esto ocurre en la producción, entonces es mejor añadir afirmaciones en código incriminado, probablemente no veremos en tu código lo que no puedes ver por ti mismo (pero siempre puedes apostar).