¿Qué es un IndexOutOfRangeException / ArgumentOutOfRangeException y cómo puedo solucionarlo?


Tengo algún código y cuando se ejecuta, lanza un IndexOutOfRangeException, diciendo,

Index estaba fuera de los límites del array.

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

Dependiendo de las clases utilizadas también puede ser ArgumentOutOfRangeException

Una excepción del sistema de tipo'.ArgumentOutOfRangeException ' ocurrió en mscorlib.dll pero no se manejó en el código de usuario Información adicional: El índice estaba fuera de rango. Debe ser no negativo y menor que el tamaño de la colección.

Author: Alexei Levenkov, 2014-01-06

3 answers

¿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 menor que el límite inferior de la colección o mayor o igual al número de elementos que contiene.

Cuando Se Lanza{[76]]}

Dado un array declarado como:

byte[] array = new byte[4];

Puede acceder a esta matriz de 0 a 3, los valores fuera de este rango causarán que IndexOutOfRangeException sea lanzado. Recuerde esto cuando cree y acceda matriz.

Longitud de la matriz
En C#, por lo general, los arrays están basados en 0. Significa que el primer elemento tiene índice 0 y el último elemento tiene í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;
    }
}

El Límite Superior No Es Inclusivo
En el siguiente ejemplo cree una matriz bidimensional 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 (inferior derecho) 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á utilizando el método GetPixel en una clase Bitmap).

Los Arrays No Crecen
Una matriz es rápida. Muy rápido en la búsqueda lineal en comparación con todos los demás colección. Se debe a que los elementos son contiguos en la memoria, por lo que la dirección de memoria se puede calcular (y el incremento es solo una adición). No hay necesidad de seguir una lista de nodos, matemáticas simples! 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 antiguos deben copiarse a un nuevo bloque). Si los redimensiona con Array.Resize<T>(), este ejemplo agrega una nueva entrada a un array 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 en Length obtendrás IndexOutOfRangeException (este comportamiento puede confundirte si crees que puede aumentar con una sintaxis similar al método Insert de otras colecciones).

Arreglos Especiales Con Límite Inferior Personalizado
El primer elemento de los arrays 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 erróneos
Si accede a una matriz utilizando argumentos no validados (desde la entrada del usuario o desde el 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 se puede lanzar por otra razón: por convención muchas funciones de búsqueda devolverán -1 (nullables se ha introducido 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 encontraron nada. Imaginemos que tienes una matriz de objetos comparable con una cadena. Usted 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 satisfagan la condición de búsqueda porque Array.IndexOf() devolverá -1 y luego lanzará el acceso al array.

El siguiente ejemplo es un ejemplo ingenuo para calcular las ocurrencias de un conjunto dado de números (conociendo el número máximo y devolviendo una matriz donde el elemento en el índice 0 representa el número 0, los elementos en el índice 1 representan 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 números negativos y números por encima de maximum.

Cómo se aplica a List<T>?

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

Tenga en cuenta que List<T> lanza ArgumentOutOfRangeException para los mismos casos en que los arrays usan IndexOutOfRangeException.

A diferencia de los arrays, 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>();

Caso común es llenar la lista con indexación (similar a Dictionary<int, T>) causará excepción:

list[0] = 42; // exception
list.Add(42); // correct

IDataReader y Columnas
Imagine que está 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() arrojará IndexOutOfRangeException porque su conjunto de datos tiene solo dos columnas, pero está tratando de obtener un valor del 3er. (los índices son siempre basados en 0).

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

También puede obtener la misma excepción si utiliza la sobrecarga de IDataReader del operador de 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 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 indexador está implementado tratando de recuperar el índice de un campo Colum1 que no existe. El método GetOrdinal lanzará esta excepción cuando su código interno de ayuda 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.

Cómo Evitar

En estos ejemplos permítanme asumir, por simplicidad, que los arrays son siempre monodimensionales y basados 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.Array, no se aplica para T[]). Tenga en cuenta que en este caso el límite superior es inclusivo entonces este código:

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

Debería reescribirse así: {[78]]}

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

Tenga en cuenta que esto no es allowed (it'll throw InvalidCastException), es por eso que si sus parámetros son T[] usted está seguro acerca de arreglos de límite inferior personalizados:

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 index proviene de un parámetro, siempre debe validarlo (lanzando ArgumentException o ArgumentOutOfRangeException apropiado). En el siguiente ejemplo 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. Yo sugeriría validar siempre los parámetros para el público funciones:

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 if lógica con Debug.Assert():

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

Comprobar el Estado del objeto
El índice del array puede no venir directamente de un parámetro. Puede ser parte del estado objeto. En general, siempre es una buena práctica validar el estado del objeto (por sí mismo 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 devueltos
En uno de los ejemplos anteriores usamos directamente Array.IndexOf() return value. 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 se pueden evitar simplemente. El tiempo que dediques 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ás para depurar tu código. Primero de todos leer este post de blog de Eric Lippert sobre depuración de pequeños programas, no voy a repetir sus palabras aquí, pero es absolutamente un debe leer.

Tiene código fuente, tiene mensaje de excepción con seguimiento de pila. Vaya allí, elija el número de línea derecha y verá:

array[index] = newValue;

Encontró su error, compruebe cómo aumenta index. ¿Es correcto? Compruebe cómo se asigna el array, es coherente con cómo aumenta index? ¿Es correcto según su especificación? Si responda a todas estas preguntas, entonces encontrará una buena ayuda aquí en StackOverflow, pero primero verifique eso usted mismo. ¡Ahorrarás tu propio tiempo!

Un buen punto de partida es utilizar siempre aserciones y validar entradas. Es posible que incluso desee utilizar contratos de código. Cuando algo salió mal y no puedes averiguar qué sucede con un vistazo rápido a tu código, entonces tienes que recurrir a un viejo amigo: debugger. Simplemente ejecute su aplicación en debug dentro de Visual Studio (o su IDE favorito), verá exactamente qué línea lanza 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, entonces es mejor agregar aserciones en código incriminado, probablemente no veremos en su código lo que no puede ver por sí mismo (pero siempre puede apostar).

 177
Author: Adriano Repetti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-15 16:57:13

Explicación simple sobre lo que es una excepción de Índice fuera de límite:

Solo piensa que un tren está allí. Sus compartimentos son D1, D2, D3. Un pasajero vino a entrar en el tren y tiene el billete para D4. ahora qué pasará. el pasajero quiere entrar en un compartimiento que no existe, por lo que obviamente surgirá un problema.

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

 15
Author: Lijo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-27 09:54:20

Para entender 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) lanza la excepción.

 3
Author: Snr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-12 15:36:10