IDisposable:在finally {}中需要检查null吗?

11

大多数在网上找到的示例,当明确不使用 "using" 时,模式看起来像这样:

  SqlConnection c = new SqlConnection(@"...");
  try {
    c.Open();
  ...
  } finally {
    if (c != null) //<== check for null
      c.Dispose();
  }

如果你使用 "using" 并查看生成的 IL 代码,你可以看到它会生成对 null 的检查。

L_0024: ldloc.1 
L_0025: ldnull 
L_0026: ceq 
L_0028: stloc.s CS$4$0000
L_002a: ldloc.s CS$4$0000
L_002c: brtrue.s L_0035
L_002e: ldloc.1 
L_002f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0034: nop 
L_0035: endfinally 

我理解IL代码被转译为检查空值的原因(不知道您在using块内部做了什么),但如果您正在使用try..finally并且您完全控制如何在try..finally块内使用IDisposable对象,那么您真的需要检查空值吗? 如果需要,为什么?


4
为了防止空引用异常,假设您在使用/try块内将变量设置为null,这样做是为了保护吗? - Gishu
that's what I'm thinking - BlackTigerX
好问题..让我拿出我的代码草稿本并学习了新东西。 - Gishu
4个回答

12

"using"语句可以使用除构造函数之外的调用来初始化变量。例如:

using (Foo f = GetFoo())
{
    ...
}

在这里,f 可能很容易为 null - 然而构造函数调用永远不会返回 null。这就是为什么 using 语句检查 null 值的原因。这与块内部的内容无关,因为 using 语句保留了原始初始值。如果您编写:

Stream s;
using (s = File.OpenRead("foo.txt"))
{
    s = null;
}
如果变量是在using语句的初始化部分中声明的,则仍然会释放流。(无论如何,如果变量在此处被声明,它也是只读的。)
在您的情况下,由于您知道在进入try块之前不为空,因此在finally块中不需要检查null,除非您在块内重新为其赋值(但我真诚地希望您不会这样做!)。
现在,根据您当前的代码,c赋值和进入try块之间可能会抛出异步异常的微小风险-但是完全避免这种竞争条件很难,因为在构造函数完成之后但在将值分配给 c 之前也可能出现异步异常。我建议大多数开发人员不必担心这种情况-异步异常倾向于足够“严重”,以至于它们会将进程崩溃。
但是,你有没有想过为什么不使用using语句呢?老实说,我现在很少编写自己的finally块...
1.请参阅Marc的答案并哭泣。尽管通常并不相关。

是的,在这种特定情况下有理由使用using语句,通常我会使用using,并且不必担心它。 - BlackTigerX
我的特定情况要么创建一个 SqlConnection 实例,要么获取一个现有实例并使用它,在最后需要处理一下以防它不是“现有实例”。 - BlackTigerX
在您的情况下,由于您知道在进入try块之前c是非空的,因此在finally块中不需要检查null,除非您正在块内重新分配其值(我真诚地希望您不会这样做!)。 这回答了我的问题,谢谢! - BlackTigerX
(重新编辑)哈哈,但是记录一下,没有理智的代码应该担心这个问题;-p - Marc Gravell

5

在你的例子中,检查 null 是不必要的,因为在显式构造之后,c 不能为 null。

在下面的例子中(类似于 using 语句生成的代码),当然需要检查 null:

SqlConnection c = null; 
  try { 
    c = new SqlConnection(@"..."); 
    c.Open(); 
  ... 
  } finally { 
    if (c != null) //<== check for null 
      c.Dispose(); 
  } 

此外,在以下情况下需要检查 null,因为您不能确定 CreateConnection 不会返回 null:
SqlConnection c = CreateConnection(...); 
  try { 
    c.Open(); 
  ... 
  } finally { 
    if (c != null) //<== check for null 
      c.Dispose(); 
  } 

你不知道他代码中的“...”部分是否是“c = null;”,因此检查是必要的。 - Matt Hamilton
@Matt - 是的,但他可能知道那里有什么。 - Joe
我的感觉是,因为他从代码示例中阅读这个,他们可能也有“...”,而作者们不想冒任何风险。 - Matt Hamilton
我只是使用...来消除连接字符串的噪音,这并不必要来说明重点。 - BlackTigerX

2

Jon已经在这里提到了主要的观点...但是一个随机的小知识:你的构造函数可能会返回null。这不是一个常见的情况,当然也不是using这么做的真正原因,你真的不应该为此折磨你的代码 - 但是它可能发生(寻找MyFunnyProxy)。


如果无法创建SqlConnection的实例,为什么不只是抛出异常呢? - BlackTigerX
请查看Jon的评论:“而构造函数调用永远不会返回null” - BlackTigerX
@BlackTigerX,@Jon,这只是一个“FYI”,不要因为这个疯狂的边缘情况而折磨代码。 - Marc Gravell

0

有趣..我正在使用VS2010,我发现通过我的自定义代码片段(R :) - 使用块非常好用。

  • 在using块中,您不能将null(或任何其他内容)分配给块变量。这会导致{{link1:编译器错误CS1656}}。
  • 接下来通过返回null的工厂方法分配块变量。在这种情况下,一个空的using块不会调用Dispose。(显然,如果尝试使用块变量,则会得到NullReferenceException)
  • 接下来,在try块中没有规则,您可以将null分配给变量。因此,在finally块中强制进行Dispose之前必须进行null检查。

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