我们知道.NET运行时在抛出异常时不是很有用,因为它只显示通用消息而不指示任何变量或参数名称。但它能以不同的方式做到吗?
例如在这种情况下:
class foo
{
public void bar() {}
}
foo f = null;
f.var(); // NullReferenceException
但是从C#6开始,如果我们使用新的?
运算符,编译器就可以生成不同的代码,因此它可以检查f
是否为空;
f?.var();
无法使用类似的空检查来包装调用,例如使用?
并获取f
的名称并创建一个类似
附加信息:对象引用“Foo类型的f”未设置为对象的实例。
是否可以将它用于其他的exeption类型并在那里放置有意义的信息,或者太昂贵无论这意味着什么?
答案 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模式的编辑也不会那样做。
我希望我能提供帮助。