O que é um IndexOutOfRangeException / ArgumentOutOfRangeException e como corrigi-lo?

191

Eu tenho algum código e, quando executado, lança um IndexOutOfRangeException, dizendo:

O índice estava fora dos limites da matriz.

O que isso significa e o que posso fazer sobre isso?

Dependendo das classes utilizadas, também pode ser ArgumentOutOfRangeException

Ocorreu uma exceção do tipo 'System.ArgumentOutOfRangeException' no mscorlib.dll, mas não foi tratada no código do usuário Informações adicionais: O índice estava fora do intervalo. Deve ser não negativo e menor que o tamanho da coleção.

Adriano Repetti
fonte
Na sua coleção, se você tiver apenas 4 itens, mas o código tentou obter um item no índice 5. Isso lançará IndexOutOfRangeException. Índice de verificação = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy

Respostas:

232

O que é isso?

Essa exceção significa que você está tentando acessar um item de coleção por índice, usando um índice inválido. Um índice é inválido quando é menor que o limite inferior da coleção ou maior que ou igual ao número de elementos que ela contém.

Quando é lançado

Dada uma matriz declarada como:

byte[] array = new byte[4];

Você pode acessar esse array de 0 a 3, valores fora desse intervalo causarão o IndexOutOfRangeExceptionlançamento. Lembre-se disso ao criar e acessar uma matriz.

Comprimento da matriz
Em C #, geralmente, as matrizes são baseadas em 0. Isso significa que o primeiro elemento tem índice 0 e o último elemento tem índice Length - 1(onde Lengthé o número total de itens na matriz), portanto, esse código não funciona:

array[array.Length] = 0;

Além disso, observe que, se você tiver um array multidimensional, não poderá usar as Array.Lengthduas dimensões, precisará 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;
    }
}

O limite superior não é inclusivo
No exemplo a seguir, criamos uma matriz bidimensional bruta de Color. Cada item representa um pixel, os índices são de (0, 0)para (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;
    }
}

Esse código falhará porque a matriz é baseada em 0 e o último pixel (canto inferior direito) da imagem é pixels[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

Em outro cenário, você pode obter ArgumentOutOfRangeExceptionesse código (por exemplo, se você estiver usando o GetPixelmétodo em uma Bitmapclasse).

Matrizes não crescem
Uma matriz é rápida. Muito rápido na pesquisa linear em comparação com todas as outras coleções. Como os itens são contíguos na memória, o endereço da memória pode ser calculado (e o incremento é apenas uma adição). Não há necessidade de seguir uma lista de nós, matemática simples! Você paga isso com uma limitação: eles não podem crescer; se você precisar de mais elementos, precisará realocar essa matriz (isso pode levar um tempo relativamente longo se itens antigos precisarem ser copiados para um novo bloco). Você as redimensiona Array.Resize<T>(), este exemplo adiciona uma nova entrada a uma matriz existente:

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

Não esqueça que os índices válidos são de 0para Length - 1. Se você simplesmente tentar atribuir um item ao Lengthseu destino IndexOutOfRangeException(esse comportamento pode confundir você, se você acha que eles podem aumentar com uma sintaxe semelhante ao Insertmétodo de outras coleções).

Matrizes especiais com limite inferior personalizado O
primeiro item das matrizes sempre indexa 0 . Isso nem sempre é verdade porque você pode criar uma matriz com um limite inferior personalizado:

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

Nesse exemplo, os índices da matriz são válidos de 1 a 4. Obviamente, o limite superior não pode ser alterado.

Argumentos errados
Se você acessar uma matriz usando argumentos não validados (da entrada do usuário ou do usuário da função), poderá receber este erro:

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

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

Resultados inesperados
Essa exceção também pode ser lançada por outro motivo: por convenção, muitas funções de pesquisa retornarão -1 (nulos foram introduzidos no .NET 2.0 e, de qualquer forma, também é uma convenção conhecida em uso há muitos anos), se não ocorreram ' Não encontrei nada. Vamos imaginar que você tenha uma variedade de objetos comparáveis ​​a uma string. Você pode escrever esse 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")]);

Isso falhará se nenhum item dentro myArraysatisfizer a condição de pesquisa porque Array.IndexOf()retornará -1 e o acesso ao array será lançado.

O próximo exemplo é um exemplo ingênuo para calcular ocorrências de um determinado conjunto de números (saber o número máximo e retornar uma matriz em que o item no índice 0 representa o número 0, os itens no índice 1 representam o número 1 e assim por diante):

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

Claro, é uma implementação bastante terrível, mas o que eu quero mostrar é que ela falhará nos números negativos e acima maximum.

Como isso se aplica List<T>?

Os mesmos casos que o intervalo de matriz de índices válidos - 0 ( Listos índices de sempre começam com 0) list.Count- o acesso a elementos fora desse intervalo causará a exceção.

Observe que List<T>lança ArgumentOutOfRangeExceptionpara os mesmos casos em que as matrizes são usadas IndexOutOfRangeException.

Ao contrário de matrizes, List<T>começa vazio - portanto, tentar acessar itens da lista recém-criada leva a essa exceção.

var list = new List<int>();

O caso comum é preencher a lista com indexação (semelhante a Dictionary<int, T>) causará exceção:

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

IDataReader e colunas
Imagine que você está tentando ler dados de um banco de dados com 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()será lançado IndexOutOfRangeExceptionporque o conjunto de dados tem apenas duas colunas, mas você está tentando obter um valor da terceira (os índices sempre são baseados em 0).

Por favor note que este comportamento é compartilhada com a maioria das IDataReaderimplementações ( SqlDataReader, OleDbDataReadere assim por diante).

Você pode obter a mesma exceção também se usar a sobrecarga IDataReader do operador do indexador que pega um nome de coluna e passa um nome de coluna inválido.
Suponha, por exemplo, que você tenha recuperado uma coluna chamada Coluna1, mas tente recuperar o valor desse campo com

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

Isso acontece porque o operador do indexador é implementado tentando recuperar o índice de um campo Colum1 que não existe. O método GetOrdinal lançará essa exceção quando seu código auxiliar interno retornar -1 como o índice de "Colum1".

Outros
Há outro caso (documentado) em que essa exceção é lançada: se, em DataView, o nome da coluna de dados fornecido à DataViewSortpropriedade não for válido.

Como evitar

Neste exemplo, deixe-me supor, por simplicidade, que as matrizes são sempre monodimensionais e baseadas em 0. Se você quer ser rigoroso (ou está desenvolvendo uma biblioteca), pode ser necessário substituir 0por GetLowerBound(0)e .Lengthpor GetUpperBound(0)(é claro que se você tiver parâmetros do tipo System.Array, isso não se aplica T[]). Observe que, nesse caso, o limite superior é inclusivo e, em seguida, este código:

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

Deve ser reescrito assim:

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

Observe que isso não é permitido (será lançado InvalidCastException); é por isso que, se seus parâmetros estiverem T[]seguros, você poderá usar matrizes de limite 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
Se o índice der origem de um parâmetro, você sempre deve validá-los (jogando apropriado ArgumentExceptionou ArgumentOutOfRangeException). No próximo exemplo, parâmetros errados podem causar IndexOutOfRangeException, os usuários dessa função podem esperar isso porque estão passando uma matriz, mas nem sempre é tão óbvio. Eu sugeriria sempre validar parâmetros para funções 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);
}

Se a função é privada, você pode simplesmente substituir a iflógica por Debug.Assert():

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

O
índice Check Object State Array pode não vir diretamente de um parâmetro. Pode fazer parte do estado do objeto. Em geral, é sempre uma boa prática validar o estado do objeto (por si só e com parâmetros de função, se necessário). Você pode usar Debug.Assert(), lançar uma exceção adequada (mais descritiva sobre o problema) ou lidar com isso como neste exemplo:

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
Em um dos exemplos anteriores, usamos diretamente o Array.IndexOf()valor de retorno. Se sabemos que pode falhar, é melhor lidar com esse caso:

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

Como depurar

Na minha opinião, a maioria das perguntas, aqui no SO, sobre esse erro pode ser simplesmente evitada. O tempo que você gasta para escrever uma pergunta adequada (com um pequeno exemplo de trabalho e uma pequena explicação) pode facilmente muito mais do que o tempo necessário para depurar seu código. Antes de tudo, leia este post de Eric Lippert no blog sobre depuração de pequenos programas , não repetirei suas palavras aqui, mas é absolutamente uma leitura obrigatória .

Você tem código fonte, mensagem de exceção com um rastreamento de pilha. Vá para lá, escolha o número da linha direita e você verá:

array[index] = newValue;

Você encontrou seu erro, verifique como indexaumenta. Está certo? Verifique como a matriz é alocada, é coerente com a forma como indexaumenta? Está certo de acordo com suas especificações? Se você responder sim a todas essas perguntas, encontrará uma boa ajuda aqui no StackOverflow, mas verifique isso sozinho. Você economizará seu próprio tempo!

Um bom ponto de partida é sempre usar asserções e validar entradas. Você pode até querer usar contratos de código. Quando algo deu errado e você não consegue descobrir o que acontece com uma rápida olhada no seu código, é necessário recorrer a um velho amigo: o depurador . Basta executar seu aplicativo em depuração no Visual Studio (ou seu IDE favorito), você verá exatamente qual linha lança essa exceção, qual matriz está envolvida e qual índice você está tentando usar. Realmente, 99% das vezes você resolverá sozinho em alguns minutos.

Se isso acontecer na produção, é melhor adicionar asserções no código incriminado, provavelmente não veremos no seu código o que você não pode ver por si mesmo (mas você sempre pode apostar).

O lado VB.NET da história

Tudo o que dissemos na resposta C # é válido para o VB.NET com as óbvias diferenças de sintaxe, mas há um ponto importante a ser considerado quando você lida com matrizes do VB.NET.

No VB.NET, as matrizes são declaradas definindo o valor máximo de índice válido para a matriz. Não é a contagem dos elementos que queremos armazenar na 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

Portanto, esse loop preencherá a matriz com 5 números inteiros sem causar nenhum IndexOutOfRangeException

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

A regra do VB.NET

Essa exceção significa que você está tentando acessar um item de coleção por índice, usando um índice inválido. Um índice é inválido quando é menor que o limite inferior da coleção ou maior queigual ao número de elementos que ele contém. o índice máximo permitido definido na declaração da matriz

Adriano Repetti
fonte
19

Explicação simples sobre o que é uma exceção fora do limite do Index:

Apenas pense que um trem está lá, seus compartimentos são D1, D2, D3. Um passageiro entrou no trem e ele tem a passagem para o D4. agora o que vai acontecer. o passageiro deseja entrar em um compartimento que não existe, portanto, obviamente, surgirá um problema.

Mesmo cenário: sempre que tentamos acessar uma lista de matrizes, etc., podemos acessar apenas os índices existentes na matriz. array[0]e array[1]existem. Se tentarmos acessar array[3], ele não está lá, na verdade, portanto, um índice fora de exceção surgirá.

Lijo
fonte
10

Para entender facilmente o problema, imagine que escrevemos 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());
    }
}

O resultado será:

hello1
hello2
hello3

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

O tamanho da matriz é 3 (índices 0, 1 e 2), mas o loop for dá laçadas 4 vezes (0, 1, 2 e 3).
Portanto, quando tenta acessar fora dos limites com (3), lança a exceção.

Snr
fonte
1

Um lado da resposta aceita muito longa e completa, há um ponto importante a ser discutido em IndexOutOfRangeExceptioncomparação com muitos outros tipos de exceção, e é:

Freqüentemente, existe um estado complexo do programa que talvez seja difícil de controlar em um ponto específico do código, por exemplo, uma conexão com o banco de dados fica inativa para que os dados de uma entrada não possam ser recuperados, etc ... Esse tipo de problema geralmente resulta em uma exceção de algum tipo que precisa subir a um nível mais alto porque, onde ocorre, não há como lidar com isso nesse ponto.

IndexOutOfRangeExceptiongeralmente é diferente, pois na maioria dos casos é bastante trivial verificar no ponto em que a exceção está sendo gerada. Geralmente, esse tipo de exceção é acionado por algum código que poderia lidar muito facilmente com o problema no local em que está ocorrendo - apenas verificando o comprimento real da matriz. Você não deseja 'consertar' isso lidando com essa exceção mais alto - mas, ao invés disso, garantindo que ela não seja lançada na primeira instância - o que, na maioria dos casos, é fácil, verificando o comprimento da matriz.

Outra maneira de colocar isso é que outras exceções podem surgir devido à falta genuína de controle sobre o estado da entrada ou do programa, mas, na IndexOutOfRangeExceptionmaioria das vezes, é simplesmente um erro do piloto (programador).

Ricibob
fonte