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!

C# – Comparando Arrays

Não tem jeito. Apesar da imensa blibloteca que o .NET Framework possui, ainda não há (pelo menos até onde consegui pesquisar) um método nativo para comparação de Arrays ou Coleções.

Como esta foi uma necessidade que tive mais de uma vez em sistemas dos quais participei na construção, publicarei um método genérico para comparação de Arrays simples em C#.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>
/// Compara dois arrays de string
/// </summary>
/// <param name="arrayA">Array 1</param>
/// <param name="arrayB">Array 2</param>
/// <returns>True se os arrays forem iguais,
/// False do contrário</returns>
public static bool ArrayCompare(string[] arrayA, string[] arrayB)
{
	if (arrayA.Length != arrayB.Length)
	{
		return false;
	}
	for (int i = 0; i < arrayA.Length; i++)
	{
		if (arrayA[i] != arrayB[i])
		{
			return false;
		}
	}
	return true;
}

O código abaixo completo mostra a utilização da função:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
 
public class testCompareArrays
{
	public static void Main(string[] args)
	{
		string[] array1 = new string[] { "a", "b", "c", "d", "e" };
		string[] array2 = new string[] { "a", "b", "c", "d", "e" };
 
		if (ArrayCompare(array1, array2))
		{
			Console.WriteLine("Os arrays são iguais");
		}
		else
		{
			Console.WriteLine("Os arrays são diferentes");
		}
	}
 
	/// <summary>
	/// Compara dois arrays de string
	/// </summary>
	/// <param name="arrayA">Array 1</param>
	/// <param name="arrayB">Array 2</param>
	/// <returns>True se os arrays forem iguais,
	/// False do contrário</returns>
	public static bool ArrayCompare(string[] arrayA, string[] arrayB)
	{
		if (arrayA.Length != arrayB.Length)
		{
			return false;
		}
		for (int i = 0; i < arrayA.Length; i++)
		{
			if (arrayA[i] != arrayB[i])
			{
				return false;
			}
		}
		return true;
	}
}

O exemplos faz uso de um array de strings, porém é simples alterá-lo para qualquer outro tipo, desde que seja nativo ou que sobrecarregue o operado ==.

Bom proveito!

Tomás

Baboseiras – Matando a saudade com o nostalgiando.com

Bom, primeiro, o que é o nostalgiando: até onde sei é um site onde o pessoal resolveu criar uma espécie de jogo da memória, só que num esquema “sonoro”. Explico.

Você entra no site, abre o jogo e ele disponibiliza uma lista de figuras, que mais parecem robozinhos de Atari com botões de Play, Stop e uma caixa de textono seguinte esquema:

Robozinho tosco
Robozinho tosco

Ao clicar no Play, uma música é tocada e você tem que adivinhar ao que ela se refere. Caso você acerte, o robozinho fica na cor branca. Existem atualmente dois motivos:

  • Desenhos antigos
  • Games antigos

Tudo bem, isso é no mínimo idiota, pensaria a maioria, eu inclusive. Mas essa impressão passa no momento após a ouvir as três primeiras músicas. É uma volta no tempo total. Desenhos de infância que atormentavam as mamães, papais e irmãos mais velhos, mas que faziam nosso dia a dia.

Confesso que minha reação quase resultou em choro quando estava chegando o final de lista.

Para cital alguns desenhos, encontrei alguns como:

  • Cavaleiros do Zodíaco
  • A turma
  • Cavalo de Fogo
  • X-Men
  • O fantástico mundo de Bob

Dá para ter uma idéia de até onde a coisa chega. Não recomendo se você tiver um coração mole ou problemas cardíacos. 😉

A pouco tempo eles liberaram um versão com Games antigos, da época do NES, SNES e Mega Drive (Genesis). Aos gamemaníacos da época, bom divertimento.

Recomendações

  • Ter vivido a maior parte de infância entre os anos 80 e 90.
  • Chame os amigos da mesma época para ajudar. É diversão na certa.

Contra-Indicações

O uso de doses excessivas pode levar ao aparecimento ou sintomas de pura nostalgia.

<< Acessar o Jogo >>

Baboseiras – Wario abalando o YouTube

Dos camaradas do Sketchtrash

Wario Land Shake It
Wario Land Shake It

Quem não conhece o Wario?

Pelo menos com o ‘irmão mau’ do Mario não dá pra fazer piadinhas cretinas.

Este video linkado aqui abaixo está rodando a net inteira hoje, o video viral do novo jogo do anti-herói para Wii virou o hype do dia. Então não poderia ficar de fora aqui do SketchTrash. Confiram a campanha no YouTube clicando no link.

Wario Land: Shake It!