Armadilhas do C# – Atribuições, população de listas e referências
Uma vez precisei corrigir uma aplicação (lembra daquela que comentei no último Momento POG quando abordei o GOTO?) que necessitava enviar mensagens para o webservice, e quando mais de uma mensagem era mandada TODAS as mensagens eram enviadas apenas com a última buscada.
Vou explicar melhor:
De acordo com um assunto, era populada uma lista a partir de um banco de dados, porém no final do código todos os itens desta lista ficavam iguais ao último item que era buscado da base.
Você consegue notar a diferença entre os códigos abaixo, depois do break?
Código I:
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4:
5: namespace ConsoleApplication1
6: {
7: public class Teste
8: {
9: public string algumacoisa = "";
10: }
11:
12: class Program
13: {
14: static void Main(string[] args)
15: {
16: string[] lista = new string[3] { "Primeiro", "Segundo", "Terceiro" };
17: List<Teste> l = new List<Teste>();
18: Teste x = new Teste();
19: foreach (string s in lista)
20: {
21: x.algumacoisa = s;
22: l.Add(x);
23: }
24: foreach (Teste saida in l)
25: {
26: Console.WriteLine(saida.algumacoisa);
27: }
28: Console.ReadKey();
29: }
30: }
31: }
Código II:
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4:
5: namespace ConsoleApplication1
6: {
7: public class Teste
8: {
9: public string algumacoisa = "";
10: }
11:
12: class Program
13: {
14: static void Main(string[] args)
15: {
16: string[] lista = new string[3] { "Primeiro", "Segundo", "Terceiro" };
17: List<Teste> l = new List<Teste>();
18: foreach (string s in lista)
19: {
20: Teste x = new Teste();
21: x.algumacoisa = s;
22: l.Add(x);
23: }
24: foreach (Teste saida in l)
25: {
26: Console.WriteLine(saida.algumacoisa);
27: }
28: Console.ReadKey();
29: }
30: }
31: }
Exceto pela variável “x” (um objeto da classe Teste) que é declarada fora do laço foreach no Código I, enquanto que no Código II ela é declarada dentro do laço, não há nenhuma outra diferença entre eles.
Mas esta sutil diferença de código modificou COMPLETAMENTE o resultado do programa. Vejam as capturas de tela com o resultado da execução dos dois programas:
Código I:
Quando estamos trabalhando com um objeto, no nosso caso a variável “x”, e utilizamos o comando new() estaremos criando uma referência do mesmo na memória. Quando, em um List ou em qualquer outra coleção adicionamos este objeto dentro de seus itens, na verdade estaremos adicionando esta referência de memória do nosso objeto.
No Código I, ao fazer a iteração do array lista, atribuímos seus valores dentro da instância de Teste em x na propriedade “algumacoisa” e em seguida adicionamos este objeto no List<> l.
Como fazemos três iterações, adicionamos três objetos dentro do List<> não é? ERRADO.
Na verdade, adicionamos a referência à variável x três vezes, e como ela foi inicializada apenas uma vez com o comando new adicionamos a mesma referência três vezes.
Então, ao modificar o valor desta referência, estaremos modificando em todos os lugares onde ela é mencionada, no nosso caso, em cada item do List<> l.
Já no Código II, ao criar uma nova instância da classe Teste em x a cada iteração, estaremos adicionando ao nosso List<> três referências diferentes, sendo que ao final de cada iteração a referência criada existirá apenas dentro do List<>. E como x tem escopo apenas dentro do laço foreach, não poderemos mais utilizá-la, sendo que para acessar cada referência criada a partir de agora, só a partir do List<>.
Como você viu, um detalhezinho sutil e que fez uma diferença tremenda…
Um abraço!
2 comentários:
As armadilhas da POO. :)
@Lucho
Pensando de uma forma mais ampla, é isso mesmo :-)
Postar um comentário