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

var s2 = new S2();
System.Console.WriteLine(s2.v);
s2.v = 1;
struct S2
{
public int v = 32;
public S2(int i) => v = i;
}

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:

public class Foo
{
private int f = 42;
public Foo()
{
System.Console.WriteLine("Foo()");
}
public Foo(int i)
{
System.Console.WriteLine("Foo(int)");
}
}

o compilador C# irá processá-lo como se o mesmo fosse escrito como:

public class Foo
{
private int f;
public Foo()
{
f = 42;
System.Console.WriteLine("Foo()");
}
public Foo(int i)
{
f = 42;
System.Console.WriteLine("Foo(int)");
}
}
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.

.class public auto ansi beforefieldinit Foo extends [System.Runtime]System.Object
{
.field private int32 f // `f` is a *private* field of type *int32* (i.e, System.Int32)
// Constructor taking no parameters
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
{
// IL_0000~IL_0003 is the generated IL for the C# code: `f = 42;`
IL_0000: ldarg.0
IL_0001: ldc.i4.s 42
IL_0003: stfld int32 Foo::f
// IL_0008~IL_0009: runs base constructor
IL_0008: ldarg.0
IL_0009: call instance void [System.Runtime]System.Object::.ctor()
// IL_000e~IL_0013: calls `Console.WriteLine("Foo()")`
IL_000e: ldstr "Foo()"
IL_0013: call void [System.Console]System.Console::WriteLine(string)
IL_0018: ret
} // end of method Foo::.ctor
// Constructor taking 1 int parameter
.method public hidebysig specialname rtspecialname instance void .ctor (int32 i) cil managed
{
// IL_0000~IL_0003 is the generated IL for the C# code: `f = 42;`
// Note that the same code used to assign 42 to `f` was emitted in this constructor as well.
IL_0000: ldarg.0
IL_0001: ldc.i4.s 42
IL_0003: stfld int32 Foo::f
// rest of constructor removed for brevity.
//...
IL_0018: ret
} // end of method Foo::.ctor
} // end of class Foo
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!

No comments: