为什么C#允许您“抛出null”?


85

有人问,在编写一些特别复杂的异常处理代码时,您是否需要确保异常对象不为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
}

8
您确定没有在自己的抛出之上抛出.NET异常(空引用)吗?
micahtan'2

4
您可能认为编译器至少会提出有关尝试直接“抛出null”的警告。
安迪·怀特2010年

不,我不确定。该框架很可能会尝试对我的对象做某事,当它为null时,框架会引发“空引用异常” ..但最终,我想确保“ ex”永远不会为空
quip

1
@Andy:警告对于语言中的其他情况可能是有意义的,但对于throw,其目的是首先引发异常的语句,警告并没有多大价值。
Mehrdad Afshari'Feb 3'10

@Mehrdad-是的,但是我怀疑是否有任何开发人员会故意“抛出null”,并希望看到NullReferenceException。也许应该是完全构建错误,例如if语句中的不正确=比较。
安迪·怀特2010年

Answers:


96

因为语言规范期望在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,粗体语句为false。因此,尽管纯粹基于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


我认为最好的办法是禁止诸如的明确短语throw null;
FrustratedWithFormsDesigner 2010年

7
实际上,完全有可能(在CLR中)抛出一个不继承自System.Exception的对象。据我所知,您无法在C#中完成此操作,但是可以通过IL,C ++ / CLR等完成。有关更多信息,请参见msdn.microsoft.com/zh-cn/library/ms404228.aspx
technophile 2010年

@technophile:是的。我说的是这里的语言。在C#中,不可能引发其他类型的异常,但是可以使用泛型catch { }子句来捕获它。
Mehrdad Afshari'2

1
尽管对于编译器拒绝接受throw其参数可能为null的值没有意义,但这并不意味着throw null必须合法。编译器可以坚持认为throw参数具有可识别的类类型。像这样的表达式(System.InvalidOperationException)null在编译时应该是有效的(执行它会导致NullReferenceException),但这并不意味着未类型化就null可以接受。
supercat 2012年

@supercat:是的,但是在这种情况下,null隐式地键入为Exception(因为在需要Exception的上下文中使用它)。null在运行时无效,因此将抛出NullReferenceException(如Anon的回答所述)。引发的异常不是“ throw”语句中的异常。
约翰·兰贝

29

显然,您可以抛出null,但是在某些地方它仍然变成异常。

尝试抛出null对象会导致(完全不相关)Null Reference Exception。

问为什么要扔东西null就像问为什么要这样做:

object o = null;
o.ToString();

5

这里拍摄:

如果在C#代码中使用此表达式,它将抛出NullReferenceException。这是因为throw语句需要Exception类型的对象作为其单个参数。但是在我的示例中,该对象为null。


5

虽然可能无法在C#中引发null,因为throw将检测到该错误并将其转换为NullReferenceException,但有可能会收到null ...我正巧正在接收该消息,这导致我的捕获(期望“ ex”为null)会遇到null引用异常,这会导致我的应用程序死亡(因为这是最后一个陷阱)。

因此,虽然我们不能从C#中抛出null,但netherworld可以抛出null,因此,最外面的catch(ex ex)最好准备好接收它。仅供参考。


3
有趣。您能发布一些代码,还是躲到下面的深处呢?
Peter Lillevold

我无法告诉您这是否是CLR错误...很难追踪.NET胆量为null的原因,以及为什么给定的调用堆栈或任何其他线索无法得到Exception的原因。我只知道最外面的catch现在在使用它之前先检查null Exception参数。
布莱恩·肯尼迪

3
我怀疑这是CLR错误,或者是由于错误的无法验证的代码。正确的CIL将只抛出一个非null对象或null(这将成为NullReferenceException)。
黛咪

我也正在使用返回空异常的服务。您是否知道如何引发null异常?
themiDdlest

2

我认为也许不能-当您尝试抛出null时,它就不能这样做,因此它会在错误情况下执行应做的事情,即抛出null引用异常。因此,您实际上并没有抛出null,而是没有抛出null,这会导致抛出异常。


2

试图回答“ ..非常感谢'ex'不为空,但是可能吗?”:

因为我们可以说不能抛出为空的异常,所以catch子句也永远不必捕获为空的异常。因此,ex永远不会为空。

我现在看到实际上已经过这个问题。


@Brian Kennedy在上面的评论中告诉我们,我们可以捕获null。
ProfK 2012年

@ Tvde1-我认为这里的区别是,可以执行throw null。但这不会引发null的异常。相反,由于throw null尝试在null引用上调用方法,因此随后迫使运行时抛出的非null实例NullReferenceException
Peter Lillevold

1

请记住,异常包括有关引发异常的详细信息。看到构造函数不知道将其扔到何处,那么throw方法将这些详细信息在throw的位置注入到对象中才有意义。换句话说,CLR尝试将数据注入null,这将触发NullReferenceException。

不知道这是否正在发生,但可以解释这种现象。

假设这是真的(我想不出将ex设为null的最佳方法就是抛出null;),这意味着ex不可能为null。


0

在较旧的C#中:

考虑以下语法:

public void Add<T> ( T item ) => throw (hashSet.Add ( item ) ? null : new Exception ( "The item already exists" ));

我认为它比这更短:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}

这告诉我们什么?
出生

顺便说一句,我不确定您对short的定义是什么,但是您的第二个示例包含的字符较少。
出生

@bornfromanegg no 1st是内联的,因此肯定要短一些。我要说的是,您可能会根据情况抛出错误。传统上,您会喜欢第二个示例,但是由于“ throw null”不会抛出任何内容,因此您可以内联第二个示例以使其看起来像第一个示例。此外,第一个示例的字符数少于第二个字符的8个字符
Arutyun Enfendzhyan

“ throw null”抛出NullReference异常。这意味着您的第一个示例将始终引发异常。
出生

是的,不幸的是现在。但这不是以前
Arutyun Enfendzhyan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.