【问题标题】:Exception before MainMain 之前的异常
【发布时间】:2018-05-09 10:28:04
【问题描述】:

我使用以下代码创建了应用程序(仅用于研究目的):

using System;
using System.CodeDom;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;

namespace HelloWorld
{
    public class Program
    {
        static Program()
        {
            throw new Exception("Here we are");
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Hello, world!");
        }
    }
}

我希望在调用Main() 方法之前在这里会出现TypeInitializationException 异常,因为静态ctor 只能在第一次调用此类的任何成员之前调用一次。 因此,在这种情况下,CLR 必须为 Program 类调用静态 ctor,然后必须调用 Main() 方法。 但这里有一件奇怪的事情:这个异常是从Main() 抛出的。但为什么?我们应该有另一个地方的异常,因为 Main 不能被调用。

这里是异常信息:

未处理的异常:System.TypeInitializationException:“HelloWorld.Program”的类型初始化程序引发了异常。 ---> System.Exception:我们在这里 在 D:\research\HelloWorld\Program.cs:line 13 中的 HelloWorld.Program..cctor() --- 内部异常堆栈跟踪结束 --- 在 HelloWorld.Program.Main(String[] args)

更新:

我有这条消息。

很遗憾,在此异常之后我无法调试应用程序。

【问题讨论】:

  • 只是一个猜测,但我认为它只在需要时调用构造函数。第一次需要该类是在调用 Main 时,所以我认为这是构造函数调用的触发器,也许这就是它说在 Main 上引发异常的原因。
  • 您能否编辑您的问题以包含您的 Program.cs 文件的完整源代码?您的代码块只有 12 行,但错误消息提到第 13 行。不要编辑错误消息或堆栈跟踪。
  • 如果您在抛出异常后尝试调试,则会收到有趣的消息。 Your app has entered a break state, but no code is currently executing that is supported by the selected debug engine (e.g. only native runtime code is executing)
  • @Progman 行号是cs文件中的实际行号。所以如果你放 100 个空行并在第 100 行抛出异常,它将是第 100 行。它在这里并不重要。
  • @M.kazemAkhgary,在抛出此异常之前的构造函数中,我在调用堆栈中只看到了我的 ctor 和 [ExternalCode]。但是,如果我用静态 ctor 定义另一个类,我会看到这样的调用堆栈: 3. static ctor(); 2.【外码】; 1. 主要()。 CLR 有调用静态 ctor 的内部机制,但我不知道为什么它在 Main() 实际上还没有执行时将 Main() 设置为调用堆栈的头部。

标签: c# exception


【解决方案1】:

它不特定于 Main 入口点方法。考虑这段代码:

public class Program
{        
    static void Main(string[] args) {
        MyClass.Test();
    }
}

static class MyClass {
    static MyClass() {            
        throw new Exception("here we are");
    }

    public static void Test() {
        Console.WriteLine("test");
    }
}

如果你运行它,异常堆栈跟踪将是:

未处理的异常:System.TypeInitializationException:类型 为“ConsoleApp2.MyClass”初始化引发了异常。 ---> System.Exception:我们在这里

在 ConsoleApp2.MyClass..cctor()

---内部异常堆栈跟踪结束---

在 ConsoleApp2.MyClass.Test()

在 ConsoleApp2.Program.Main(String[] args)

因此,与入口点类的静态构造函数中的异常情况相同。

如果您使用 WinDbg 运行该应用程序并在引发异常时运行 !clrstack,您将看到:

000000af568fdc98 00007ffd54659d98 [GCFrame: 000000af568fdc98]

000000af568fde58 00007ffd54659d98 [GCFrame: 000000af568fde58]

000000af568fea00 00007ffd54659d98 [PrestubMethodFrame: 000000af568fea00] ConsoleApp2.MyClass.Test()

000000af568febe0 00007ffce37704a2 ConsoleApp2.Program.Main(System.String[])

000000af568fee40 00007ffd42d86793 [GCFrame: 000000af568fee40]

在堆栈窗口中你可以看到:

clr!MethodTable::DoRunClassInitThrowing + 0x599

clr!MethodTable::CheckRunClassInitThrowing + 0xbb

clr!MethodDesc::DoPrestub + 0xd1d

何时调用静态类型构造函数由 JIT 编译器决定。当类型显式定义静态构造函数时,C# 编译器不会用BeforeFieldInit 标志标记类型。允许初始化具有该标志的类型 以“轻松”的方式,在访问其成员之前的某个时间点(或至少在访问其静态字段之前)。因此,对于他们来说,JIT 可能会在您访问它们之前的任何时候发出静态构造函数调用,甚至在您的应用程序启动时也是如此。没有该标志的类型以“精确”方式初始化 - 当第一次访问某个成员时,JIT 将为它们发出对静态构造函数的调用。

JIT 即时编译方法。如果方法尚未编译为本机代码 - 方法将指向“存根”。此存根包含运行 JIT、检查方法、将其编译为本机代码的代码,然后将方法指针从存根更改为已编译的本机代码,以便在下次调用此方法流时直接转到已编译的代码,而无需存根。

正如您从 WinDbg 输出中看到的 - 当异常发生时,我们处于 MyClass.Test() 方法的存根中。此时,在将MyClass.Test() 编译为本机代码时,JIT 看到静态构造函数尚未运行,发出代码调用静态构造函数并编译MyClass.Test()。但是这一切都发生在方法被技术调用之后(但在该方法的任何代码实际执行之前),所以它出现在调用堆栈上。

我使用了不同的代码来说明它与Main 无关,但是您问题中的代码情况是一样的:

0000007ba0b3dba8 00007ffbfbb89d98 [GCFrame: 0000007ba0b3dba8]

0000007ba0b3dd68 00007ffbfbb89d98 [GCFrame: 0000007ba0b3dd68]

0000007ba0b3e910 00007ffbfbb89d98 [PrestubMethodFrame: 0000007ba0b3e910] ConsoleApp2.Program.Main(System.String[])

0000007ba0b3ed20 00007ffbfbb89d98 [GCFrame: 0000007ba0b3ed20]

【讨论】:

    【解决方案2】:

    我在哪里执行代码,测试类有静态构造函数,会抛出错误,

     static void Main(string[] args)
            {
    
                Console.WriteLine("Test");
                Thread.Sleep(1000);
                try
                {
                    Test t = new Test();
                }
                catch (Exception ex)
                {
                           Console.WriteLine(ex.ToString());
                }
    
    class Test
        {
            static Test()
            {
                throw new Exception("Errror");
            }        
        }
    

    执行后会给出如下输出

    Test
    System.TypeInitializationException: The type initializer for 'Test.Test' threw a
    n exception. ---> System.Exception: Errror
       at Test.Test..cctor() in \\gbl.ad.hedani.net\home_ap$\prana3$\Documents\Visua
    l Studio 2015\Projects\Test\Test\Program.cs:line 45
       --- End of inner exception stack trace ---
       at Test.Test..ctor()
       at Test.Program.Main(String[] args) in \\gbl.ad.hedani.net\home_ap$\prana3$\D
    ocuments\Visual Studio 2015\Projects\Test\Test\Program.cs:line 20
    

    所以它不会立即抛出错误,但是当尝试访问类的任何成员或方法或任何构造函数时会抛出错误。

    这也是 RunTime 在遇到场景时会发生的情况,其中入口点静态构造函数会引发错误。当 CLR 尝试立即执行 main 方法时,它得到了程序的静态构造函数抛出的异常。


    这是因为static void Main 是应用程序的入口点。 CLR 先找到这个方法(static void Main),然后运行剩下的东西。

    这里的情况可能是在执行 main 之前出现此错误,但 CLR 首先进入入口点,然后抛出异常。由于应用程序没有入口点,它可能不会立即抛出异常。

    【讨论】:

    • @Evk 的回答似乎表明这实际上不是原因......
    • @Chris - 它应该在到达 main 之前抛出异常,对吧?但事实并非如此,在到达入口点后引发异常(我知道在访问任何成员或类的方法之前首先调用微软静态构造函数,但似乎不是 OP 代码的情况)
    • @PranayRana 这不是重点,@Evk 证明这可能发生在 not Main 的静态方法上。
    • @PranayRana:如果你不同意 evk 的分析,那么你应该评论他的回答,告诉他为什么他错了。他似乎已经证明了为什么会抛出异常,而我只是想引起您的注意。我不是说你错了,只是你有两个相互矛盾的答案,他的证据似乎很有说服力。
    • @Chris - 我并不是说他错了......我所说的是当你尝试访问第一个第一个成员或类的方法时发生异常,它不会先进入...... .
    猜你喜欢
    • 2013-03-03
    • 2017-06-24
    • 2011-01-02
    • 2019-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多