¿Qué es una excepción IndexOutOfRangeException / ArgumentOutOfRangeException y cómo la arreglo?

Tengo un código y cuando se ejecuta, arroja una IndexOutOfRangeException , diciendo:

El índice esta fuera de los límites de la matriz.

¿Qué significa esto y qué puedo hacer al respecto?

Dependiendo de las clases utilizadas, también puede ser ArgumentOutOfRangeException

Se produjo una excepción del tipo ‘System.ArgumentOutOfRangeException’ en mscorlib.dll pero no se manejó en el código de usuario. Información adicional: Index estaba fuera de rango. Debe ser no negativo y menor que el tamaño de la colección.

¿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 la cantidad 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 provocarán que se IndexOutOfRangeException . Recuerde esto cuando crea y accede a una matriz.

Longitud de la matriz
En C #, por lo general, las matrices están basadas en 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 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 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; } } 

Upper Bound no es inclusivo
En el siguiente ejemplo, creamos una matriz tridimensional sin procesar 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 (en la parte inferior derecha) de la imagen es pixels[imageWidth - 1, imageHeight - 1] :

 pixels[imageWidth, imageHeight] = Color.Black; 

En otro escenario, puede obtener ArgumentOutOfRangeException para este código (por ejemplo, si está usando el método GetPixel en una clase Bitmap ).

Las matrices no crecen
Una matriz es rápida. Muy rápido en la búsqueda lineal en comparación con cualquier otra colección. 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! Usted paga esto con una limitación: no pueden crecer, si necesita más elementos necesita reasignar esa matriz (esto puede ser expansivo si los elementos viejos deben copiarse en un nuevo bloque). Los Array.Resize() tamaño con Array.Resize() , 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 intenta asignar un elemento en Length obtendrá IndexOutOfRangeException (este comportamiento puede confundirlo si cree que puede boost con una syntax similar al método Insert de otras colecciones).

Arrays especiales con límite inferior personalizado
El primer elemento en matrices siempre tiene el í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 usando argumentos no validados (de la entrada del usuario o del usuario de la función) puede recibir 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 (las variables se han introducido con .NET 2.0 y, de todos modos, también es una convención conocida en uso desde hace muchos años) si no encuentran nada . Imaginemos que tiene una matriz de objetos comparable 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 no hay elementos en myArray que cumplan con la condición de búsqueda porque Array.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 dado de números (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 representa el número 1 y así sucesivamente):

 static int[] CountOccurences(int maximum, IEnumerable 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á en números negativos y números superiores al maximum .

¿Cómo se aplica a List ?

Los mismos casos que array - rango de índices válidos - 0 (los índices de la lista siempre comienzan con 0) a list.Count - accediendo a elementos fuera de este rango causará la excepción.

Tenga en cuenta que List arroja ArgumentOutOfRangeException para los mismos casos en que los arrays usan IndexOutOfRangeException .

A diferencia de las matrices, List comienza en blanco, por lo que tratar de acceder a los elementos de la lista recién creada conduce a esta excepción.

 var list = new List(); 

El caso común es llenar lista con indexación (similar a Dictionary ) causará 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() lanzará IndexOutOfRangeException porque su conjunto de datos tiene solo dos columnas, pero está tratando de obtener un valor del tercero (los índices siempre están basados ​​en 0).

Tenga en cuenta que este comportamiento se comparte con la mayoría de IDataReader implementaciones de IDataReader ( SqlDataReader , OleDbDataReader , etc.).

También puede obtener la misma excepción si utiliza la sobrecarga IDataReader del operador del indexador que toma el nombre de una columna y pasa un nombre de columna no válido.
Supongamos, por ejemplo, que ha recuperado una columna llamada Column1 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 campo de Colum1 que no existe. El método GetOrdinal lanzará esta excepción cuando su código auxiliar interno devuelva un -1 como el í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 suministra a la propiedad DataViewSort no es válido.

Como evitar

En este ejemplo, déjenme suponer, por simplicidad, que las matrices son siempre monodimensionales y basadas en 0. Si quieres ser estricto (o estás desarrollando una biblioteca), es posible que necesites reemplazar 0 con GetLowerBound(0) y .Length con GetUpperBound(0) (por supuesto, si tienes parámetros de tipo System.Arra y, doesn) no se aplica para T[] ). Tenga en cuenta que en este caso el límite superior incluye este código:

 for (int i=0; i < array.Length; ++i) { } 

Debe ser reescrito 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 son T[] está seguro sobre arreglos de límite inferior personalizado:

 void foo(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 una ArgumentException o ArgumentOutOfRangeException apropiadas). En el ejemplo siguiente, los parámetros incorrectos pueden causar IndexOutOfRangeException , los usuarios de esta función pueden esperar esto porque están pasando una matriz, pero no siempre es tan obvio. Sugeriría siempre validar los parámetros para las funciones públicas:

 static void SetRange(T[] array, int from, int length, Func 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 if lógica con Debug.Assert() :

 Debug.Assert(from >= 0 && from < array.Length); 

Verificar el estado del objeto
El índice de matriz no puede 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 (por sí mismo y con los parámetros de la función, si es necesario). Puede usar Debug.Assert() , arrojar 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 valor de retorno Array.IndexOf() . 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) fácilmente podría requerir mucho más tiempo que el necesario para depurar su código. En primer lugar, lea esta publicación del blog de Eric Lippert sobre la depuración de pequeños progtwigs , no voy a repetir sus palabras aquí, pero es absolutamente imprescindible leer .

Tiene un código fuente, tiene un mensaje de excepción con el seguimiento de la stack. Ve allí, elige el número de la línea derecha y verás:

 array[index] = newValue; 

Encontraste tu error, mira cómo aumenta el index . ¿Es correcto? Comprobar cómo se asigna la matriz, ¿es coherente con la forma en index aumenta el index ? ¿Está bien según tu especificación? Si responde afirmativamente a todas estas preguntas, encontrará una 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 las entradas. Puede que quiera usar contratos de código. Cuando algo salió mal y usted no puede entender lo que sucede con un rápido vistazo a su código, entonces tiene que recurrir a un viejo amigo: depurador . Simplemente ejecute su aplicación en la 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ás tú mismo en pocos 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).

Explicación simple sobre qué es un índice fuera de la excepción encuadernada:

Solo piense que un tren está allí, sus compartimentos son D1, D2, D3. Un pasajero vino a entrar al tren y tiene el boleto para D4. ahora que va a pasar el pasajero quiere ingresar a un compartimento que no existe, por lo que obviamente surgirá un problema.

Mismo escenario: cada vez que intentamos acceder a una lista de arreglos, etc., solo podemos acceder a los índices existentes en la matriz. array[0] y array[1] son existentes. Si intentamos acceder a la array[3] , en realidad no está allí, por lo que surgirá una excepción de índice fuera de límite.

Para comprender fácilmente el problema, imagine que escribimos este código:

 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()); } } 

El resultado será:

 hello1 hello2 hello3 Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array. 

El tamaño de la matriz es 3 (índices 0, 1 y 2), pero cuando intenta acceder fuera de los límites con (3) arroja la excepción.