奇怪的异常行为,为什么

本文关键字:为什么 异常 | 更新日期: 2023-09-27 18:17:46

我制作了一个受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是否存储异常直到实际行执行?

奇怪的异常行为,为什么

来自c#语言规范的静态字段初始化:

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

意思是:

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

如果在此过程中抛出异常,则该类型在AppDomain的剩余生命周期内变得不可用,并且每次尝试使用该类型时,您都会得到一个TypeInitializationException抛出(内部异常是原始异常)。出于这些目的,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静态字段,但是由于您正在使用该类型(通过访问Default),因此在首次使用该类型之前,所有静态字段仍然被初始化。你也会注意到你会得到同样的异常3次,总是在极限之后。Default和"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在触发静态构造函数调用时抛出。

表示是,异常被保留并抛出在导致初始化的代码行。这是有意义的,否则你会有一个异常抛出在一个'随机'的时间。