Eu gostaria de elaborar um ponto específico que Eric Lippert fez em sua resposta e colocar os holofotes em uma ocasião específica que nunca foi abordada por mais ninguém. Eric disse:
[...] uma tarefa quase sempre deixa para trás o valor que acabou de ser atribuído em um registro.
Eu gostaria de dizer que a atribuição sempre deixará para trás o valor que tentamos atribuir ao nosso operando esquerdo. Não apenas "quase sempre". Mas não sei porque não encontrei esse problema comentado na documentação. Teoricamente, pode ser um procedimento implementado muito eficaz para "deixar para trás" e não reavaliar o operando esquerdo, mas é eficiente?
'Eficiente' sim para todos os exemplos construídos até agora nas respostas deste tópico. Mas eficiente no caso de propriedades e indexadores que usam acessadores get e set? De modo nenhum. Considere este código:
class Test
{
public bool MyProperty { get { return true; } set { ; } }
}
Aqui temos uma propriedade que nem sequer é um invólucro para uma variável privada. Sempre que for chamado, ele retornará verdadeiro, sempre que alguém tentar definir seu valor, nada fará. Assim, sempre que essa propriedade for avaliada, ele deve ser verdadeiro. Vamos ver o que acontece:
Test test = new Test();
if ((test.MyProperty = false) == true)
Console.WriteLine("Please print this text.");
else
Console.WriteLine("Unexpected!!");
Adivinha o que imprime? Imprime Unexpected!!
. Como se vê, o acessador definido é realmente chamado, o que não faz nada. Mas, posteriormente, o acessador get nunca é chamado. A atribuição simplesmente deixa para trás o false
valor que tentamos atribuir à nossa propriedade. E esse false
valor é o que a instrução if avalia.
Terminarei com um exemplo do mundo real que me levou a pesquisar esse problema. Criei um indexador que era um invólucro conveniente para uma coleção ( List<string>
) que uma classe minha tinha como variável privada.
O parâmetro enviado ao indexador era uma sequência que deveria ser tratada como um valor em minha coleção. O acessador get simplesmente retornaria true ou false se esse valor existisse na lista ou não. Portanto, o acessador get era outra maneira de usar oList<T>.Contains
método.
Se o acessador de conjunto do indexador fosse chamado com uma string como argumento e o operando certo fosse um bool true
, ele adicionaria esse parâmetro à lista. Mas se o mesmo parâmetro fosse enviado ao acessador e o operando certo fosse um bool false
, ele excluiria o elemento da lista. Assim, o acessador definido foi usado como uma alternativa conveniente para ambos List<T>.Add
eList<T>.Remove
.
Eu pensei que tinha uma "API" limpa e compacta, envolvendo a lista com minha própria lógica implementada como gateway. Com a ajuda de um indexador sozinho, eu poderia fazer muitas coisas com algumas combinações de teclas. Por exemplo, como posso tentar adicionar um valor à minha lista e verificar se ela está lá? Eu pensei que esta era a única linha de código necessária:
if (myObject["stringValue"] = true)
; // Set operation succeeded..!
Mas, como meu exemplo anterior mostrou, o acessador get, que deveria ver se o valor realmente está na lista, nem foi chamado. O true
valor sempre foi deixado para trás, destruindo efetivamente qualquer lógica que eu havia implementado no meu acessador.