Dada a seguinte estrutura:
public struct S : IDisposable { public int Value { get; private set; } public S(int value) : this() { this.Value = value; } public void SetValue(int value) { this.Value = value; } public void Dispose() { Console.WriteLine( "Disposing: {0}", this.Value); this.Value = 0; } }
Qual é o resultado da execução do seguinte código?
using (var s1 = new S(1)) { s1.SetValue(-1); } Console.WriteLine(); var s2 = new S(2); using (s2) { s2.SetValue(-1); } Console.WriteLine("Disposed: {0}", s2.Value); Console.WriteLine(); var s3 = new S(3); try { s3.SetValue(-1); } finally { s3.Dispose(); } Console.WriteLine("Disposed: {0}", s3.Value); Console.WriteLine(); var s4 = new S(4); try { s4.SetValue(-1); } finally { ((IDisposable)s4).Dispose(); } Console.WriteLine("Disposed: {0}", s4.Value); Console.WriteLine();
Resultado
O resultado da execução é:
Disposing: -1 Disposing: 2 Disposed: -1 Disposing: -1 Disposed: 0 Disposing: -1 Disposed: -1
Explicação
Para entender o que se está aqui a passar é necessário ter em mente que as estruturas (struct
) são tipos valor, o que quer dizer que quando se copia uma estrutura não se está apenas a copiar o seu endereço no heap, mas está-se a copiar toda a estrutura.
No primeiro caso, o compilador está a lidar apenas com a variável s1
. No entanto, dado o escopo da variável não é possível validar que o valor final de s1.Value
é 0
.
No segundo caso, como se está a usar na instrução using
um valor previamente criado, o compilador usa uma cópia desse valor para no final invocar o método Dispose
. A razão para a utilização desta cópia é para evitar que o valor do escopo da instrução using
seja alterado. E como se trata de um tipo valor, é feita uma cópia integral do objeto o que faz com que o objeto contido na variável s2
não seja aquele a que foi invocado o método Dispose
mas sim a cópia feita anteriormente. É por esta razão que o valor do campo Value
do objeto em que foi invocado o método Dispose
ainda é 2
e o valor do campo Value
do objeto contido na variável s2
ainda é -1
.
O terceiro caso é um caso simples em que não existe qualquer “truque”.
No quarto caso existe uma operação explícita de cast de um tipo valor para uma interface. Esta operação de cast é feita à custa de uma operação de boxing do objeto do tipo valor (que consiste em alocar memória no heap e copiar para lá o valor do objeto) e posterior acesso como se tratando do objeto de um tipo referência. É por existir esta cópia no troço finally
que o objeto em que está a ser invocado o método Dispose
não é o mesmo contido na variável s4
mas uma cópia feita no momento da invocação do método Dispose
, pelo que, a variável s4
mantém o estado que tinha antes da invocação feita à sua cópia.
Conclusão
Os tipos valor, nomeadamente as estruturas (struct
), têm algumas vantagens (como por exemplo alocação no stack evitando o peso do GC (garbage collector)) mas quando se altera o seu estado interno pode ter consequências imprevisíveis.