Strange exception behavior ... why?

I created a C ++ inspired class std::numeric_limits

to get the min and max value of a type. It populates two read-only static members using reflection to read properties MaxValue

and MinValue

type. It throws an exception if it T

doesn't have this 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 you step through the following program, I see strange behavior.

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

      

There is a breakpoint here when reading a property MaxValue

. On going over Limits<int>

, the breakpoint is hit and the property is read. Then, before execution WriteLine("1")

, the breakpoint is hit again for reading Limits<object>

. This throws an exception as it object

doesn't MaxValue

, so one would expect the exception to be caught in Main. But this does not happen, it is executed WriteLine("1")

, and only then the exception is caught .... why is this? Does the CLR keep the exception up to the actual line?

+3


source to share


2 answers


From the C # language specification in static field initialization :

If a static constructor exists in the class (section 10.11), the execution of the static field initializers occurs just before the execution of that static constructor. Otherwise, static field initializers are executed at implementation-dependent times prior to the first use of that class's static field.

So this means:

  • The user has no direct control over when this initialization process is started.
  • It is guaranteed that it will run once and before using the field.

If an exception is thrown in this process, that type becomes unusable for the rest of the life of the AppDomain, and every time you try to use that type, you get a throw TypeInitializationException

(with an inner exception being the original exception). Check msdn for static constructors. For these purposes, Limits<int>

and are Limits<object>

considered as different types, so you can still use Limits<int>

.

This is why you get an exception when you try to get Limits<object>.MaxValue

it because the initialization code is called by clr for you and the stored exception, so every time you use it, it can be passed as TypeInitializationException

.



It is also important to note that all static fields will be initialized before the type is used for the first time. Thus, you can add the following static property to your static class:

public static T Default = default(T);

      

And then change the test program like this:

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 MaxValue static fields directly, but since you are using this type (by accessing the default), all static fields are still initialized before using that type for the first time. You will also notice that you will get the same exception 3 times, always after Limits.Default and "1" have been written.

+3


source


The ecma standard says:

17.4.5.1: "If a static constructor exists in a class (§17.11), the execution of the static field initializers occurs just before the execution of that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time before the first use of that class's static field."

those. it can run the initializer on a separate thread.



and http://msdn.microsoft.com/en-us/library/aa664609(v=vs.71).aspx

• If the lookup for a matching catch convention reaches a static constructor (section 10.11) or a static field initializer, then a System.TypeInitializationException is thrown at the point that caused the static constructor to be called.

says yes, the exception is saved and thrown into the line of code that calls the initialization. This makes sense, since otherwise you would have an exception thrown at a "random" time.

+1


source







All Articles