- Structs em C# - diversão garantida
- Rápida introdução à Value Types vs Reference Types.
- Inicialização de campos em estruturas (este post).
- Comportamento de construtores em estruturas.
- Outros cenários em que o comportamento de construtores em estruturas podem te surpreender.
- 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#.
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)"); | |
} | |
} |
- 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 |
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!