奇怪的异常行为......为什么?

时间:2014-08-12 07:01:18

标签: c# .net clr

我创建了一个受C ++ std::numeric_limits启发的课程,用于获取类型的最小值和最大值。 它使用反射填充两个静态只读成员,以读出该类型的MaxValueMinValue属性。如果T没有该属性,它会抛出异常。

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);
    }
}

现在,当我逐步完成以下程序时,我看到了一些奇怪的行为。

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

阅读MaxValue属性时有一个断点。单步执行Limits<int>时,会触发断点并读取属性。然后在执行WriteLine("1")之前,再次点击断点以读取Limits<object>。这引发了一个异常,因为object没有MaxValue所以人们会期望在Main中捕获异常。但这不会发生,WriteLine("1")被执行,只有异常被捕获....为什么会这样? CLR是否存储异常,直到执行实际行?

2 个答案:

答案 0 :(得分:3)

来自static field initialization的C#语言规范:

  

如果类中存在静态构造函数(第10.11节),则在执行该静态构造函数之前立即执行静态字段初始值设定项。否则,静态字段初始值设定项在首次使用该类的静态字段之前的实现相关时间执行。

这意味着:

  • 用户无法直接控制何时运行此初始化过程。
  • 保证在使用该字段之前运行一次。

如果在此过程中抛出异常,则该类型在AppDomain的剩余生命周期内变得不可用,并且每次尝试使用该类型时,您将获得TypeInitializationException抛出(具有内部异常)作为原始异常)检查静态构造函数上的the msdn。出于这些目的,Limits<int>Limits<object>被视为不同的类型,因此您仍然可以使用Limits<int>

这就是为什么当你尝试获取Limits<object>.MaxValue时得到异常的原因,因为clr为你调用了初始化代码并且存储了异常,因此每次都可以抛出TypeInitializationException你用它。

同样重要的是要注意在第一次使用类型之前将初始化所有静态字段。 因此,您可以将以下静态属性添加到静态类:

public static T Default = default(T);

然后按如下方式更改您的测试程序:

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();
}

您没有直接使用MaxValue静态字段,但由于您正在使用该类型(通过访问默认值),所有静态字段在首次使用该类型之前仍在初始化。您还会注意到,在Limits.Default和“1”之后,您将获得相同的异常3次。

答案 1 :(得分:1)

ecma标准说:

17.4.5.1:“如果类中存在静态构造函数(第17.11节),则在执行该静态构造函数之前立即执行静态字段初始值设定项。否则,静态字段初始值设定项将在与实现相关的时间执行在第一次使用该类的静态字段之前。“

即它可以在单独的线程上运行初始化程序。

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

•如果搜索匹配的catch子句到达静态构造函数(第10.11节)或静态字段初始化程序,则会在触发静态构造函数调用的位置抛出System.TypeInitializationException。

说是的,异常被保留并抛出导致初始化的代码行。这是有道理的,否则你会在“随机”时间抛出异常。