Monday, October 30, 2006 6:00 PM bart

Answers to C# Quiz - Static constructors and type initialization

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.

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

No Comments