Dec 22, 2023

Structs em C# - diversão garantida - Parte 5/9: Outros cenários em que o comportamento de construtores em estruturas podem te surpreender.

Read this post in English

Lisez cet post en french

  1. Structs em C# - diversão garantida
  2. Rápida introdução à Value Types vs Reference Types.
  3. Inicialização de campos em estruturas.
  4. Comportamento de construtores em estruturas.
  5. Outros cenários em que o comportamento de construtores em estruturas podem te surpreender (este post).
  6. Argumentos default  em construtores de estruturas (você ainda não esta confuso ?).
  7. Modificador `required` do C# 11 não vai salvar seu c* trabalho.
  8. Estruturas usadas como valor default de argumentos..
  9. 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:

Dado o código acimae, qual valores você espera que sejam mostrados? 42, 42, -1, 42?

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:
...
  • 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.
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.

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).

Les structures en C# sont amusantes - Partie 5/9: Des autres scénarios dans lesquels le comportement des constructeurs de structure peut vous surprendre

Leia este post em português

Read this post in English.

  1. Les structures en C# sont amusantes
  2. Brève introduction aux Value Types vs Reference Types.
  3. Initialisation des champs dans les structures.
  4. Comportement des constructeurs dans structures.
  5. Des autres scénarios dans lesquels le comportement des constructeurs de structure peut vous surprendre (cet post).
  6. Struct avec des valeurs d'argument par défaut dans les constructeurs, ou, n'êtes-vous pas encore confus ?
  7. Le modificateur `required`de C # 11 ne sauvegardera pas votre c*l emploi.
  8. Structure utilisée comme valeurs d'argument défaut.
  9. Bonus: L'evolution des structures en C#

Dans l'article précédent, j'ai présenté un scénario dans lequel un struct ctor ne serait pas invoqué, peu importe la façon dont la syntaxe peut nous laisser croire qu'il le serait. Cet article développe cela en ajoutant quelques autres scénarios présentés dans le code ci-dessous:

Compte tenu du code ci-dessus, qu'attendez-vous d'être imprimé ? 42, 42, -1, 42?

Avec la connaissance du post précédent, nous savons que le quatrième appel (marqué comme //4) invoquera le constructeur sans paramètre d'où s_4.v aura une valeur de 42 ; mais qu'en est-il des 3 autres cas

Si nous inspectons l'IL généré, nous verrons que le compilateur a émis un appel au constructeur S(int) pour s_3 mais qu'aucun constructeur n'a été invoqué pour les deux premiers cas, ce qui signifie que s_1 et s_2 auront leur champ v initialisé à 0, donc le le code ci-dessus s'imprimera 0, 0, -1, 42.

Mais, porquoi ?

On pourrait dire que dans les 4 scénarios, un type valeur est instancié et à partir duquel un constructeur aurait dû être exécuté. Examinons donc un peu plus les 2 scénarios ci-dessus pour lesquels aucun constructeur n'est invoqué et essayons de raisonner sur la motivation derrière cette décision.

En commençant par s_1, si nous regardons la documentation de l'expression par défaut, nous pouvons lire:

Une expression de valeur par défaut est utilisée pour obtenir la valeur par défaut (valeurs par défaut) d’un type.

qui  référence de section valeurs par défaut où nous pouvons lire:

Pour une variable d’un Value_type, la valeur par défaut est la même que la valeur calculée par le constructeur par défaut de Value_type(constructeurs par défaut).

qui lui-même fait référence à la section Constructeurs par Défaut où l'on peut enfin lire:

Tous les types valeur déclarent implicitement un constructeur d’instance sans paramètre public appelé le constructeur par défaut. Le constructeur par défaut retourne une instance initialisée à zéro appelée valeur par défaut pour le type de valeur :
...
  • Pour un struct_type, la valeur par défaut est la valeur produite en affectant à tous les champs de type valeur leur valeur par défaut et à tous les champs de type référence la valeur null .

Ainsi, même si la documentation mentionne l'invocation du constructeur par défaut, elle indique également que le constructeur par défaut pour un type valeur équivaut à mettre à zéro toute la mémoire réservée au type1. Ainsi, aucun constructeur invoqué dans ce scénario n'est le comportement attendu.

Cela nous laisse avec notre dernier scénario : des tableaux (arrays) de types valeur. Pour résoudre ce problème, recourons à nouveau à ce que dit la spécification du langage à propos de la création des tableaux :

Les instances de tableau sont créées par array_creation_expression (expressions de création de tableau)
...
Les éléments des tableaux créés par array_creation_expression s sont toujours initialisés à leur valeur par défaut (valeurs par défaut).

ce qui signifie que lorsqu'un tableau est instancié, tous ses éléments sont définis sur la valeur par défaut du type tableau, ce qui pour les types valeur, comme nous l'avons vu ci-dessus, équivaut à remettre à zéro la mémoire allouée.

Je crois que ce comportement consistant à ne pas invoquer le constructeur par défaut sur les types valeur a deux motivations principales : i) historiquement, C# n'a pas pris en charge le concept de constructeurs explicites sans paramètre (par défaut) sur les types valeur jusqu'à C# 102, et ii) performance : imaginez du code faire quelque chose comme
new S[1_000_000]; si les constructeurs par défaut devaient être exécutés, cela pourrait prendre un temps imprévisible.

As always, all feedback is welcome.

Structs in C# are fun - Part 5/9: Other scenarios in which struct constructor behavior may surprise you

Leia este post em português

Lire cet post en français.

  1. Structs in C# are fun.
  2. Brief introduction to Value Types vs Reference Types.
  3. Field initialization in structs.
  4. Constructors and struct behavior
  5. Other scenarios in which struct constructors behavior may surprise you (this post)
  6. Struct with default argument values in constructors, a.k.a, are you not confused yet?
  7. `required` feature from C# 11 will not save your a** job.
  8. Struct used as default argument values.
  9. Bonus: Struct evolution in C#.

In the previous post I've presented a scenario in which a struct ctor would not be invoked, no matter how the syntax may lead us to believe one would be. This post expands on that, adding a couple more of those scenarios shown in the code below:

Given the code above, what do you expect to be printed ? 42, 42, -1, 42?

With the knowledge from the previous post we know that the fourth invocation (marked as // 4) will invoke the parameterless constructor whence, s_4.v will have a value of 42; but what about the other 3 cases? 

If we inspect the generated IL we'll see that the compiler emitted a call to S(int) constructor for s_3 but no constructors were invoked for the two first cases meaning that s_1 and s_2 will have its v field initialized to 0, so the code above will print 0, 0, -1, 42.

Why ?

One could argue that in all 4 scenarios a value type is being instantiated and whence a constructor should have been executed, so lets dig a little more on the 2 scenarios above for which no constructor is invoked and try to reason about the motivation  behind the decision to do so.

Starting with s_1, if we look into the documentation for the default expression we can read:

A default value expression is used to obtain the default value (§9.3) of a type.

referencing section 9.3 Default Values where we can read:

For a variable of a value_type, the default value is the same as the value computed by the value_type’s default constructor (§8.3.3).

which itself references section 8.3.3 Default Constructors where we can finally read:

All value types implicitly declare a public parameterless instance constructor called the default constructor. The default constructor returns a zero-initialized instance known as the default value for the value type:
...
  • For a struct_type, the default value is the value produced by setting all value type fields to their default value and all reference type fields to null.

so, even though the documentation mentions invoking the default constructor to obtain the default value it also says that the default constructor for a value type is equivalent to zeroing out all the memory reserved for the type1 so, no constructor being invoked in this scenario is the expected behavior.

This leaves us with our last scenario: arrays of value types. To tackle that one lets resort again on what language specification says about array creation:

Array instances are created by array_creation_expressions (§12.8.16.5)....

Elements of arrays created by array_creation_expressions are always initialized to their default value (§9.3).

which means that when an array is instantiated all of its elements are set to the default value of the array type, which for value types, as we saw above, is the equivalent of zeroing out the allocated memory.

I believe that this behavior of not invoking the default constructor on value types has two main motivations: i) historically C# did not supported the concept of explicit parameterless (default) constructors on value types until C# 102, and ii) performance: imagine some code doing something like new S[1_000_000]; if default constructors were to be executed this could take an unpredictable amount of time.

As always, all feedback is welcome.

Nov 22, 2023

Les structures en C# sont amusantes - Partie 4/9: Comportement des constructeurs dans structures

Leia este post em português

Read this post in English.

  1. Les structures en C# sont amusantes
  2. Brève introduction aux Value Types vs Reference Types.
  3. Initialisation des champs dans les structures.
  4. Comportement des constructeurs dans structures (cet post).
  5. Des autres scénarios dans lesquels le comportement des constructeurs de structure peut vous surprendre.
  6. Struct avec des valeurs d'argument par défaut dans les constructeurs, ou, n'êtes-vous pas encore confus ?
  7. Le modificateur `required`de C # 11 ne sauvegardera pas votre c*l emploi.
  8. Structure utilisée comme valeurs d'argument défaut.
  9. Bonus: L'evolution des structures en C#

Dans le post précédent de notre série sur les Value Types, nous avons présenté le code ci-dessous:

qui imprime 0 (par opposition à 32 comme certains pourraient s'y attendre) et a également exploré les bases de la gestion de l'initialisation des champs en C#. Cet article élargit cette discussion en explorant certains aspects clés qui contribuent à la disparité entre le comportement attendu/observé.

Commençons par la déclaration suivante du post précédent:

D'une manière trop simpliste, chaque fois que le compilateur C# trouve une initialisation de champ, il déplacera simplement le code d'initialisation vers les constructeurs, c'est-à-dire que l'initialisation d'un champ équivaut à définir sa valeur dans les constructeurs (les champs statiques sont initialisés dans les constructeurs statiques)...

Si cela est vrai (spoiler : c'est le cas) et que le code instancie une nouvelle structure (ligne 1), pourquoi le champ n'est-il pas initialisé ? La réponse courte est que malgré l'expression new, aucun constructeur n'est exécuté, ce qui peut être facilement vérifié en examinant le il généré ci-dessous:

Notez que l'expression 'new S2()'  a été compilée comme l'instruction IL intobj S2 (IL_0002 dans la méthode '<Main>$' mise en évidence dans la capture d'écran ci-dessus). La documentation de cette instruction indique1:

Initialise tous les champs du type de valeur figurant à l'adresse spécifiée en utilisant la référence null ou la valeur 0 du type primitif qui convient...

Contrairement à Newobj, initobj n’appelle pas la méthode du constructeur. Initobj est destiné à l’initialisation des types de valeurs, tandis qu’il newobj est utilisé pour allouer et initialiser des objets.

nous conduisant à la question suivante, naturelle : pourquoi le compilateur a-t-il émis une instruction initobj au lieu d'une newobj?

Si vous prêtez une attention particulière à la déclaration de la structure S2 (lignes 5 à 9), vous remarquerez qu'il n'y a en fait aucun constructeur sans paramètre déclaré, donc la réponse à cette question devient claire : parce que le compilateur ne peut pas invoquer un constructeur non existant, et pour les Value Types, il peut recourir à initobj à la place!

Pour prouver ce point, vous pouvez simplement modifier le code en introduisant un constructeur sans paramètre dans S22:

et observez que maintenant IL_0002 contient l'instruction call instance void S2::.ctor() exécutant effectivement le constructeur sans paramètre sur s2 et d'où, exécutant l'initialisation du champ et provoquant la sortie du programme 32.

Donc en résumé :

  1. Les initialiseurs de champ sont injectés dans les constructeurs du type dans lequel le champ est déclaré ;
  2. Puisqu'aucun constructeur n'est exécuté, tous les champs de structure sont simplement mis à zéro, ce qui explique pourquoi le programme en haut imprime zéro au lieu de 32.

Comme toujours, tous les commentaires sont bienvenus.

Amuse toi!


Structs in C# are fun - Part 4/9: Constructors and struct behavior

Leia este post em português

Lire cet post en français.

  1. Structs in C# are fun.
  2. Brief introduction to Value Types vs Reference Types.
  3. Field initialization in structs.
  4. Constructors and struct behavior (this post).
  5. Other scenarios in which struct constructors behavior may surprise you.
  6. Struct with default argument values in constructors, a.k.a, are you not confused yet?
  7. `required` feature from C# 11 will not save your a** job.
  8. Struct used as default argument values.
  9. Bonus: Struct evolution in C#.

In the previous post of our series on Value Types we presented the code below:

which prints 0 (as opposed to 32 as some would expect) and we've also explored the basics of how field initialization is handled in C#. This post expands that discussion exploring some key relevant aspects that contributes to the disparity between expected/observed behavior.

Let's start with the following statement from the previous post:

In an oversimplified way, whenever C# compiler finds a field initialization it will simply move the initialization code to the constructors, i.e, initializing a field is equivalent to setting its value in the constructors (static fields are initialized in static constructors)...

If that is true (spoiler: it is) and the code is instantiating a new struct (line 1)  why is the field not being initialized ? The short answer is that despite the new expression, no constructor is being run which can be easily verified by looking into the generated il below: 

Note that the expression `new S2()` was compiled as the IL instruction intobj S2 (IL_0002 in method '<Main>$' highlighted in the above screenshot) by the compiler. The documentation for that instruction states1:

Initializes each field of the value type at a specified address to a null reference or a 0 of the appropriate primitive type.

Unlike Newobj, initobj does not call the constructor method. Initobj is intended for initializing value types, while newobj is used to allocate and initialize objects.

leading us to the next, natural, question: why does the compiler emitted a initobj instruction instead of a newobj?

If you pay close attention to the struct declaration (lines 5~9) you'll notice that there are actually no parameterless constructor declared, so the answer to that question becomes clear: because the compiler cannot invoke a non existing constructor, and for value types, it can resort to initobj instead!

To prove that point you can simply change the code, introducing a parameterless constructor in S22:

and observe that now IL_0002 contains the instruction call instance void S2::.ctor() effectively running  the parameterless constructor on s2 and whence, running the field initialization and causing the program to output 32.

So, in summary:

  1. Field initializers are injected into the constructors of the type in which the field is declared;
  2. Since no constructor is executed, all struct fields are simply zeroed out which  explains why the program at the top prints zero instead of 32.

Last, but not least, note that the behavior for classes is different and simply changing the declaration from struct to class (in the original program) leads to a compilation error since the compiler cannot use initobj instruction to initialize a reference type and requires a constructor3 to be available.

As always, all feedback is welcome.

Structs em C# - diversão garantida - Parte 4/9: Comportamento de construtores em estruturas

Read this post in English

Lisez cet post en french.

  1. Structs em C# - diversão garantida
  2. Rápida introdução à Value Types vs Reference Types.
  3. Inicialização de campos em estruturas.
  4. Comportamento de construtores em estruturas (este post).
  5. Outros cenários em que o comportamento de construtores em estruturas podem te surpreender.
  6. Argumentos default  em construtores de estruturas (você ainda não esta confuso ?).
  7. Modificador `required` do C# 11 não vai salvar seu c* trabalho.
  8. Estruturas usadas como valor default de argumentos.
  9. Bonus: Evolução das estruturas em C#. 

No post anterior da nossa série sobre Value Types apresentamos o código abaixo:

o qual imprime 0 (ao contrário de 32 como alguns esperariam) e também exploramos os princípios básicos de como a inicialização de campos é tratada em C#. Este post expande essa discussão explorando alguns aspectos chaves que contribuem para a disparidade entre o comportamento esperado/observado.

Vamos começar com a seguinte declaração do post anterior:

De uma forma simplificada, sempre que o compilador C# encontra a inicialização de um campo o mesmo simplesmente move o código de inicialização para os construtores, ou seja, inicializar um campo é equivalente a definir seu valor nos construtores (campos estáticos são inicializados em construtores estáticos)...

Se isso for verdade (spoiler: é) e o código está instanciando uma nova estrutura (linha 1), por que o campo não está sendo inicializado? A resposta curta é que, apesar da expressão new, nenhum construtor está sendo executado, o que pode ser facilmente verificado observando-se o IL gerado abaixo:

Observe que a expressão C# `new S2()` foi compilada para a instrução IL intobj S2 (IL_0002 no método '<Main>$' destacado na imagem acima). A documentação dessa instrução afirma1:

Inicializa cada campo do tipo de valor em um endereço especificado como uma referência nula ou 0 do tipo primitivo apropriado
...
Ao contrário de Newobj , initobj não chama o método de construtor. Initobj destina-se à inicialização de tipos de valor, enquanto newobj é usado para alocar e inicializar objetos.

levando-nos à próxima pergunta natural: por que o compilador emitiu uma instrução initobj em vez de um newobj?

Se você prestar atenção à declaração da estrutura S2 (linhas 5 a 9), notará que na verdade não existe um construtor sem parâmetros declarado, então a resposta a essa pergunta fica clara: porque o compilador não pode invocar um construtor inexistente e para, Value Types, a opção natural é a utilização da instrução initobj!

Para provar esse ponto você pode simplesmente alterar o código introduzindo um construtor sem parâmetros em S22:

Observe que agora IL_0002 contém a instrução call instance void S2::.ctor(),  efetivamente executando o construtor sem parâmetros em s2 e desta forma a inicialização do campo fazendo com que o programa produza 32 como resultado.

Então, em resumo:

  1. Field Initializers são injetados nos construtores do tipo em que o campo é declarado;

  2. Como no programa exemplo nenhum construtor é executado, todos os campos da struct são simplesmente zerados, o que explica por que o programa imprime zero em vez de 32.

Como sempre, todo feedback é bem vindo.

Divirta-se!


1. This is not 100% accurate since newobj may be used to allocate value types in some scenarios, but that is not important for our discussion.
2. Until version 10, C# did not allow structs to have explicit parameterless constructors.
3. Read more about ctors in C#

Sep 10, 2023

Sponsors, Sponsors, Sponsors (Francais)

Leia este post em português.

Read this post in English.


Aujourd'hui, j'ai reçu une excellente nouvelle : je viens d'avoir un deuxième sponsor dans les sponsors github. Cela signifie beaucoup pour moi ; plus que le $ (qui est toujours le bienvenu), cela signifie que le projet est d'une manière ou d'une autre utile à certaines personnes et que certaines d'entre elles peuvent se permettre de le reconnaître par le parrainage.

Je ne peux que vous dire un grand merci :), votre parrainage est apprécié.

Cela dit, j'aimerais souligner qu'il existe plusieurs façons de contribuer à des projets open source si vous le souhaitez ; certains impliquent de donner de l'argent, d'autres impliquent de donner du temps pour signaler des bogues, s'engager dans des discussions, contribuer avec des demandes d'extraction, proposer des modifications, etc.

Plus précisément dans le cas de Cecilifier, si vous souhaitez contribuer avec du code, nous pouvons organiser des séances en binôme pour aider à abaisser la barrière d'entrée et donner un aperçu de haut niveau ainsi que plonger dans certaines parties du code.

Si cela vous intéresse, laissez-moi simplement un commentaire dans ce post ou envoyez-moi un message privé sur Twitter / Mastodon (Je peux communiquer nativement en portugais, avec compétence en anglais et dans une certaine mesure en français - cette post c'est traduit de la version anglaise :) et nous pourrons arranger ça
.



Sponsors, Sponsors, Sponsors!

Leia este post em português

Lisez cet post en francais.

Today I've been grated with some great news: I've just got a second sponsor in github sponsors. This means a lot to me; more than the $ (which is always welcome), this means the project is somehow useful to some people and that some of those people can afford recognizing it by sponsorship.

I can only say thank you very much :), your sponsorship is appreciated.

That said, I'd like to emphasize that there are multiple ways to contribute to open source projects if you are willing to; some involve donating $, some involve donating time to file bugs, engaging in discussions, contributing with pull requests, proposing changes, etc.

Specifically in the case of Cecilifier, if you are interested in contributing with code we can arrange some pair sessions to help lowering the entry barrier and give some high level  overview as well dive in some parts of the code. 

If that interests you, just drop me a comment in this post or DM me in twitter / mastodon (I can communicate natively in Portuguese, proficiently in English and to some extent in French :) and we can arrange something.



Patriocinadores, Patriocinadores, Patriocinadores!

Lisez cet post en français.

Read this post in English.


Hoje recebi uma ótima notícia: acabei de conseguir um segundo patrocinador no github. Isto significa muito para mim; mais do que o $ (que é sempre bem-vinda), isso significa que o projeto é, de alguma forma, útil para algumas pessoas e que algumas dessas pessoas podem se dar ao luxo de reconhecê-lo por meio de patrocínio.

Só posso dizer muito obrigado :), Seu patrocínio é muito apreciado.

Dito isto, gostaria de enfatizar que existem diversas maneiras de contribuir com projetos de código aberto se você estiver disposto a fazê-lo; alguns envolvem doar $, alguns envolvem doar tempo para registrar bugs, participar de discussões, contribuir com pull requests, propor mudanças, etc.

Especificamente no caso do Cecilifier, se você estiver interessado em contribuir com código, podemos organizar algumas sessões de pair programming para ajudar a diminuir a barreira de entrada e fornecer uma visão geral, bem como mergulhar em algumas partes do código.

Se isso lhe interessa, entre em contato comentando neste post ou me envie uma DM no twitter / mastodon (posso me comunicar nativamente em português, proficientemente em inglês e até certo ponto em francês :) e podemos combinar algo.



Aug 31, 2023

Structs em C# - diversão garantida - Parte 3/9: Inicialização de campos em structs

Read this post in English

Lisez cet post en french.

  1. Structs em C# - diversão garantida
  2. Rápida introdução à Value Types vs Reference Types.
  3. Inicialização de campos em estruturas (este post).
  4. Comportamento de construtores em estruturas.
  5. Outros cenários em que o comportamento de construtores em estruturas podem te surpreender.
  6. Argumentos default  em construtores de estruturas (você ainda não esta confuso ?).
  7. Modificador `required` do C# 11 não vai salvar seu c* trabalho.
  8. Estruturas usadas como valor default de argumentos.
  9. Bonus: Evolução das estruturas em C#. 

Continuando com nossa série sobre Value Types, dado o código abaixo, o que você espera que ele imprima (recomendo tentar responder antes de executá-lo):

Se você respondeu 0 (zero), acertou em cheio e provavelmente já possui um bom entendimento de como .NET/C# lida com instanciação/inicialização de campos de value types.; por outro lado, se você respondeu 32 provavelmente você foi levado ao engano. 

Neste post discutirei brevemente um dos aspectos responsáveis por esse comportamento: inicialização de campos em .NET, ou para ser mais preciso, em C#.

De uma forma simplificada, sempre que o compilador C# encontra a inicialização de um campo o mesmo simplesmente move o código de inicialização para os construtores, ou seja, inicializar um campo é equivalente a definir seu valor nos construtores (campos estáticos são inicializados em construtores estáticos), portanto, dado o código abaixo:

o compilador C# irá processá-lo como se o mesmo fosse escrito como:
o  que pode ser confirmado no código
IL gerado abaixo contudo,  caso não esteja familiarizado com código IL, tenha o seguinte em mente:

  • Não se deixe assustar com sua aparente complexidade.
  • Incluí comentários para enfatizar as partes principais.
  • Detalhes menos imporatntes foram omitidos.
  • Não é necessário compreender todos os detalhes para entender o conceito principal.

As linhas 6~22 e 25~36 definem, respectivamente, um construtor sem parâmetros e um que recebe um número inteiro; observe que o código relacionado à inicialização do campo (`f = 42`) foi introduzido em ambos (linhas 9~11 e 29~31).

No próximo post começaremos a explorar o comportamento de construtores em  estruturas (struct), também conhecido como a segunda parte do quebra-cabeça que explica por que o programa no topo do post imprime 0.

Como sempre, todo feedback é bem vindo.

Divirta-se!

Structs in C# are fun - Part 3/9: Field initialization in structs

Leia este post em português

Lisez cet post en french.

  1. Structs in C# are fun.
  2. Brief introduction to Value Types vs Reference Types.
  3. Field initialization in structs (this post).
  4. Constructors and struct behavior.
  5. Other scenarios in which struct constructors behavior may surprise you.
  6. Struct with default argument values in constructors, a.k.a, are you not confused yet?
  7. `required` feature from C# 11 will not save your a** job.
  8. Struct used as default argument values.
  9. Bonus: Struct evolution in C#.

Continuing with our series on Value Types, given the code below, what do you expect it to print (I recommend trying to answer without running it first):

If you answered 0 (zero) you nailed it and probably already have a good understanding of how .NET/C# handles value type instantiation / field initialization; by the other hand, if you answered 32 you may have been tricked.

In this post I'll briefly discuss one of the aspects that leads to this behavior: field initialization in .NET, or to be more precise, in C#.

In an oversimplified way, whenever C# compiler finds a field initialization it will simply move the initialization code to the constructors, i.e, initializing a field is equivalent to setting its value in the constructors (static fields are initialized in static constructors), so given the code below:

the C# compiler will process it as it was written as:

which you can confirm in the generated IL below, but keep the following in mind if you're not familiar with IL code:

  1. There's no need to be daunted by its apparent complexity.
  2. I've included comments to emphasize the key parts.
  3. I've omitted certain less crucial specifics.
  4. Grasping all the details isn't important to understand the main concept.

Lines 6~22 and 25~36 defines, respectively, the constructor taking no parameters and the one taking an integer;  note that the code related to field initialization (`f = 42`) has been introduced in both (lines 9~11 and 29~31).

In the next post we'll start exploring struct constructor behavior, a.k.a, the second part of the puzzle that explains why the program at the top of the post prints 0.

As always, all feedback is welcome.

Have fun!

Les structures en C# sont amusantes - Partie 3/9: Initialisation des champs dans les structures

Leia este post em português

Read this post in English.

  1. Les structures en C# sont amusantes
  2. Brève introduction aux Value Types vs Reference Types.
  3. Initialisation des champs dans les structures (cet post).
  4. Comportement des constructeurs dans structures.
  5. Des autres scénarios dans lesquels le comportement des constructeurs de structure peut vous surprendre.
  6. Struct avec des valeurs d'argument par défaut dans les constructeurs, ou, n'êtes-vous pas encore confus ?
  7. Le modificateur `required`de C # 11 ne sauvegardera pas votre c*l emploi.
  8. Structure utilisée comme valeurs d'argument défaut.
  9. Bonus: L'evolution des structures en C#.

Poursuivant notre série sur les types de valeur (Value Types), étant donné le code ci-dessous, qu'attendez-vous qu'il imprime (je vous recommande d'essayer de répondre avant de l'exécuter):

Si vous avez répondu 0 (zéro), vous l'avez cloué et avez probablement déjà une bonne compréhension de la façon dont .NET/C# gère l'instanciation de type valeur/l'initialisation de champ; par contre, si vous avez répondu 32, vous avez peut-être été trompé.

Dans cet article, je vais aborder brièvement l'un des aspects qui conduisent à ce comportement : l'initialisation des champs en .NET, ou pour être plus précis, en C#.

D'une manière trop simpliste, chaque fois que le compilateur C# trouve une initialisation de champ, il déplacera simplement le code d'initialisation vers les constructeurs, c'est-à-dire que l'initialisation d'un champ équivaut à définir sa valeur dans les constructeurs (les champs statiques sont initialisés dans les constructeurs statiques), donc étant donné le code ci-dessous :

le compilateur C# le traitera tel qu'il a été écrit ainsi :

ce que vous pouvez confirmer dans l'IL généré ci-dessous, mais gardez ce qui suit à l'esprit si vous n'êtes pas familier avec le code IL :

  1. Il ne faut pas se laisser intimider par son apparente complexité.
  2. J'ai inclus des commentaires pour souligner les éléments clés.
  3. J'ai omis certains détails moins cruciaux.
  4. Il n’est pas nécessaire de saisir tous les détails pour comprendre le concept principal.

Les lignes 6~22 et 25~36 définissent le constructeur ne prenant aucun paramètre et celui prenant un int (Int32); notez que le code lié à l'initialisation du champ (`f = 42`) a été introduit dans les deux (lignes 9~11 et 29~31).

Dans le prochain article, nous commencerons à explorer le comportement du constructeur de structure, c'est-à-dire la deuxième partie du puzzle qui explique pourquoi le programme en haut de l'article imprime 0.

Comme toujours, tous les commentaires sont bienvenus.

Amuse toi!