¿Qué es una IndexOutOfRangeException / ArgumentOutOfRangeException y cómo lo soluciono?

191

Tengo un código y cuando se ejecuta, arroja un 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: El índice estaba fuera de rango. Debe ser no negativo y menor que el tamaño de la colección.

Adriano Repetti
fuente
En su colección si solo tiene 4 elementos, pero el código intentó obtener un elemento en el índice 5. Esto arrojará IndexOutOfRangeException. Comprobar índice = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy

Respuestas:

232

¿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 IndexOutOfRangeExceptionque 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.Lengthpara 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 ArgumentOutOfRangeExceptioneste código (por ejemplo, si está utilizandoGetPixel método en una Bitmapclase).

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 0a 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 Insertmé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 myArraysatisface 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 ( Listlos í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 ArgumentOutOfRangeExceptionpara 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á IndexOutOfRangeExceptionporque 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 IDataReaderimplementaciones ( 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 DataViewSortpropiedad 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 0con GetLowerBound(0)y .Lengthcon GetUpperBound(0)(por supuesto, si tiene parámetros de tipo System.Array, 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 ArgumentExceptiono 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 ifló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 indexaumenta. ¿Es correcto? Compruebe cómo se asigna la matriz, ¿es coherente con cómo indexaumenta? ¿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

Adriano Repetti
fuente
19

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 entró al tren y tiene el boleto para D4. ahora que va a pasar. el pasajero desea ingresar a un compartimiento que no existe, por lo que obviamente surgirá un problema.

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

Lijo
fuente
10

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 los bucles for-loop 4 veces (0, 1, 2 y 3).
Entonces, cuando intenta acceder fuera de los límites con (3), arroja la excepción.

Snr
fuente
1

A un lado de la muy completa respuesta aceptada, hay un punto importante que hacer en IndexOutOfRangeExceptioncomparación con muchos otros tipos de excepciones, y es:

A menudo hay un estado de programa complejo que puede ser difícil de controlar en un punto particular del código, por ejemplo, una conexión de base de datos se cae, por lo que los datos de una entrada no se pueden recuperar, etc. Este tipo de problema a menudo resulta en una excepción de algún tipo tiene que burbujear a un nivel superior porque donde ocurre no tiene forma de lidiar con eso en ese punto.

IndexOutOfRangeExceptiones generalmente diferente en el sentido de que, en la mayoría de los casos, es bastante trivial verificarlo en el punto donde se genera la excepción. En general, este tipo de excepción es arrojado por algún código que podría tratar muy fácilmente el problema en el lugar donde está ocurriendo, simplemente verificando la longitud real de la matriz. No desea 'arreglar' esto manejando esta excepción más arriba, sino asegurándose de que no se lance en primera instancia, lo que en la mayoría de los casos es fácil de hacer al verificar la longitud de la matriz.

Otra forma de decir esto es que pueden surgir otras excepciones debido a una genuina falta de control sobre la entrada o el estado del programa, PERO la IndexOutOfRangeExceptionmayoría de las veces es simplemente un error piloto (programador).

Ricibob
fuente