Arquivo da tag: for

C# – System.InvalidOperationException ao varrer coleções

Collections no C#
Collections no C#

A situação

Uma rotina tradicional, principalmente para quem trabalham com coleções. E para quem não sabe, trabalhar com DataTables e DataSets não o livra de usá-las pois estes objetos não passam de coleções, sejam de DataColumns, DataRows ou mesmo DataTables.

Pois bem, você executa um código “normal”, por exemplo, para remover linhas de um DataTable “varrendo” sua coleção de linhas. O código seria algo parecido com isto:

1
2
3
4
5
6
7
8
9
10
public void RemoveProducts(int ID)
{
        DataTable productData = new DataTable();
        productData = GetProducts(ProductID, false, true);
        foreach ( dr in productData.Rows) {
                if ((dr("ID") != ID.Value)) {
                        productData.Rows.Remove(dr);
                }
        }
}

Sem muitos segredos, o método preenche um DataTable e através de um laço foreach, remove as linhas que obedeçam a um determinado critério.

O problema

Qualquer código que trate coleções desta forma será muito “bem” recebido com a seguinte mensagem:

System.InvalidOperationException: Collection was modified; enumeration operation might not execute.

A descrição da Exception é bem genérica, mas para este caso a explicação é que, uma vez que um laço como o foreach analisa uma coleção para promover o laço, ela cria um Enumerador na primeira execução para promover um MoveNext, que fará avançar para próximo item. Se a coleção for modificada, seja por adição ou remoção de itens, o Enumerador se tornará inválido e o aplicativo retornará a Exception acima.

Abaixo segue a explicação da Microsoft para o fato:

After an enumerator is created or after the Reset method is called, an enumerator is positioned before the first element of the collection, and the first call to the MoveNext method moves the enumerator over the first element of the collection.

If MoveNext passes the end of the collection, the enumerator is positioned after the last element in the collection and MoveNext returns false. When the enumerator is at this position, subsequent calls to MoveNext also return false until Reset is called.

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to MoveNext or Reset throws an InvalidOperationException

Portanto, nada de mexer na coleção durante a execução de um laço foreach OK.

A solução

Se for realmente necessário construir uma rotina que altere a coleção durante a execução do laço, será preciso recorrer ao laço for tracional ao while. De qualquer forma, mesmo no for tradicional, será necessário fazer o laço de forma reversa, já que você não sabe se o último item estará lá durante a execução do laço.

Por isso a melhor forma de contruir o código sem ter problemas é varrer a coleção de trás para frente. No caso do código apresentado acima, uma possível solução seria esta:

1
2
3
4
5
6
for (int i = productData.Rows.Count - 1; i <= 0; i += -1) {
        DataRow dr = productData.Rows(i);
        if ((dr("ID") != ID.Value)) {
            productData.Rows.RemoveAt(i);
        }
    }

Como dito, o laço é feito da última linha para a primeira, tomando o cuidado de sempre referenciar o índice com o valor -1.

Referências

http://msdn.microsoft.com/en-us/library/system.invalidoperationexception.aspx

http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.movenext.aspx

Bom proveito!