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?
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:
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.