Okay, so here's the code of the C# Quiz presented a couple of days ago:
using System;
public abstract class Foo
{
static Foo()
{
Console.WriteLine(".cctor Foo");
}
public static string Instance;
public static void SetInstance(string s)
{
Instance = s;
}
}
public class Bar : Foo
{
static Bar()
{
Console.WriteLine(".cctor Bar");
SetInstance("Hello World!");
}
}
public class Demo
{
public static void Main()
{
Console.WriteLine(Bar.Instance ?? "Oops");
}
}
System;
public abstract class Foo
{
static Foo()
{
Console.WriteLine(".cctor Foo");
}
public static string Instance;
public static void SetInstance(string s)
{
Instance = s;
}
}
public class Bar : Foo
{
static Bar()
{
Console.WriteLine(".cctor Bar");
SetInstance("Hello World!");
}
}
public class Demo
{
public static void Main()
{
Console.WriteLine(Bar.Instance ?? "Oops");
}
} And here's the output:
.cctor Foo
Oops
Maybe you didn't expect this; that's the whole point of this kind of quizzes after all. A typical reasoning goes as follows:
Demo::Main contains Bar::Instance => Bar::.cctor => “.cctor Bar”
Bar::.cctor bevat Foo::SetInstance => Foo::.cctor => “.cctor Foo”
So, we'd expect:
.cctor Bar
.cctor Foo
Hello World!
Be prepared to be shocked (maybe): the Bar.Instance call in Demo::Main has nothing to do with Bar after all. It's just some kind of "syntactical sugar" introduced by C#. After all, Bar derives from Foo, isn't it? However, Bar.Instance is nothing more or less than Foo.Instance. Look at the IL to confirm this:
IL_0000: nop
IL_0001: ldsfld string Foo::Instance
IL_0006: dup
IL_0007: brtrue.s IL_000f
IL_0009: pop
IL_000a: ldstr "Oops"
IL_000f: call void [mscorlib]System.Console::WriteLine(string)
IL_0014: nop
IL_0015: ret
As the matter in fact, the Bar class is out of play. Its static constructor won't ever be called by the runtime when executing this peice of code. You can verify this by executing ildasm.exe /out:demo.il demo.exe, dropping the Bar portion from it, and re-ilasming the stuff using ilasm.exe demo.il. Everything will just work fine. Another experiment is to change IL_0001 into ldsfld string Bar::Instance and re-compile using ilasm. When executing the executable, you'll get a MissingMethodException thrown in your face.
In the CLI (I.8.9.5) one can read:
3. 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.
4. If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e., is triggered by):
- First access to any static or instance field of that type, or
- First invocation of any static, instance, or virtual method of that type
The BeforeFieldInit portion is not relevant in here and refers to a metadata token on the class-level that allows semantic relaxation to increase performance by dropping timing guarantees for execution of the static constructor (as long as it happens before the first access of a static field) as mentioned in 3.
More important is that there's no reason to call the static constructor based on the points mentioned above (more specifically in 4 because BeforeFieldInit won't be set by the C# compiler upon compilation of the code fragment).
There are two solutions to the problem. Solution 1 consists of adding a static field to the Bar class and using it in the Main method. This will cause the runtime to invoke the type initializer (.cctor) in Bar (see CLI I.8.9.5 for the reasons):
using System;
public abstract class Foo
{
static Foo()
{
Console.WriteLine(".cctor Foo");
}
public static string Instance;
public static void SetInstance(string s)
{
Instance = s;
}
}
public class Bar : Foo
{
internal static int junk = 0;
static Bar()
{
Console.WriteLine(".cctor Bar");
SetInstance("Hello World!");
}
}
public class Demo
{
public static void Main()
{
int junk = Bar.junk;
Console.WriteLine(Bar.Instance ?? "Oops");
}
}
System;
public abstract class Foo
{
static Foo()
{
Console.WriteLine(".cctor Foo");
}
public static string Instance;
public static void SetInstance(string s)
{
Instance = s;
}
}
public class Bar : Foo
{
internal static int junk = 0;
static Bar()
{
Console.WriteLine(".cctor Bar");
SetInstance("Hello World!");
}
}
public class Demo
{
public static void Main()
{
int junk = Bar.junk;
Console.WriteLine(Bar.Instance ?? "Oops");
}
} Solution 2 is more sexy and uses a RuntimeHelper to call the static constructor, as shown below:
using System;
public abstract class Foo
{
static Foo()
{
Console.WriteLine(".cctor Foo");
}
public static string Instance;
public static void SetInstance(string s)
{
Instance = s;
}
}
public class Bar : Foo
{
static Bar()
{
Console.WriteLine(".cctor Bar");
SetInstance("Hello World!");
}
}
public class Demo
{
public static void Main()
{
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Bar).TypeHandle);
Console.WriteLine(Bar.Instance ?? "Oops");
}
}
System;
public abstract class Foo
{
static Foo()
{
Console.WriteLine(".cctor Foo");
}
public static string Instance;
public static void SetInstance(string s)
{
Instance = s;
}
}
public class Bar : Foo
{
static Bar()
{
Console.WriteLine(".cctor Bar");
SetInstance("Hello World!");
}
}
public class Demo
{
public static void Main()
{
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Bar).TypeHandle);
Console.WriteLine(Bar.Instance ?? "Oops");
}
} RuntimeHelpers.RunClassConstructor does ask the runtime to call the static constructor anyway, regardless of the reasons specified in the CLI. This technique is employed by the Delphi .NET compilers to ensure deterministic unit initialization order.
The conclusion? Static constructors (or type initializers or .cctors, whatever name you like to give it) are tricky things. Don't get fooled by them.
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks