Try/Finally块与调用dispose的区别?

16

这两个代码样例有区别吗?如果没有,那么为什么要使用 using 关键字?

StreamWriter writer;
try {
    writer = new StreamWriter(...)
    writer.blahblah();

} finally {
    writer.Dispose();
}

对比:

using (Streamwriter writer = new Streamwriter(...)) {
    writer.blahblah
}

我的意思是,在第二个示例中,您确实应该将其放在try块中,因此添加finally块并不需要更多的努力。我知道整个事情可能被包含在一个更大的try块中,但是对我来说似乎是多余的。


3
为什么?你可以让任何异常上浮到堆栈顶部,并在日志中留下清晰的消息和堆栈跟踪,但无论如何都应该将其放在 try 块中。 - Tim Schmelter
同样的话也适用于foreach,它只是调用迭代器方法和属性的更加简洁的版本。 - O. R. Mapper
你的try/finally示例有缺陷,using(){}块将正常工作。 - H H
我一直听说,在Dispose方法执行期间抛出异常时,Using语句似乎与Try-Finally块相比没有用处。我还看到了这篇MSDN文章,在WCF客户端中也是如此。 - Narayanan
6个回答

17

你的代码存在一些问题。请使用using替代never这种写法,下面是原因:some issues

StreamWriter writer;
try {
    // What if you failed here to create StreamWriter? 
    // E.g. you haven't got permissions, the path is wrong etc. 
    // In this case "writer" will point to trash and
    // The "finally" section will be executed
    writer = new StreamWriter(...) 
    writer.blahblah();
} finally {
    // If you failed to execute the StreamWriter's constructor
    // "writer" points to trash and you'll probably crash with Access Violation
    // Moreover, this Access Violation will be an unstable error!
    writer.Dispose(); 
}

当您像这样使用using
  using (StreamWriter writer = new StreamWriter(...)) {
    writer.blahblah();
  }

这等同于代码

StreamWriter writer = null; // <- note the assignment

try {
  writer = new StreamWriter(...); 
  writer.blahblah();
}
finally {
  if (!Object.ReferenceEquals(null, writer)) // <- ... And to the check
    writer.Dispose();
}

为什么要使用 Object.ReferenceEquals(null) 而不是只用 !=null?答案在这里:https://dev59.com/NKnka4cB1Zd3GeqPL0sW#51795257 - T.Todua
1
@T.Todua 为什么要用 !=null 而不是 writer?.Dispose()?我的答案和你的一样:语言在不断发展。特别是自 Dmitry 回答以来已经过去了超过6.5年的时间;-) - Jay Allen
使用 using() {} 不会将 new 语句放在 try 的顶部之外,从而避免了 null 的可能性吗?无论如何,我不确定在 == 运算符重载期间出现恶性递归的可能性与我是否使用 x == null 有任何关系。显然,运算符重载总是很棘手的,在几乎所有情况下都是不必要的。太多的糖分对你也不好,即使是“语法糖”。 - ebyrob

9

这两段代码有什么区别吗?

有,using在调用Dispose之前会检查null(即它实际扩展成的代码会引入一个空值检查)。

为什么需要using

因为代码更简洁。只是语法糖。


6
你所写的基本上就是using包装的模式。因此,这就是使用using的目的,它可以避免每次使用可处理对象时都要编写相同的try....finally块。
至于你编辑后的问题:

[...]在第二个示例中,你真的应该把它放在try块中,所以添加finally块并不需要太多的努力。

很有可能你不能(或不想)明确地处理来自blahblah的错误,而只是希望它冒泡到调用代码……但仍然要清理掉你的StreamWriter资源!
因此,你最终会得到这个结果:
StreamWriter writer;
try{
    writer = new StreamWriter(...)
    writer.blahblah();
}catch{
  throw; // pointless!
} finally [
    writer.Dispose();
}

所以你最终会得到这个结果,为什么要这样做?finally不会吞噬异常,因此如果您只想让异常向上冒泡到调用代码,则根本不需要显式地抛出它。 - ken2k
原帖作者表示他们无论如何都会使用 try..catch,那只是一个编造的例子,说明为什么你可能不需要它。 - Jamiec
如果catch块记录了发生的异常,而finally块根据原始的try块是正常退出还是通过异常退出来调整其处理与Dispose相关的问题,则后一种模式可能并不无用。 - supercat

1
他们并不完全相同。try/finally块不能保护免受愚蠢错误的影响。
StreamWriter writer = new StreamWriter(...);
try {
  ...
  writer = new StreamWriter(...);
  ...
} finally {
  writer.Dispose();
}

请注意,只有第二个编写器被处理。相比之下,
using (StreamWriter writer = new StreamWriter(...)) {
  ...
  writer = new StreamWriter(...);
  ...
}

会产生编译时错误。

1
首先,使用“using”比你的代码更安全 - 它可以正确处理“可处理对象”构造函数中的错误,并且不会在空对象上调用dispose。
第二个区别在于代码的可读性 - 看看你的示例。第一个版本需要7行。第二个版本只需要3行。

不幸的是,真正的区别不仅在于代码的可读性。如果在StreamWriter构造函数中抛出异常,“try {} catch {}”将崩溃并出现浮动访问冲突;而“using {}”则会正确执行。 - Dmitry Bychenko
这就是第一段的内容。但是我会重写它,因为显然不太清楚。 - Dariusz

0
后者只是前者的语法糖,它们应该做相同的事情,但后者需要更少的样板代码。 我建议使用“using”这个,因为这样更不容易出错。

3
不幸的是,后者并不仅仅是前者的语法糖。它们之间存在微妙的差别:如果在前者中,在StreamWriter构造函数中抛出异常,代码将崩溃并出现浮动的AccessViolation,而后者(即“using{...}”)将正确执行。 - Dmitry Bychenko

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