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.Length
per 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 ArgumentOutOfRangeException
questo codice (ad esempio se si utilizza il GetPixel
metodo su una Bitmap
classe).
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 0
a 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 Insert
metodo 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 myArray
soddisfa 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 ( List
gli 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 ArgumentOutOfRangeException
per 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à IndexOutOfRangeException
perché 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 IDataReader
implementazioni ( 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 DataViewSort
proprietà 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 0
con GetLowerBound(0)
e .Length
con GetUpperBound(0)
(ovviamente se hai parametri di tipo System.Arra
y, 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 ArgumentException
o 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 if
logica 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 index
aumenta. È giusto? Verifica come viene allocato l'array, è coerente con gli index
aumenti? È giusto secondo le tue specifiche? Se rispondi sì 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