- Structs em C# - diversão garantida
- Rápida introdução à Value Types vs Reference Types.
- Inicialização de campos em estruturas.
- Comportamento de construtores em estruturas.
- Outros cenários em que o comportamento de construtores em estruturas podem te surpreender (este post).
- Argumentos default em construtores de estruturas (você ainda não esta confuso ?).
- Modificador `required` do C# 11 não vai salvar seu
c*trabalho. - Estruturas usadas como valor default de argumentos..
- Bonus: Evolução das estruturas em C#.
No post anterior apresentamos um programa no qual struct contructors não erão invocados não importando como a sintaxe nos levasse a acreditar no contrário. Neste post continuamos a discussão sobre construtores em Value Types adicionando mais alguns cenários mostrados no código abaixo:
Baseado no conhecimento do post anterior sabemos que a inicialização da quarta variável (marcada como // 4) introduzirá uma chamada ao construtor sem parâmetros assim s_4.v será inicializado com o valor 42; mas e quanto aos outros 3 casos?
Se inspecionarmos o código IL gerado notaremos que o compilador emitiu uma chamada ao constructor S(int) para a variável s_3 mas que nenhum construtor foi executado na inicialização das duas primeiras variáveis o que implica que s_1 e s_2 terão o campo v inicializado com 0 fazendo com que código acima imprima 0, 0, -1, 42.
Porque ?
Poderíamos argumentar que nos quatro casos um value type está sendo instanciado e que, assim sendo, nos quatro casos um construtor deveria ser executado. Para tentar compreender porque nenhum construtor foi executado para as duas primeiras variáveis vamos recorrer à especificação da linguagem C#.
Começando com s_1, a documentação de default expression diz:
Uma expressão de valor padrão é usada para obter o valor padrão (valores padrão) de um tipo.
referenciando a sessão valores padrão (Default Values), onde podemos ler:
Para uma variável de um value_type, o valor padrão é o mesmo que o valor calculado pelo construtor padrão do Value_type(construtores padrão).
que por sua vez referencia a sessão construtores padrão (Default Constructors) onde finalmente podemos ler:
Todos os tipos de valor declaram implicitamente um construtor de instância sem parâmetros públicos chamado de construtor padrão. O construtor padrão retorna uma instância inicializada em zero conhecida como valor padrão para o tipo de valor:de forma que mesmo mencionando a execução de construtores padrão ela também define que o construtor padrão para um value type1 é equivalente a inicializar a memória reservada para a variável com 0 (zero). Assim sendo não executar tais construtores é o comportamento experado neste cenário.
...
- Para um struct_type, o valor padrão é o valor produzido definindo todos os campos de tipo de valor como seu valor padrão e todos os campos de tipo de referência como null.
Neste ponto nos resta apenas um último cenário: arrays de value types. Para compreender o mesmo vamos mais uma vez analisar o que a especificação da linguágem diz a respeito da instanciação de array:
As instâncias de matriz são criadas por array_creation_expressions (expressões de criação de matriz) ou por declarações de campo ou de variável local que incluem um array_initializer (inicializadores de matriz).....
Os elementos das matrizes criados por array_creation_expression s são sempre inicializados para seu valor padrão (valores padrão).
o que significa que quando um array é instanciado todos seus elementos são inicializados com seus respectivos valores default que no caso de value types, como vimos acima, é equivalente a inicializar a memória reservada para a variável com 0 (zeros).
Acredito que este comportamento tenha 2 principais motivos: i) historicamente C# introduziu suporte a declaração explicita de construtores sem parâmetros em value types apenas na versão 102 da linguágem e, ii) performance: imagine um programa alocando um array como new S[1_000_000]; se constructores padrão tivessem que ser executados, instanciação de arrays de value types poderiam apresentar um grande impacto em performance.
Como sempre, todo feedback é bem vindo.
Divirta-se!
1. Observe que mesmo com a introdução do suporte a declaração explícita de construtores padrão (sem parâmetros) no C# 10, nada muda com relação a default expressions para value types, ou seja, mesmo que um value type (VT) declare um construtor sem parâmetos default(VT) irá produzir uma instância inicializada com 0 (zero) e nenhum construtor será invocado (provavelmente a especificação da linguagem será modificada para tornar este ponto mais claro).