Servé Laurijssen Servé Laurijssen - 1 month ago 6
C# Question

strange exception behaviour...why?

I've made a class inspired by C++'s

std::numeric_limits
for getting min and max value of a type.
It fills two static read only members using reflection to read out the
MaxValue
and
MinValue
properties of the type. It throws an exception if
T
does not have that property.

public class Limits<T>
{
public static readonly T MaxValue = Read("MaxValue");
public static readonly T MinValue = Read("MinValue");

private static T Read(string name)
{
FieldInfo field = typeof(T).GetField(name, BindingFlags.Public | BindingFlags.Static);

if (field == null)
{
throw new ArgumentException("No " + name + " property in " + typeof(T).Name);
}

return (T)field.GetValue(null);
}
}


Now when stepping through the following program Im seeing some strange behaviour.

try
{
Console.WriteLine(Limits<int>.MaxValue);
Console.WriteLine("1");
Console.WriteLine(Limits<object>.MaxValue);
}
catch
{
Console.WriteLine("2");
}


There's a breakpoint on reading the
MaxValue
property. When stepping through
Limits<int>
the breakpoint is hit and the property is read. Then before executing
WriteLine("1")
the breakpoint is hit again for reading
Limits<object>
. This throws an exception since
object
does not have
MaxValue
so one would expect the exception being caught in Main. But that doesn't happen,
WriteLine("1")
is executed and only then the exception is caught....why is this? Does the CLR store the exception until the actual line is executed?

Answer

From the C# language spec on static field initialization:

If a static constructor (Section 10.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.

So that means:

  • The user has no direct control about when this initialization process is run.
  • It is guaranteed to be run once and before the field is used.

If an exception is thrown in this process, the type becomes unusable for the rest of the life of the AppDomain, and every time you try to use that type you will get a TypeInitializationException thrown (with the inner exception being the original exception) Check the msdn on static constructors. For these purposes, Limits<int> and Limits<object> are considered different types, so you could still use Limits<int>.

That´s why you get the exception when you try to get Limits<object>.MaxValue, because the initialization code was called by the clr for you and the exception stored so it can be thrown as TypeInitializationException every time you use it.

It is also important to notice that all static fields will be initialized before the type is used for the first time. So you could add the following static property to your static class:

public static T Default = default(T);

And then change your test program as follows:

static void Main(string[] args)
{
    for (int x = 0; x < 3; x++)
    {
        try
        {
            Console.WriteLine(Limits<int>.Default);
            Console.WriteLine("1");
            Console.WriteLine(Limits<object>.Default);
        }
        catch (TypeInitializationException e)
        {
            Console.WriteLine("TypeInitializationException: " + e.Message);
        }
    }
    Console.ReadKey();
}

You are not using directly the MaxValue static fields, but because you are using the type (by accessing Default) all the static fields are still being initialized prior to first usage of the type. You will also notice that you will get the same exception 3 times, always after Limits.Default and "1" have been written.