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

时间:2015-11-04 05:40:01

标签: c# .net exception runtime c#-6.0

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

例如在这种情况下:

class foo
{
    public void bar() {}
}

foo f = null;
f.var(); // NullReferenceException

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

f?.var(); 

无法使用类似的空检查来包装调用,例如使用?并获取f的名称并创建一个类似

的执行消息
  

附加信息:对象引用“Foo类型的f”未设置为对象的实例。

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

2 个答案:

答案 0 :(得分:11)

没有。如果可以的话,他们会做到的。布拉德·亚当斯has blogged about it back in 2004

  

发生NullReferenceException是因为像“call”这样的指令   [eax + 44]“或”mov edx,[esi + 24]“导致访问违规。   我们没有保留足够的信息来形成通信   在特定EIP和特定EIP之间的特定寄存器之间是空的   事实上,应用程序中的特定引用为null。   特别是因为EIP可能像写一样在共享助手中   屏障例程或数组助手。在那些情况下,我们必须这样做   执行有限的堆栈步行以获得有效的EIP。

     

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

正如您所看到的,可用于提供有意义的错误消息的信息非常少,而且创建机制也需要花费很多精力。你不太可能在.Net中看到这个功能。

答案 1 :(得分:3)

想想你自己试图在整个代码中实现这样的异常。你要做的是在每次使用之前对每个引用类型变量进行null检查,如果它是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模式的编辑也不会那样做。

我希望我能提供帮助。