Che cos'è IndexOutOfRangeException / ArgumentOutOfRangeException e come si corregge?


191

Ho un po 'di codice e quando viene eseguito, genera un IndexOutOfRangeException, dicendo:

L'indice era al di fuori dei limiti della matrice.

Cosa significa questo e cosa posso fare al riguardo?

A seconda delle classi utilizzate può anche essere ArgumentOutOfRangeException

Si è verificata un'eccezione di tipo "System.ArgumentOutOfRangeException" in mscorlib.dll ma non è stata gestita nel codice utente Ulteriori informazioni: L'indice non rientra nell'intervallo. Deve essere non negativo e inferiore alla dimensione della raccolta.


Nella tua raccolta se hai solo 4 articoli, ma il codice ha cercato di ottenere un elemento nell'indice 5. Ciò genererà IndexOutOfRangeException. Verifica indice = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy,

Risposte:


232

Che cos'è?

Questa eccezione indica che stai tentando di accedere a un elemento di raccolta per indice, utilizzando un indice non valido. Un indice non è valido quando è inferiore al limite inferiore della raccolta o maggiore o uguale al numero di elementi che contiene.

Quando viene lanciato

Dato un array dichiarato come:

byte[] array = new byte[4];

È possibile accedere a questo array da 0 a 3, causeranno valori al di fuori di questo intervallo IndexOutOfRangeException generati. Ricordalo quando crei e accedi a un array.

Lunghezza della matrice
In C #, di solito, le matrici sono basate su 0. Significa che il primo elemento ha indice 0 e l'ultimo elemento ha indice Length - 1(dove Lengthè il numero totale di elementi nella matrice), quindi questo codice non funziona:

array[array.Length] = 0;

Inoltre, si noti che se si dispone di un array multidimensionale, non è possibile utilizzare Array.Lengthper entrambe le dimensioni, è necessario utilizzare 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;
    }
}

Il limite superiore non è inclusivo
Nel seguente esempio creiamo una matrice bidimensionale grezza di Color. Ogni elemento rappresenta un pixel, gli indici sono da (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;
    }
}

Questo codice avrà esito negativo poiché l'array è basato su 0 e l'ultimo pixel (in basso a destra) nell'immagine è pixels[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

In un altro scenario è possibile ottenere ArgumentOutOfRangeExceptionquesto codice (ad esempio se si utilizza il GetPixelmetodo su una Bitmapclasse).

Gli array non crescono
Un array è veloce. Molto veloce nella ricerca lineare rispetto ad ogni altra collezione. È perché gli elementi sono contigui nella memoria, quindi è possibile calcolare l'indirizzo di memoria (e l'incremento è solo un'aggiunta). Non è necessario seguire un elenco di nodi, matematica semplice! Paghi questo con una limitazione: non possono crescere, se hai bisogno di più elementi devi riallocare quell'array (questo potrebbe richiedere un tempo relativamente lungo se gli oggetti vecchi devono essere copiati in un nuovo blocco). Le ridimensionate con Array.Resize<T>(), questo esempio aggiunge una nuova voce a un array esistente:

Array.Resize(ref array, array.Length + 1);

Non dimenticare che gli indici validi provengono da 0a Length - 1. Se provi semplicemente ad assegnare un elemento Length, otterrai IndexOutOfRangeException(questo comportamento potrebbe confonderti se pensi che possano aumentare con una sintassi simile al Insertmetodo di altre raccolte).

Array speciali con limite inferiore personalizzato Il
primo elemento negli array ha sempre l'indice 0 . Questo non è sempre vero perché puoi creare un array con un limite inferiore personalizzato:

var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });

In questo esempio, gli indici di array sono validi da 1 a 4. Naturalmente, il limite superiore non può essere modificato.

Argomenti errati
Se si accede a un array utilizzando argomenti non convalidati (dall'input dell'utente o dall'utente della funzione) è possibile che venga visualizzato questo errore:

private static string[] RomanNumbers =
    new string[] { "I", "II", "III", "IV", "V" };

public static string Romanize(int number)
{
    return RomanNumbers[number];
}

Risultati imprevisti
Questa eccezione può essere generata anche per un altro motivo: per convenzione, molte funzioni di ricerca restituiranno -1 (nullables è stato introdotto con .NET 2.0 e comunque è anche una convenzione ben nota in uso da molti anni) se non lo facessero non trovo niente. Immaginiamo di avere una matrice di oggetti comparabili con una stringa. Potresti pensare di scrivere questo codice:

// 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")]);

Ciò non riuscirà se nessun elemento in myArraysoddisfa la condizione di ricerca perché Array.IndexOf()restituirà -1 e quindi verrà lanciato l'accesso all'array.

Il prossimo esempio è un esempio ingenuo per calcolare le occorrenze di un determinato set di numeri (conoscendo il numero massimo e restituendo una matrice in cui l'elemento all'indice 0 rappresenta il numero 0, gli elementi all'indice 1 rappresentano il numero 1 e così via):

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

Certo, è un'implementazione piuttosto terribile, ma quello che voglio mostrare è che fallirà per i numeri negativi e sopra maximum.

Come si applica a List<T>?

Stessi casi dell'array - range di indici validi - 0 ( Listgli indici di sempre iniziano con 0) a list.Count- l'accesso a elementi al di fuori di questo intervallo provocherà l'eccezione.

Si noti che List<T>genera ArgumentOutOfRangeExceptionper gli stessi casi in cui vengono utilizzate le matrici IndexOutOfRangeException.

A differenza degli array, List<T>inizia vuoto - quindi il tentativo di accedere agli elementi dell'elenco appena creato porta a questa eccezione.

var list = new List<int>();

Il caso comune è popolare l'elenco con l'indicizzazione (simile a Dictionary<int, T>) causerà un'eccezione:

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

IDataReader e colonne
Immagina di provare a leggere i dati da un database con questo codice:

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()genererà IndexOutOfRangeExceptionperché il tuo set di dati ha solo due colonne ma stai cercando di ottenere un valore dal terzo (gli indici sono sempre basati su 0).

Tieni presente che questo comportamento è condiviso con la maggior parte delle IDataReaderimplementazioni ( SqlDataReader,OleDbDataReader e così via).

È possibile ottenere la stessa eccezione anche se si utilizza il sovraccarico IDataReader dell'operatore dell'indicizzatore che accetta un nome di colonna e passa un nome di colonna non valido.
Supponiamo, ad esempio, di aver recuperato una colonna denominata Column1 ma di provare a recuperare il valore di quel campo con

 var data = dr["Colum1"];  // Missing the n in Column1.

Ciò accade perché l'operatore dell'indicizzatore è implementato nel tentativo di recuperare l'indice di un Colum1 campo che non esiste. Il metodo GetOrdinal genererà questa eccezione quando il suo codice di supporto interno restituisce un -1 come indice di "Colum1".

Altro
Esiste un altro caso (documentato) quando viene generata questa eccezione: se, in DataView, il nome della colonna di dati fornito alla DataViewSortproprietà non è valido.

Come evitare

In questo esempio, supponiamo, per semplicità, che gli array siano sempre monodimensionali e basati su 0. Se vuoi essere severo (o stai sviluppando una libreria), potrebbe essere necessario sostituirlo 0con GetLowerBound(0)e .Lengthcon GetUpperBound(0)(ovviamente se hai parametri di tipo System.Array, non è applicabile T[]). Si noti che in questo caso il limite superiore è comprensivo di questo codice:

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

Dovrebbe essere riscritto in questo modo:

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

Si noti che questo non è consentito (verrà generato InvalidCastException), ecco perché se i parametri sono T[]sicuri per gli array con limite inferiore personalizzati:

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

Convalida parametri
Se l'indice proviene da un parametro, è necessario convalidarli sempre (lancio appropriato ArgumentExceptiono ArgumentOutOfRangeException). Nel prossimo esempio, possono verificarsi parametri errati IndexOutOfRangeException, gli utenti di questa funzione possono aspettarselo perché stanno passando un array ma non è sempre così ovvio. Suggerirei di convalidare sempre i parametri per le funzioni pubbliche:

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

Se la funzione è privata, puoi semplicemente sostituire la iflogica con Debug.Assert():

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

L'
indice Check State Array Array potrebbe non provenire direttamente da un parametro. Può far parte dello stato dell'oggetto. In generale è sempre una buona pratica convalidare lo stato dell'oggetto (da solo e con parametri di funzione, se necessario). È possibile utilizzare Debug.Assert(), generare un'eccezione appropriata (più descrittiva sul problema) o gestirla come in questo esempio:

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];
        }
}

Convalida dei valori di ritorno
In uno degli esempi precedenti abbiamo utilizzato direttamente il Array.IndexOf()valore di ritorno. Se sappiamo che potrebbe non riuscire, è meglio gestire quel caso:

int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }

Come eseguire il debug

A mio avviso, la maggior parte delle domande, qui su SO, su questo errore possono essere semplicemente evitate. Il tempo impiegato per scrivere una domanda corretta (con un piccolo esempio funzionante e una piccola spiegazione) potrebbe facilmente molto più del tempo necessario per eseguire il debug del codice. Prima di tutto, leggi questo post sul blog di Eric Lippert sul debug di piccoli programmi , non ripeterò qui le sue parole ma è assolutamente da leggere .

Hai il codice sorgente, hai un messaggio di eccezione con una traccia dello stack. Vai lì, scegli il numero di riga giusto e vedrai:

array[index] = newValue;

Hai trovato il tuo errore, controlla come indexaumenta. È giusto? Verifica come viene allocato l'array, è coerente con gli indexaumenti? È giusto secondo le tue specifiche? Se rispondi a tutte queste domande, troverai un buon aiuto qui su StackOverflow, ma per prima cosa controlla da solo. Risparmierai il tuo tempo!

Un buon punto di partenza è usare sempre le asserzioni e convalidare gli input. Potresti persino voler utilizzare i contratti di codice. Quando qualcosa è andato storto e non riesci a capire cosa succede con una rapida occhiata al tuo codice, devi ricorrere a un vecchio amico: debugger . Basta eseguire l'applicazione nel debug all'interno di Visual Studio (o il tuo IDE preferito), vedrai esattamente quale riga genera questa eccezione, quale array è coinvolto e quale indice stai cercando di usare. Davvero, il 99% delle volte lo risolverai da solo in pochi minuti.

Se ciò accade nella produzione, allora è meglio aggiungere asserzioni nel codice incriminato, probabilmente nel tuo codice non vedremo ciò che non puoi vedere da solo (ma puoi sempre scommettere).

Il lato VB.NET della storia

Tutto ciò che abbiamo detto nella risposta C # è valido per VB.NET con le ovvie differenze di sintassi, ma c'è un punto importante da considerare quando si tratta di array VB.NET.

In VB.NET, gli array vengono dichiarati impostando il valore indice massimo valido per l'array. Non è il conteggio degli elementi che vogliamo archiviare nell'array.

' declares an array with space for 5 integer 
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer

Quindi questo ciclo riempirà l'array con 5 numeri interi senza causare alcuna IndexOutOfRangeException

For i As Integer = 0 To 4
    myArray(i) = i
Next

La regola VB.NET

Questa eccezione indica che stai tentando di accedere a un elemento di raccolta per indice, utilizzando un indice non valido. Un indice non è valido quando è inferiore al limite inferiore della raccolta o maggiore diuguale al numero di elementi che contiene. l'indice massimo consentito definito nella dichiarazione dell'array


19

Spiegazione semplice su cosa sia un indice fuori limite associato:

Basti pensare che un treno è lì i suoi scomparti sono D1, D2, D3. Un passeggero è venuto a salire sul treno e ha il biglietto per D4. ora cosa accadrà. il passeggero desidera entrare in un compartimento che non esiste, quindi ovviamente sorgeranno problemi.

Stesso scenario: ogni volta che proviamo ad accedere a un elenco di array, ecc. Possiamo accedere solo agli indici esistenti nell'array. array[0]e array[1]esistono. Se proviamo ad accedere array[3], in realtà non è lì, quindi sorgerà un indice fuori eccezione legata.


10

Per capire facilmente il problema, immagina di aver scritto questo codice:

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

Il risultato sarà:

hello1
hello2
hello3

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

La dimensione dell'array è 3 (indici 0, 1 e 2), ma i loop for-loop 4 volte (0, 1, 2 e 3).
Quindi quando tenta di accedere al di fuori dei limiti con (3) genera l'eccezione.


1

A parte la lunghissima risposta completa accettata, c'è un punto importante da considerare IndexOutOfRangeExceptionrispetto a molti altri tipi di eccezione, e cioè:

Spesso c'è un complesso stato del programma che può essere difficile avere il controllo su un particolare punto del codice, ad esempio una connessione DB si interrompe, quindi i dati per un input non possono essere recuperati ecc ... Questo tipo di problema spesso si traduce in un'eccezione di un tipo che deve passare a un livello superiore perché dove si verifica non ha modo di affrontarlo a quel punto.

IndexOutOfRangeExceptionè generalmente diverso in quanto nella maggior parte dei casi è piuttosto banale verificare nel punto in cui viene sollevata l'eccezione. Generalmente questo tipo di eccezione viene generato da un codice che potrebbe facilmente risolvere il problema nel luogo in cui si sta verificando, semplicemente controllando la lunghezza effettiva dell'array. Non si vuole "risolvere" questo problema gestendo questa eccezione più in alto, ma assicurandosi invece che non venga generata in prima istanza, cosa che nella maggior parte dei casi è facile da verificare controllando la lunghezza dell'array.

Un altro modo per dirlo è che possono sorgere altre eccezioni a causa della reale mancanza di controllo sull'input o sullo stato del programma, ma il IndexOutOfRangeExceptionpiù delle volte è semplicemente un errore pilota (programmatore).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.