为什么C#允许你“抛出null”?
在编写一些特别复杂的exception处理代码的时候,有人问,你不需要确保你的exception对象不为空? 我当然不是,但是后来决定尝试。 显然,你可以抛出null,但它仍然变成一个exception的地方。
为什么这是允许的?
throw null;
在这个片段中,幸好“ex”不是null,但可以吗?
try { throw null; } catch (Exception ex) { //can ex ever be null? //thankfully, it isn't null, but is //ex is System.NullReferenceException }
因为语言规范期望在那里有一个System.Exception
types的expression式(因此, null
在该上下文中是有效的)并且不限制这个expression式是非空的。 一般来说,它不可能检测出该expression式的值是否为null
。 这将不得不解决停滞的问题。 无论如何,运行时将必须处理null
情况。 看到:
Exception ex = null; if (conditionThatDependsOnSomeInput) ex = new Exception(); throw ex;
当然,他们可以做出把null
字面无效的特殊情况,但是这并没有什么帮助,那么为什么要浪费规范空间,降低一致性而获得一点好处呢?
免责声明(在我被Eric Lippert拍下之前):这是我自己对这个devise决定背后的推理的猜测。 当然,我没有参加过devise会议;)
第二个问题的答案是,在catch子句中捕获的expression式variables是否可以为空:尽pipeC#规范没有提到其他语言是否可以导致传播一个null
exception,但它确实定义了传播exception的方式:
catch子句(如果有的话)按照外观的顺序进行检查,find一个合适的例外处理程序。 指定exceptiontypes或exceptiontypes的基本types的第一个catch子句被认为是匹配的。 一般的catch子句被认为是任何exceptiontypes的匹配。 […]
对于null
,大胆的陈述是错误的。 所以,虽然纯粹基于C#规范的说法,但我们不能说底层的运行时不会抛出null,我们可以肯定的是,即使是这样,它也只能被genericscatch {}
处理。条款。
对于CLI中的C#实现,我们可以参考ECMA 335规范。 该文档定义了CLI在内部抛出的所有exception(均不为null
),并提到用户定义的exception对象由throw
指令throw
。 该指令的描述实际上与C# throw
语句相同(除了它不限制对象的types为System.Exception
):
描述:
throw
指令将exception对象(typesO
)抛出到堆栈并清空堆栈。 有关例外机制的细节,请参阅分区I.
[注意:虽然CLI允许抛出任何对象,但CLS描述了一个特定的exception类,它将用于语言的互操作性。 尾注]例外:
如果
obj
为null
则引发System.NullReferenceException
。正确性:
正确的CIL确保对象总是为
null
或对象引用(即,typesO
)。
我相信这些足以得出结论,捕获的exception不会为null
。
显然,你可以抛出null,但它仍然变成一个exception的地方。
试图抛出一个null
对象导致一个(完全不相关的)空引用exception。
问为什么你被允许抛出null
就像问你为什么被允许这样做:
object o = null; o.ToString();
虽然可能无法在C#中抛出null,因为throw会检测到它,并将其变为NullReferenceException,但可能会收到null …我碰巧正在接收它,导致我的catch(这不是期待“前”为空)体验空引用exception,然后导致我的应用程序死亡(因为这是最后一个捕获)。
所以,虽然我们不能从C#中抛出null,但是下层可以抛出null,所以你最外层的catch(Exception ex)更好地准备好接收它。 只是供参考。
采取从这里 :
如果你在你的C#代码中使用这个expression式,它将抛出一个NullReferenceExceptionexception。 这是因为throw语句需要一个Exceptiontypes的对象作为其单个参数。 但是这个对象在我的例子中是空的。
我认为也许你不能 – 当你试图抛出null时,它不能,所以它在错误情况下应该如此,这是抛出一个空引用exception。 所以你实际上并没有抛出null,你没有抛出null,导致抛出。
试图回答“..感谢”前“不是空的,但它可以永远吗?”:
由于我们可以说无法抛出空的exception,所以catch子句也永远不会捕捉到空的exception。 因此,ex不能为空。
我现在看到这个问题其实已经被问到了 。
请记住,exception包含有关引发exception的详细信息。 看到构造函数不知道它在哪里被抛出,那么只有在抛出点的时候抛出方法将这些细节注入到对象中才有意义。 换句话说,CLR正试图将数据注入到null中,这会触发NullReferenceException。
不知道这是不是正在发生的事情,但它解释了这种现象。
假设这是真的(我不能认为一个更好的方式来得到为空比抛空;),这将意味着EX不可能为空。