Is the order of static class initialization in C# deterministic?
Asked Answered
U

4

51

I've done some searching and I think the following code is guaranteed to produce output:

B.X = 7

B.X = 0

A.X = 1

A = 1, B = 0
static class B
{
    public static int X = 7;
    static B() {
        Console.WriteLine("B.X = " + X);
        X = A.X;
        Console.WriteLine("B.X = " + X);
    }
}

static class A
{
    public static int X = B.X + 1;
    static A() {
        Console.WriteLine("A.X = " + X);
    }
}

static class Program
{
    static void Main() {
        Console.WriteLine("A = {0}, B = {1}", A.X, B.X);
    }
}

I've run this numerous times and always get the output above the code section; I just wanted to verify will it change? Even if textually, class A and class B are re-arranged?

Is it guaranteed that the first use of a static object will trigger the initialization of its static members, followed by instantiating its static constructor? For this program, using A.X in main will trigger the initialization of A.X, which in turn initializes B.X, then B() and after finishing the initialization of A.X, will proceed to A(). Finally, Main() will output A.X and B.X`.

Upswing answered 9/9, 2010 at 22:36 Comment(3)
If Main() had used B.X first, then the output values would be 7, 8, 8, A = 8, B = 8.Upswing
For the specific code given in your question then Porges's answer is right on the money. For the more general question from your title -- "Is the order of static class initialization in C# deterministic?" -- the answer is that it depends: if the static types involved all have static constructors then initialization order is deterministic; if they don't then it's not.Careaga
Related: #1495235Arvell
P
60

Straight from ECMA-334:

17.4.5.1: "If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class."

And:

17.11: The execution of a static constructor is triggered by the first of the following events to occur within an application domain:

  • An instance of the class is created.
  • Any of the static members of the class are referenced.

If a class contains the Main method (§10.1) in which execution begins, the static constructor for that class executes before the Main method is called. If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor (§17.4.5).

So the order is:

  • A.X used, so static A() called.
  • A.X needs to be initialized, but it uses B.X, so static B() called.
  • B.X needs to be initialized, and it is initialized to 7. B.X = 7
  • All static fields of B are initialized, so static B() is called. X is printed ("7"), then it is set to A.X. A has already started being initialized, so we get the value of A.X, which is the default value ("when a class is initialized, all static fields in that class are first initialized to their default value"); B.X = 0, and is printed ("0").
  • Done initializing B, and the value of A.X is set to B.X+1. A.X = 1.
  • All static fields of A are initialized, so static A() is called. A.X is printed ("1").
  • Back in Main, the values of A.X and B.X are printed ("1", "0").

It actually comments upon this in the standard:

17.4.5: 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.

Pulchritude answered 9/9, 2010 at 23:20 Comment(1)
Shouldn't the initializer be called BEFORE the ctor? So is step 1 correct? A.X used, so static A() called. If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructorTalanian
R
13

About four different rules in the C# spec are involved in making this guarantee, and it is specific to C#. The only guarantee made by the .NET runtime is that type initialization begins before the type is used.

  • That static fields are zero-initialized until the type initializer runs.
  • That static field initializers run immediately before the static constructor.
  • That static constructors are called at the first instance constructor call or first static member reference.
  • That function arguments are evaluated in left-to-right order.

Relying on this is a very bad idea because it is likely to confuse anyone reading your code, especially if they are familiar with languages with a similar syntax which do not make all four of the above guarantees.

Please note that Porges comment was related to my initial statement (based on the .NET behavior) that the guarantees are too weak to assure the observed behavior. Porges is right that the guarantees are strong enough, but in fact a far more complex chain is involved than he suggests.

Reprove answered 9/9, 2010 at 22:47 Comment(1)
Not true. Read 17.4.5.1: "[If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor.] Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class." Static field initialization is only implementation-defined if you don't have a static constructor.Pulchritude
L
7

You may be interested to know that it's even possible to assign values to a field between its default initialization and variable initialization.

private static int b = Foo();
private static int a = 4;

private static int Foo()
{
    Console.WriteLine("{0} - Default initialization", a);
    a = 3;
    Console.WriteLine("{0} - Assignment", a);
    return 0;
}

public static void Main()
{
    Console.WriteLine("{0} - Variable initialization", a);
}

outputs

0 - Default initialization
3 - Assignment
4 - Variable initialization
Lilywhite answered 27/11, 2017 at 3:19 Comment(0)
T
-2

Deterministic initialization of static members is indeed guaranteed ... but it's not in "textual order". Furthermore, it may not be performed in a completely lazy fashion (meaning only when the static variable is first referenced). However, in your example using integers it wouldn't make a difference.

In some cases, it's desirable to get lazy initialization (particularly with expensive Singletons) - in which case you sometimes have to jump through some hoops to get it right.

Typical answered 9/9, 2010 at 22:40 Comment(2)
Due to the circular reference between the initialization of A and B laziness does affect the output.Reprove
Actually, static field initializers are executed in textual order: https://mcmap.net/q/41078/-is-the-order-of-static-class-initialization-in-c-deterministicHoneycutt

© 2022 - 2024 — McMap. All rights reserved.