"using"语句是“糟糕的代码”吗?

4
我已经阅读过这样的使用方式:
using (myObject)
{
   myObject.DoStuff();
}

可以这样想:
try
{
   myObject.DoStuff();
}
finally
{
   myobject.Dispose()
}

因此,如果myObject.DoStuff抛出异常ExceptionA,然后myObject.Dispose()也抛出异常(ExceptionB),那么ExceptionA将会丢失。(请参见这里的MSDN示例以获得更好的描述)。

这是否意味着,如果using块中的代码可能会抛出异常(这是大多数代码的情况?),那么使用using语句就是一种不好的做法?


“可以这样想”有点轻描淡写了。 :) - bzlm
10
Dispose 函数抛出异常是什么意思? - Ben Voigt
@blzm 这并不是那段代码的确切版本...那只是一个使用语句创建的简化版本的代码。 - Reed Copsey
个人而言,我更喜欢使用封装和try/catch,以防Dispose会抛出异常。但是正如其他人已经提到的那样,如果Dispose首先就抛出异常,那么确实存在一些问题。 - Quintium
@BenVoigt - 一些Dispose调用确实会抛出异常。请参见zugbo在他的答案中提供的链接(来自Marc Gravell)。 - Vaccano
1
@Vaccano:我并没有说Dispose不能抛出异常,我是说它不应该这样做。那些对象是有问题的。幸运的是,Marc展示了一种用包装对象修复它们的方法。 - Ben Voigt
4个回答

19

这是否意味着,如果 using 块中的代码可能会引发异常(这是大多数情况下的情况?),那么使用 using 语句是一个不好的实践?

不是这样。

然后 myObject.Dispose() 也会抛出异常。

这真正是您问题的关键。

这才是“不好的实践”。IDisposable.Dispose 实现应该设计成在除了真正无法恢复的情况下不会引发异常。

由于 IDisposable 的目的实际上是释放所涉及的资源,因此主要问题应该确保在大多数情况下,此实现不会引发任何异常。清理方法抛出异常将会带来很多麻烦,这就是为什么在 WCF 客户端等情况下不应使用using 语句的原因。

话虽如此,我认为 using 语句本身并不是不好的实践。事实上,它往往是一种非常好的实践,因为它避免了一个非常常见的陷阱(在异常情况下缺失资源的可处置性)。


6
Dispose 调用中抛出异常相当于从 C++ 析构函数中抛出异常。这将导致调用代码无法正确处理可被销毁的集合项。+1 - JaredPar

7

using语句确保实现IDisposable接口的类型被正确处理(也就是说,这是语法糖,用于正确实现Dispose模式)。

它们非常符合良好实践

Dispose函数抛出异常是不好的实践


4
有关该问题,有一个具体的关注点是异常情况。 - bzlm

6
这是WCF中常见的问题,即Dispose()经常会抛出异常。有一种方法可以将可释放对象包装起来,以便您可以继续获得using()语句的好处,而不会冒失丢失异常。它基本上会吞噬在Dispose期间抛出的任何异常,以便原始异常始终是抛到更高上下文的异常。 http://marcgravell.blogspot.com/2008/11/dontdontuse-using.html

2
在.NET中,异常处理的一个根本限制是它遵循C++异常处理模型,所有与当前异常上下文相关的信息都必须封装在一个单独的异常对象中;实际上,在C#中,所有关于是否应该捕获异常的信息都必须封装在单个异常对象的类型中。此外,在C#中,甚至要发现异常是否发生,唯一的方法就是同意捕获它,并且没有办法声明地表明希望对异常进行操作,但无意将其处理得足以被认为已“解决”。由于这些限制,“using”存在困难。
在现实世界中,可能会在Dispose()期间发生一些不希望出现的事情,这应该打断任何没有预料到它们的代码的流程。这些情况通常应该是异常。不幸的是,如果Dispose正在运行,因为发生了其他异常,并且其中发生异常,那么在C#中只有三种实际可行的行动方案:
1. 丢失有关导致“Dispose”运行的异常的所有信息,因为没有办法让“Dispose”或调用它的代码获取该信息,并且在“finally”块中发生的任何异常都会破坏任何先前挂起指令的信息。 2. 阻止在Dispose中发生的异常-可以将其记录在某个地方,但除非调用者明确检查日志,否则它将不知道发生了异常。 3. 捕获任何和所有类型的异常,将其类型抓到一个变量中,并重新抛出;在“finally”块中操作相关变量。
在VB.NET中,有第四种具有更好语义的可能性,但需要看起来不太好的代码:使用异常过滤器将任何发生的异常锁定到变量上,而不是捕获它,并像上述#3一样,在“finally”块中操作该变量。
“using”语句提供了上面列出的第一种语义。在某些情况下,其他方法可能更好。我希望VB和C#能够提供一个接受类型为Exception的参数的“finally”语句版本。它可以允许使用#4上述(语义最好)而不需要丑陋的代码。

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