class One
{
private static string me = "Am I different?";
}
class Two
{
private static string me;
static Two() { me = "Am I different?"; }
}
There's a certain confusion around static constructors. Why should we use them instead of static initializers? Any difference between the two samples above?
The rule of thumb is simple. If you want to control the time when CLR would initialize static fields, add the static constructor. If you don't care - skip it and use static initializers.
Type construction.
Basically, type construction comes in two flavors:
- relaxed (by default, for types without static constructors)
It guarantees that the static constructor will be run "at some point" before the first access to a static field. So it could be run much earlier if the CLR would decide so.
- precise (for types with explicit static constructors - even empty ones).
It guarantees that the static ctor gets called exactly before the first access to any method or field of the class. Not earlier.
Relaxed semantics gives more power to the CLR which could make some optimizations itself. On the other hand, precise semantics gives this power to you, so you can gain some performance in scenarios like the one below[1]
public class ExpensiveResource
{
static ExpensiveResource() { } //use the 'precise' .cctor semantics
private ExpensiveResource() { }
public static readonly ExpensiveResource Instance = CreateMe();
private static ExpensiveResource CreateMe()
{
Console.WriteLine("Ouch! Long-running expensive method was just called.");
return new ExpensiveResource();
}
...
}
Because of the explicit static constructor, JIT compiler would initialize static fields only if they're actually used, not just mentioned in the method:
public void Foo(bool run)
{
if (run) {
ExpensiveResource.Instance.Boo();
}
...
}
(Complete sample here.) If we'd pass false, we will not see this "Ouch!..." in the output: with "precise" semantics, if the field is not used it won't be initialized. However, if we remove the static constructor, the CLR will use the relaxed mode and can (on my machine it does) create an instance of ExpensiveResource regardless of whether run is true or false.
More subtleties.
Consider this example inspired by the CLI spec:
class A
{
public static readonly A a;
public static readonly B b;
static A()
{
b = new B();
a = B.a;
}
}
class B
{
public static readonly A a;
public static readonly B b;
static B()
{
a = new A();
b = A.b;
}
}
Any attempt to reference the above static fields would cause a problem, since the static ctor of each type requires the other one to be finished first. Sounds like a deadlock, eh? So the CLR has to be a bit weaker about type initializers: "Type initializer will have started to run, but it need not have completed."
As the spec states further, similar yet more complex problems arise in a multi-threaded environment. In any case, the CLR will try to avoid deadlocks as much as possible, and instead will allow you to see incomplete results of the initialization: null references.
So, no wonder this test:
public static void Main()
{
Console.WriteLine("A.a=" + ((object) A.a ?? "null"));
Console.WriteLine("A.b=" + ((object) A.b ?? "null"));
Console.WriteLine("B.a=" + ((object) B.a ?? "null"));
Console.WriteLine("B.b=" + ((object) B.b ?? "null"));
}
...brings these results:
A.a=Program.A
A.b=Program.B
B.a=Program.A
B.b=null
Press any key to continue . . .
Footnotes.
-
Relaxed semantics is also called "BeforeFieldInit" based on the name of the corresponding CLR type attribute. Precise semantics is called "exact" in Rotor source code, but not sure I've seen this elsewhere.