Search code examples
c#staticdependenciesinternals

Order of static constructors/initializers in C#


While working on a C# app I just noticed that in several places static initializers have dependencies on each other like this:

static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };

Without doing anything special that worked. Is that just luck? Does C# have rules to resolve this?

Edit: (re: Panos) In a file lexical order seems to be king? what about across files?

In looking I tried a cyclical dependency like this:

static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { a[0] };

and the program didn't run the same (the test suit failed across the board and I didn't look further).


Solution

  • It seems to depend on the sequence of lines. This code works:

    static private List<int> a = new List<int>() { 1 };
    static private List<int> b = new List<int>() { a[0] };
    

    while this code does not work (it throws a NullReferenceException)

    static private List<int> a = new List<int>() { b[0] };
    static private List<int> b = new List<int>() { 1 };
    

    So, obviously no rules for cyclical dependency exist. It's peculiar however that the compiler does not complain...


    EDIT - What's happening "across files"? If we declare these two classes:

    public class A {
        public static List<int> a = new List<int>() { B.b[0] };
    }
    public class B {
        public static List<int> b = new List<int>() { A.a[0] };
    }
    

    and try to access them with this code:

    try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); }
    try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
    try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
    

    we are getting this output:

    The type initializer for 'A' threw an exception.
    Object reference not set to an instance of an object.
    The type initializer for 'A' threw an exception.
    

    So the initialization of B causes an exception in static constructor A and lefts field a with the default value (null). Since a is null, b can not also be initialized properly.

    If we do not have cyclical dependencies, everything works fine.


    EDIT: Just in case you didn't read the comments, Jon Skeet provides a very interesting reading: The differences between static constructors and type initializers.