Monday, January 08, 2007 1:20 PM bart

Answers to C# Quiz - Field initialization

A few days ago I posted the following two posts for this C# Quiz:

The question was the following:

What's the difference between the following two code fragments?

Fragment 1:

class Bar { private int i = 123; public Bar() { } }

Fragment 2:

class Bar { private int i; public Bar() { i = 123; } }

Some of my readers got the point right and saw the difference with or without aid of my extra code sample:

1 using System; 2 3 class Foo 4 { 5 public Foo() 6 { 7 Console.WriteLine(this.ToString()); 8 } 9 } 10 11 class Bar1 : Foo 12 { 13 private int i; 14 15 public Bar1() 16 { 17 i = 123; 18 } 19 20 public override string ToString() 21 { 22 return i.ToString(); 23 } 24 } 25 26 class Bar2 : Foo 27 { 28 private int i = 123; 29 30 public Bar2() 31 { 32 } 33 34 public override string ToString() 35 { 36 return i.ToString(); 37 } 38 } 39 40 class Program 41 { 42 static void Main() 43 { 44 new Bar1(); 45 new Bar2(); 46 } 47 }

This piece of code will print

0
123

The reason? Simply stated, field initializers are guaranteed to be executed prior to all constructor logic. In Bar2 this means that i will have the value 123 right before the statements in the constructor (or the default constructor) are executed. Still, this doesn't explain the difference completely. In addition, one needs to know that base constructors are called before all subclass constructor logic happens. In this case it means the following call chain:

new Bar2()
-> new Foo()
   -> new System.Object()

Once this chain of calls has completed, constructor logic is exected. In the case of Bar2, the constructor is empty. In the call chain above, the field i is guaranteed to be set to 123. Basically the assignment happens as the first instruction of the Bar2 class's constructor, before the base constructors are called:

30 - public Bar2()
31 - {
28 -    private int i = 123;
 5 -    public Foo()
 6 -    {
           <outside scope - System.Object::ctor>
 7 -       Console.WriteLine(this.ToString());
 8 -    }
32 - }

In Bar1 the situation is different, since the field i is not initialized:

15 - public Bar1()
16 - {
 5 -    public Foo()
 6 -    {
           <outside scope - System.Object::ctor>
 7 -       Console.WriteLine(this.ToString());
 8 -    }
17 -    i = 123;
32 - }

The IL equivalents of both constructors are displayed below:

Bar1:

.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 18 (0x12) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void Foo::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldc.i4.s 123 IL_000b: stfld int32 Bar1::i IL_0010: nop IL_0011: ret } // end of method Bar1::.ctor

Bar2:

.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 18 (0x12) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.s 123 IL_0003: stfld int32 Bar2::i IL_0008: ldarg.0 IL_0009: call instance void Foo::.ctor() IL_000e: nop IL_000f: nop IL_0010: nop IL_0011: ret } // end of method Bar2::.ctor

Observe the base constructor calls which occur in IL_0001 for class Bar1 and in line IL_0009 for class Bar2. For our sample, this means that the base constructor's Console.WriteLine(this.ToString()) call happens before i has been set in class Bar1 and after i has been set in class Bar2. Since ToString is a virtual method, it's called on Bar1 and Bar2 respectively, both classes which have access to the field i, which gets printed out.

The moral of the story: field initialization code gets rewritten as follows:

class Child : Parent { private int i = 123; private int j = 456; public Child() { //constructor logic } }

becomes (pseudo-code):

class Child : Parent { private int i; private int j; public Child() { i = 123; j = 456; Parent.ctor(); //constructor logic } }

Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: Answers to C# Quiz - Field initialization

Thursday, January 11, 2007 12:57 AM by Frederik

This also shows that calling virtual methods in a constructor can lead to unexpected results. So, unless you really know what you're doing, and have a reason to do it, you should never call a virtual method from inside a constructor. (When you analyze an assembly with FxCop, FxCop will also report this if you do so).