为什么在C#中使用(null)是一个有效的情况?

13

请问为什么下面展示的代码在C#中是合法的,并且可以执行对Console.WriteLine的调用?

using (null) 
{
   Console.WriteLine ("something is here")
}

它编译成(最终块显示)。正如你所看到的,编译器决定不执行Dispose()方法并跳转到endfinally指令。

IL_0013:  ldnull
IL_0014:  ceq
IL_0016:  stloc.1
IL_0017:  ldloc.1
IL_0018:  brtrue.s   IL_0021 // branches here and decide not to execute Dispose()
IL_001a:  ldnull
IL_001b:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
IL_0020:  nop
IL_0021:  endfinally

然而,如果我运行以下代码,它将会产生一个 NullReferenceException(这是预料之中的):

((IDisposable)null).Dispose();
IL_0023:  ldnull
IL_0024:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
为什么第一个版本编译通过?为什么编译器决定不执行“Dispose()”?是否还有其他情况下编译器可能在using块中决定不调用Dispose()

7
我现在非常怀疑这篇帖子,因为埃里克最近的博客...这是真正的问题吗? - Marc Gravell
这不是已经被问过了吗? https://dev59.com/83E85IYBdhLWcg3w9onw - Vladislav Rastrusny
是的,对我来说似乎有点牵强。 - Ritch Melton
3个回答

17

语言规范明确指出(8.13),如有必要,将测试捕获的值是否为null,即finally实际上是这样(关于非空类型的注意事项有限制)。

if(tmp != null) tmp.Dispose();

我经常利用这一点,对于那些可能为null的事情,但当它们不是null时:需要进行处理。实际上,这是一个有用的场景(手动枚举 IEnumerable):

IEnumerable blah = ...; // note non-generic version
IEnumerator iter = blah.GetEnumerator();
using(iter as IDisposable)
{
    // loop
}

作为非泛型版本的IEnumerator不一定是IDisposable,但当它是时,应该被处理。


1
感谢您分享使用 as IDisposable 的技巧。这正是我一直在寻找的。 - Andrey Taptunov
这个太聪明了,不符合我的口味 :) - mafu

1

我认为这是using(some_expression)更一般情况的自然结果,其中some_expression允许计算为null

这需要一个特殊规则来区分这种情况和更一般的情况。


0

在您提供的文档中哪里有提到这个?在该页面上搜索“null”没有返回任何结果。 - Cody Gray
1
在备注部分。如果(font1!= null) ((IDisposable) font1) .Dispose(); - Anuraj
using语句被翻译成三个部分:获取、使用和处理。资源的使用隐式地包含在包括finally子句的try语句中。这个finally子句会处理资源。如果获取到了一个空的资源,则不会调用Dispose,也不会抛出异常。 - boro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接