Order of static constructors/initializers in C#
Asked Answered
A

4

29

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).

Arriviste answered 8/10, 2008 at 23:44 Comment(3)
I think that across files (i.e. across different classes) it will happen the same. During the type initialization of class A, class B will be asked to initialize, and class B will find a null reference to class A.Hanyang
Now, across files of same class (partial class), probably is up to the pre-processor to determine if it fails or not.Hanyang
So if A references B.b then A.a getting inited will bump B.b?Arriviste
H
15

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.

Hanyang answered 8/10, 2008 at 23:54 Comment(2)
This is sure. The static constructor is called when the type is referenced for the first time.Hanyang
Be careful here about the difference between static variable initializers and static constructors. There are different rules around when type initialization occurs based on the presence/absence of a static constructor. See pobox.com/~skeet/csharp/beforefieldinit.htmlFerrous
G
23

See section 10.4 of the C# spec for the rules here:

when a class is initialized, all static fields in that class are first initialized to their default values, and then the static field initializers are executed in textual order. Likewise, when an instance of a class is created, all instance fields in that instance are first initialized to their default values, and then the instance field initializers are executed in textual order. It is possible for static fields with variable initializers to be observed in their default value state. However, this is strongly discouraged as a matter of style.

So in other words, in your example 'b' is initialized to its default state (null) and so the reference to it in the initializer of 'a' is legal but would result in a NullReferenceException.

These rules are different to Java's (see section 8.3.2.3 of the JLS for Java's rules about forward references, which are more restrictive).

Garges answered 8/10, 2008 at 23:59 Comment(3)
Great answer to the question I asked. However it seems I didn't ask the question I wanted to: what about across files?Arriviste
I'm not a C# whiz (just knew where to look) but check out msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx -- "It is possible to construct circular dependencies that allow static fields with variable initializers to be observed in their default value state". Does that help?Garges
@Garges not really. It doesn't address the cross file issue.Arriviste
H
15

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.

Hanyang answered 8/10, 2008 at 23:54 Comment(2)
This is sure. The static constructor is called when the type is referenced for the first time.Hanyang
Be careful here about the difference between static variable initializers and static constructors. There are different rules around when type initialization occurs based on the presence/absence of a static constructor. See pobox.com/~skeet/csharp/beforefieldinit.htmlFerrous
B
2

Personally I would get rid of the static initializers since it isn't clear and add a static constructor to initialize these variables.

static private List<int> a;
static private List<int> b;

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

Then you don't have to guess what is going on and you're being clear in your intentions.

Bacchae answered 8/10, 2008 at 23:59 Comment(1)
Note that those pieces of code aren't exactly equivalent in terms of when they run: pobox.com/~skeet/csharp/beforefieldinit.htmlFerrous
O
0

Yes, you were lucky. C# appears to execute the code in the order it appears in the class.

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

Will work but ...

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

Will fail.

I would recommend putting all your dependencies in one place, the static constructor is the place for this.

static MyClass()
{
  a = new List<int>() { 0 };
  b = new List<int>() { a[0] };
}
Opt answered 8/10, 2008 at 23:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.