避免显式调用Dispose()方法合理吗?

9

禁止显式调用 Dispose() 方法是否合理?

using 语句有无法确保正确清理 IDisposable 对象的情况吗?


尽管我喜欢让垃圾回收器为我管理内存,但如果有任何理由需要这样做,我毫不犹豫地调用Dispose()方法。 - Sam I am says Reinstate Monica
4
@SamIam:调用Dispose的目的是释放一个内存管理器不会为您释放的资源。Dispose明确涉及管理非内存资源,因此根本不要将内存管理器引入其中。 - Eric Lippert
6个回答

11

是否有道理制定一个规则禁止显式调用Dispose()来释放IDisposable对象?

不是。

是否存在某些情况,使用using语句无法正确确保清除IDisposable对象?

当需要清除对象的期望寿命不受包含using语句的方法的特定激活限制时,有一些情况确实不适合使用using自动清除对象。例如,对于"接管管理"另一个可释放对象的可释放对象,可能需要在外部对象被using块释放之前通过显式调用Dispose()清除内部对象,而该内部对象通常存储在外部对象的私有字段中。


一个好的、简洁的答案!我想这个问题涌现出来是因为我曾经热爱 C++ 及其表达 RAII 的清晰语法。 - Nick Strupat
1
@NickStrupat:你所说的“清晰语法”是指“字符}会导致代码执行”。这不是一种“清晰”的语法,而是一种极具误导性的语法。用于标记词法作用域的字符不应与可执行代码相关联;在C++中,它是C++设计缺陷之一。我仍然感到困惑的是,为什么有人认为这是一个好主意;如果我的资源清理代码很重要,那么我希望它在代码中是明确的,这样我就可以阅读理解调试它。如果它很重要,就不要隐藏它。 - Eric Lippert
1
@EricLippert - 说句反话,C#中关闭using块的}也可能导致代码执行。您认为这是“危险误导”的语法,还是块开头的“using”足以原谅它? - kvb
1
@kvb:使用“using”(或“foreach”或“lock”)明确指出其后面的代码块具有特殊语义。 - Eric Lippert
2
@EricLippert,我认为使用Dispose以确保正确性并不罕见。例如,在StreamWriter上调用Dipose()会刷新其缓冲区。如果不调用它(并依赖于终结),则缓冲区永远不会被刷新,因此文件的内容是不正确的。 - svick
显示剩余2条评论

5

在某些情况下,无法避免显式调用Dispose并仍然保持正确的语义。例如,考虑具有类型为IDisposable的字段的IDisposable对象。它们必须使用显式调用Dispose来释放该字段。

class Container : IDisposable {
  private readonly IDisposable _field;

  public void Dipose() {

    // Don't want a using here.
    _field.Dispose();
  }
}

1
好吧,你仍然可以使用using块来触发处理...但这会很奇怪,因为它不会匹配对象的整个生命周期,只是它的死亡。using控制表达式不必创建对象,只需分配一个变量即可。 - Ben Voigt
@BenVoigt 是的,你可以使用using。但这可能有点过度设计了;0 - JaredPar

3

人们通常会认为,无论对象处于什么状态,都可以随时调用Dispose方法清理对象的资源。

但这种自然的假设并非总是正确的。

WCF客户端代理为例。

管理代理生命周期的正确方式如下:

var serviceClient = new sandbox.SandboxServiceClient();
serviceClient.HelloWorld(name);
if(serviceClient.State == CommunicationState.Faulted)
{
    serviceClient.Abort();
}
else
{
    serviceClient.Dispose();
}

使用using语法会导致不安全的代码:

using (var serviceClient = new sandbox.SandboxServiceClient()) 
{
    serviceClient.HelloWorld(name);
}  // Here An exception will be thrown if the channel has faulted

你可以认为(就像我们所有人一样)这是WCF的一个缺陷设计方面,但编程的现实是我们有时必须修改我们的风格以适应我们正在使用的框架。以下是一个例子,即使对象的生命周期仅包含在一个函数调用中,也无法应用显式Dispose的统一规则。


2

using语句(实际上是一个带有Dispose在finally块中调用的try/finally的简写)适用于获取资源、使用它,然后在同一方法内处理该资源的情况。如果您没有这样线性使用资源(例如,其使用分散在多个方法中),则必须调用Dispose


1
如果创建可丢弃类型的实例成本很高(例如封装远程连接的类型),您可能希望重用实例以分摊成本。在这种情况下,using 将无法发挥作用,您必须在某个时候调用 Dispose

1
我会反对这样的规则,如果你有一个对象想要在多个函数调用中使用,using语句将强制处理该对象的释放,下一次你想要使用它时,你必须重新初始化...

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