新的 .NET 运行时是否会引发更有意义的空引用异常

本文关键字:更有意义 引用 异常 NET 运行时 是否 新的 | 更新日期: 2023-09-27 18:20:36

我们知道 .NET 运行时在引发异常时不是很有用,因为它只显示泛型消息,而不指示任何变量或参数名称。但它能以不同的方式做吗?

例如,在这种情况下:

class foo
{
    public void bar() {}
}
foo f = null;
f.var(); // NullReferenceException

但是从 C# 6 开始,如果我们使用 new ? 运算符,编译器能够生成不同的代码,因此它能够检查f是否为 null;

f?.var(); 

它不能像使用 ? 时那样使用类似的空检查来包装调用并获取f的名称并创建类似 exeption 消息吗

其他信息:对象引用"Foo类型f"未设置为对象的实例。

是否有可能将其也用于其他扩展类型并在那里放置有意义的信息,或者无论这意味着什么,它是否昂贵了?

新的 .NET 运行时是否会引发更有意义的空引用异常

No.如果可以的话,他们早就做到了。布拉德·亚当斯(Brad Adams(早在2004年就在博客上写过它

出现 NullReferenceException 是因为像"调用"这样的指令 [EAX+44]"或"Mov EDX, [ESI+24]"导致访问冲突。 我们没有保留足够的信息来形成通信 在特定 EIP 处的特定寄存器为 NULL 和 申请中的特定引用为空的事实。 特别是因为 EIP 可能位于共享帮助程序中,例如写入 屏障例程或阵列帮助程序。 在这些情况下,我们将不得不 执行有限的堆栈遍历以获得有效的 EIP。

改进此错误消息所需的机制是 巨大。 在可预见的未来,您将不得不依赖调试器, 或在FX代码上明确检查并抛出适当的 NullArgumentException.

如您所见,可用于提供有意义的错误消息的信息很少,并且创建机制也需要付出很多努力。你不太可能在 .Net 中看到此功能。

想想你自己试图在整个代码中实现这样的异常。你要做的是在每次使用之前对每个引用类型变量进行空检查,如果变量名称为 null,则抛出一个有意义的异常。

因此,它很昂贵,而且可能毫无价值,因为如果程序集旁边有 PDB 调试数据库,则异常详细信息包含确切的行号。

请考虑以下代码块:

static void Main(string[] args)
{
    object nullObject = null;
    string nullPointerAccess = nullObject.ToString();
}

编译器为此代码生成的 IL 如下所示(我在生成的 IL 操作旁边放置注释(

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       10 (0xa)
  .maxstack  1
  .locals init ([0] object nullObject) // declare local variable. After this point, the variable has no name. It only has a position.
  IL_0000:  ldnull 
  IL_0001:  stloc.0 // read local variable at position 0 (formerly nullObject)
  IL_0002:  ldloc.0 // load the local variable at position 0 to the stack
  IL_0003:  callvirt   instance string [mscorlib]System.Object::ToString() // call ToString()
  IL_0008:  pop // pop (remove) the return value on top of the stack
  IL_0009:  ret // return
} // end of method Program::Main

您希望编译器将变量名称保留在与现有堆栈平行的另一个堆栈中,并在发生空指针异常时访问该堆栈,这也需要保持两个堆栈同步,以便运行时知道正在引用 nullObject,我认为这不在任何编译器的总体计划中,并且不会持续很长时间。

首先,这将使执行程序所需的CPU周期增加一倍/三倍。即使是 DEBUG 模式编译也不会这样做。

我希望我能有所帮助。