I'm concerned about the interaction between the following two behaviors:
http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=179
2.1. If the type is not yet initialized, try to take an initialization lock.
2.2.1. If not successful, see whether this thread or any thread waiting for this thread to complete already holds the lock.
2.2.2. If so, return since blocking would create a deadlock. This thread will now see an incompletely initialized state for the type, but no deadlock will arise.
http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=69
If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type.
This code example demonstrates the possible interaction:
static class Foo {
public static int A = 1;
}
static class Bar {
public static int B = Foo.A;
}
static class Program {
static void Main() {
Console.WriteLine(Bar.B);
}
}
When testing this in any sane environment, it will output 1. However, it seems that the spec allows it to output 0, by doing the following:
Is this really allowed? How am I supposed to write type initializers so I don't get bitten by it?
Is this really allowed?
It sure looks like it is allowed by the spec.
When testing this in any sane environment, it will output 1.
That's correct. It's helpful to understand the reasoning behind the optimization. The purpose of the "relaxed semantics" is to move the check for "has the static constructor run?" from execution time of an access to the type to jit time of a method which accesses the type. That is, if we have:
void M()
{
blah
if blah blah
... Foo.A ...
if blah blah blah
... Foo.A ...
blah blah blah
}
Suppose it is jit time for M
and Foo
's cctor has not executed yet. To be strictly conforming, the jitter has to generate code on every access to Foo.A to check whether the Foo
cctor has executed yet and execute it if it has not.
But if we do the cctor call at jit time, the jitter knows that Foo
is accessed inside M
and so can call the cctor when M
is jitted, and then skip generating each check inside M
.
The jitter is smart enough to do the right thing when executing the cctor at jit time; it does not execute the cctors in the "wrong" order as you describe, because the people who wrote the jitter were sane people who were just trying to make your code faster.
How am I supposed to write type initializers so I don't get bitten by it?
You should assume that the authors of the conforming implementation are sane.
If for whatever reason you cannot assume that: you can put all the static field initializers you care about into a static constructor. The C# compiler will not allow BeforeFieldInit
semantics on types with static constructors.