IDisposable:是否有必要在最后{}检查null?

Bla*_*erX 11 .net c# coding-style idisposable

在明确不使用 "使用" 时在Web上找到的大多数示例中,模式看起来像:

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

如果您确实使用"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 
Run Code Online (Sandbox Code Playgroud)

我理解为什么IL被翻译为检查null(不知道你在using块中做了什么),但是如果你正在使用try..finally并且你可以完全控制IDisposable对象如何在try中使用..最后阻止,你真的需要检查null吗?如果是这样,为什么?

Jon*_*eet 12

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

using (Foo f = GetFoo())
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

这里f很容易被空-而一个构造函数调用绝不能1个返回NULL.这就是using语句检查无效的原因.这与块本身内部的内容无关,因为该using语句保留了原始初始值.如果你写:

Stream s;
using (s = File.OpenRead("foo.txt"))
{
    s = null;
}
Run Code Online (Sandbox Code Playgroud)

然后流仍然会被处理掉.(如果变量声明中的初始化部分using的语句,它的只读反正.)

在你的情况下,如你所知,c在你输入try块之前它是非空的,你不需要在块中检查null,finally除非你重新分配它的值(我真诚地希望你不是!)块.

现在,与您现有的代码中有轻微的风险,即异步异常可以在分配之间被掀翻c,并进入try块-但很难避免这种竞争状态完全,因为有同样可以是异步异常的构造函数后完成但在分配值之前c.我建议大多数开发人员不必担心这类事情 - 异步异常往往足够"难",他们无论如何都会打倒这个过程.

你有什么理由不想只使用using语句吗?说实话,finally这些天我很少写自己的积木......


1见Marc的回答并哭泣.虽然通常不相关.


Joe*_*Joe 5

在您的示例中,检查null是不必要的,因为在显式构造之后c不能为null.

在下面的示例中(类似于using语句生成的内容),检查null当然是必要的:

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

此外,在以下情况下将需要检查null,因为您无法确定CreateConnection不会返回null:

SqlConnection c = CreateConnection(...); 
  try { 
    c.Open(); 
  ... 
  } finally { 
    if (c != null) //<== check for null 
      c.Dispose(); 
  } 
Run Code Online (Sandbox Code Playgroud)