saner
saner

Reputation: 438

Static constructors and field initialization order in C#

This sample from C# in a Nutshell says it writes 0 followed by 3 due to "the field initializer that instantiates a Foo executes before X is initialized to 3".

class Program {
  static void Main() { Console.WriteLine (Foo.X); }   // 3
}

class Foo
{
    public static Foo Instance = new Foo(); //static field 1
    public static int X = 3; //static field 2
    
    Foo() => Console.WriteLine (X);   // 0
}

My question is, why does this not go into an infinite recursion due to static field 1 which makes a new Foo (which makes a new Foo, which makes a new Foo, etc.)?

Upvotes: 0

Views: 99

Answers (2)

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186668

Let's have a look at what's going on. BeforeFoo class addressing static fields must be initialized.

class Program {
  static void Main() { 
    // Before Foo class is addressed (here Foo.X), Foo must be initialized 
    Console.WriteLine(Foo.X); 
  }   
}

.Net will do it in order they are mentioned in the class declaration:

class Foo
{
    // Will be run first
    public static Foo Instance = new Foo(); 
    // Will be run second
    public static int X = 3; 
    
    Foo() => Console.WriteLine(X);   
}
  1. So far so good, Instance start its initialization
public static Foo Instance = new Foo(); 

constructor Foo() called which prints X: Foo() => Console.WriteLine (X); note, that since X has not been initialized, 0 will be printed.

  1. X will be initialized, it's now 3

Initialization is completed now, and .Net is ready to adddress Foo class

Console.WriteLine(Foo.X); 

and 3 will be printed (note the 2nd step of the initialization)

Upvotes: 1

pigeonhands
pigeonhands

Reputation: 3414

Static fields and properties are shared across all instances of a class, and get initialized in a special constructor called .cctor that gets called when the class is first referenced.

So the compiled code is something similar to

class Foo
{
    public static Foo Instance;
    public static int X;
    .cctor() {
        Instance = new Foo();
        X = 3;
    }
    
    Foo() => Console.WriteLine (X);
}

The call flow would be Foo::.cctor -> Foo::Foo() -> Foo:get_X().

So there is no recursion, but X will have its default value when .cctor is called.

Upvotes: 1

Related Questions