Value Types, Stack, Heap e confusões
Um dos blogs que leio e com certeza é o que considero mais importante é o do Eric Lippert, inclusive traduzi uma série de artigos sobre variância e postei aqui no blog. Ele faz parte do time que desenvolve o compilador do C# e no post mais recente ele voltou a falar sobre Value Types e a confusão que normalmente ocorre, inclusive em muitos livros. Achei importe falar sobre o assunto também para o público brasileiro.
Primeiramente vamos recapitular, o que são Value Types?
...
Não, não são objetos que ficam na Stack(Pilha)! Os dois principais motivos para essa afirmação estar errada são: nem sempre os Value Types ficam na Stack; e não é essa a semântica dos Value Types.
Value Types são copiados por valor e ponto. Essa é a definição, senão ele se chamaria Stack Types certo?
Mas por que tanta confusão? O fato é que até na documentação da Microsoft diz que Value Types são alocados na Stack, mas é apenas um detalhe de implementação, na runtime distribuída pela Microsoft. Apesar disso não podemos nos basear na implementação para fazer decisões, pois teoricamente numa futura e hipotética versão do .NET pode ser que tudo fique no Heap. Claro que não vai acontecer, mas devemos ter em mente o real significado. Isso não quer dizer que distribuições de outras empresas tenham que funcionar desta forma, dependendo inclusive do SO e do hardware.
Agora por que nem sempre eles ficam na Stack? O motivo é simples, eles só ficam na Stack quando podem. Ficar na Stack tem a vantagem de ser muito mais rápido para desalocar, bastando mover um ponteiro, ao contrário do que muita gente pensa, a alocação no Heap e Stack quase sempre levam o mesmo tempo, não vou entrar em detalhes de como funciona a Stack e Heap, você pode ler mais no meu artigo sobre GC publicado na .NET Magazine.
Um Value Type pode ficar na Stack sempre que for short-lived, isto é, uma variável local que não é usada dentro de um método anônimo ou lambda e desde que o método não seja um iterator, pois nesses casos a variável é "hoisted" e acaba virando uma propriedade de uma classe gerada pelo compilador. Quando a variável é short-lived ela pode ser descartada assim que o método retornar e por isso que Reference Types não podem ficar na Stack, já que podem por exemplo serem atribuídos à uma variável global, o que não acontece com Value Types pois é sempre feito uma cópia deles na atribuição.
Nada disso é motivo pra sair criando Value Types achando que vai melhorar a performance da sua aplicação. Raramente você vai precisar criar um Value Type, e quando precisar é bom seguir algumas boas práticas, como assegurar que seu Value Type é imutável.
Value Types devem sempre representar uma única informação como um inteiro, data, etc.
Se for mal usado pode até deixar sua aplicação mais lenta.