在编写一些特别复杂的异常处理代码时,有人问,您是否需要确保您的异常对象不为null?我说,当然不是,但是后来决定尝试一下。显然,您可以抛出null,但是在某些地方它仍会变成异常。
为什么允许这样做?
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
的表达式(因此null
在该上下文中是有效的),并且不将该表达式限制为非null。通常,无法检测到该表达式的值是否为null
。它必须解决停止问题。无论如何,运行时都必须处理null
情况。参见:
Exception ex = null;
if (conditionThatDependsOnSomeInput)
ex = new Exception();
throw ex;
当然,他们可以使抛出null
文字的特定情况无效,但这无济于事,为什么要浪费规格空间并降低一致性却无济于事?
免责声明(在我被Eric Lippert打耳光之前):这是关于本设计决策背后原因的猜测。当然,我还没有参加设计会议;)
第二个问题的答案,是否在catch子句中捕获的表达式变量永远可以为null:尽管C#规范未提及其他语言是否会导致null
异常传播,但它确实定义了异常的方式传播:
catch子句,如果有的话,将按照出现的顺序进行检查,以找到合适的异常处理程序。第一个catch子句指定异常类型或异常类型的基本类型
被视为匹配项。常规catch子句被认为是任何异常类型的匹配项。 [...]对于null
,粗体语句为假。因此,虽然纯粹基于C#规范所说的,我们不能说底层运行时永远不会抛出空值,但是我们可以确保即使是这种情况,也只能由通用catch {}
处理条款。
对于CLI上的C#实现,我们可以参考ECMA 335规范。该文档定义了CLI内部抛出的所有异常(没有一个是null
),并且提到了throw
指令抛出了用户定义的异常对象。该指令的说明实际上与C#throw
语句相同(除了它不将对象的类型限制为System.Exception
):
说明:
throw
指令将异常对象(类型O
)抛出到堆栈上并清空堆栈。有关异常机制的详细信息,请参见分区I。[注意:尽管CLI允许抛出任何对象,但是CLS描述了特定的异常类,该类将用于语言互操作性。尾注]例外:
如果
System.NullReferenceException
为obj
,则抛出[null
。正确性:
正确的CIL确保对象始终是
null
或对象引用(即类型O
)。我相信这些足以断定所捕获的异常永远不会是
null
。
显然,您可以抛出null,但在某些地方仍会变成异常。
由于我们可以说不能抛出为空的异常,所以catch子句也永远不必捕获为空的异常。因此,ex永远不会为空。
不确定这是否正在发生,但是可以解释这种现象。
public void Add<T> ( T item ) => throw (hashSet.Add ( item ) ? null : new Exception ( "The item already exists" ));