- Structs in C# are fun.
- Brief introduction to Value Types vs Reference Types.
- Field initialization in structs (this post).
- Constructors and struct behavior.
- Other scenarios in which struct constructors behavior may surprise you.
- Struct with default argument values in constructors, a.k.a, are you not confused yet?
- `required` feature from C# 11 will not save your
a**job. - Struct used as default argument values.
- 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):
var s2 = new S2(); | |
System.Console.WriteLine(s2.v); | |
s2.v = 1; | |
struct S2 | |
{ | |
public int v = 32; | |
public S2(int i) => v = i; | |
} |
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#.
public class Foo | |
{ | |
private int f = 42; | |
public Foo() | |
{ | |
System.Console.WriteLine("Foo()"); | |
} | |
public Foo(int i) | |
{ | |
System.Console.WriteLine("Foo(int)"); | |
} | |
} |
the C# compiler will process it as it was written as:
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)"); | |
} | |
} |
- There's no need to be daunted by its apparent complexity.
- I've included comments to emphasize the key parts.
- I've omitted certain less crucial specifics.
- Grasping all the details isn't important to understand the main concept.
.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 |
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!
No comments:
Post a Comment