【问题标题】:c# destructors: the dispose "pattern" and best practisesc#析构函数:dispose“模式”和最佳实践
【发布时间】:2011-12-29 00:01:27
【问题描述】:

我知道 c# 中析构函数和终结函数在含义和使用上的区别。

但是,“我应该……”的答案通常是“不要使用析构函数,而是使用 MSDN 中所示的处置模式”。 Eric Lippert writes相当strongly反对不必要地使用析构函数。

但是,这种“模式”主张编写析构函数~T() { Dispose(false); }。声明的原因是它是一个“后备”,以防程序员忘记调用Dispose()。当然,这忽略了终结者在其操作中是不确定的,甚至可能永远不会运行的事实。

因此:

  1. 如果我使用 dispose 模式,我是否也应该提供析构函数?顺便说一句,我只处理托管资源(例如实体框架DataContext)。

  2. 如果我确实提供了析构函数:如果我的类派生自 IDisposable,它可能已经提供了析构函数,那么我是否也应该提供一个析构函数?我认为在这种情况下永远不会编写析构函数,但是文档说无论如何它都会自动调用基类的析构函数。

【问题讨论】:

  • SO 上已经有几十个类似的问题了。
  • 你有一个 unmanaged 资源吗?
  • 没有与其中之一相关的问题。我在问为什么这些答案中有一个 catch-22。
  • PS:MSDN 处理“模式”感觉像是一种包罗万象的方法,可以尽可能多地驯服边缘情况。但它过于复杂,文档有时令人困惑。
  • @PeterMarks 问题出在 MSDN 文档上——它仅在您使用非托管资源时记录了正确的实现。它提到应该在其他情况下使用IDisposable,但“参考实现”在这些情况下不合适。

标签: c# destructor dispose


【解决方案1】:

我不会真正回答你的两个问题,但我会就以下方面提供意见:

声明的原因是它是一个“后备”,如果程序员忘记调用Dispose(),就会调用它。

如果方法的调用者传递一个非空字符串是一个要求,那么如果他们传递空字符串,您完全有权抛出异常,对吧?来电者违反合同;这是异常行为,因此您抛出异常。你不认为,哦,调用者“忘记”传递一个有效的参数,我想我会接受错误的输入并继续前进。例如,这样做可以有效地将方法的约定从“null 是不可接受的并且会产生异常”更改为“null 是可以接受的并且被视为空字符串”。

如果用户在完成后调用 Dispose 是一个要求,而他们没有,那么这与调用者在调用方法时未能履行合同没有什么不同。调用者未能满足要求,因此使他们的程序崩溃。如果遇到未处置的对象,则让析构函数抛出一个信息异常。就像调用者很快就会知道将错误的参数传递给方法会很痛苦一样,他们也会知道未能处置您的对象也会很痛苦。

显式处理对象是必要的,或者不是。如果有必要,请确保用户这样做。否则就是隐藏他们的错误。

【讨论】:

  • +1 - 虽然我完全同意这一点,但我也建议有条件地构建以仅在 DEBUG 构建中包含析构函数是一个不错的选择......没有真正的理由包括在发布版本中使用析构函数的额外开销,如果它纯粹是为了帮助正确使用,因为它确实会影响您的 GC 性能。
  • @Eric:“让他们的程序崩溃”......一如既往的富有表现力! :-) 也许我会包含“你违反了我的合同!”的信息。在异常中。
  • @PeterMarks:好点。里德在上面提出了一个很好的观点。如果析构函数的目的只是捕获用户的错误,那么将其设为仅调试代码可能是正确的做法。这就是我倾向于做的事情。我通常不会为没有非托管资源的类编写析构函数。
  • @PeterMarks:您可以将异常称为“TasteJusticeException”,并让错误消息为“您违反了我的合同,现在您将品尝正义!”那太棒了。
  • 如果你想避免与终结器相关的奇怪的关机崩溃,你可能还想用 if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload( ))
【解决方案2】:

如果我使用 dispose 模式,我是否也应该提供析构函数?顺便说一句,我只是在处理托管资源(例如实体框架 DataContext)。

在这种情况下,不。原因是,当您的类被 GC 捕获时,所有这些对象也将由 GC 处理。在这种情况下,没有理由添加析构函数的开销。

这是IDisposable 复杂性的一部分——确实应该超过标准实现,具体取决于使用情况。在这种情况下,您将封装实现IDisposable 的资源。因此,允许您的用户(间接)处置这些资源很重要,但您不需要处理析构函数,因为没有您直接“拥有”的非托管资源。如果您想了解更多详情,我会在 Part 3 of my series on IDisposable 中介绍。


如果我确实提供了一个析构函数:如果我的类是从 IDisposable 派生的,它可能已经提供了一个析构函数,那么我是否也应该提供一个?我认为在这种情况下永远不会编写析构函数,但是文档说无论如何它都会自动调用基类的析构函数。

在这种情况下,基类应公开protected virtual void Dispose(bool disposing) 形式的受保护方法。您可以将资源清理逻辑放在那里,因为基类析构函数会为您处理对此方法的调用。详情请见Part 2 of my series on IDisposable。

【讨论】:

  • 我认为您上面的评论“MSDN 文档......仅在您使用非托管资源时记录正确的实现”是真正的问题。这种模式感觉很安全,因为它已经过 MS 审查,但对于托管资源用例来说并不完全有意义。
【解决方案3】:

如果您正在编写一个类,则不能强制使用该类的每个人都遵循预期的 IDisposable 模式。这就是你需要析构函数后备的原因。

即使“每个人”都是“只有你”,你也是人,有时也会犯错。

【讨论】:

  • 是的。但请记住,它甚至不能保证运行。
  • 不是在写“一个类”的时候。仅适用于非常稀有和非常特殊的课程。
  • 在这种情况下,这是不正确的。没有理由添加终结器的开销,因为此类没有“拥有”的非托管资源。如果需要,实体框架的上下文类应自行管理。
  • @Reed:是的,这也是我的想法。但是,为什么这种“模式”会获得如此多的通话时间。应尽可能避免使用终结者(哎呀,我不记得我上次为真正需要而写了一个)。
  • @PeterMarks 该模式是绝对要求,因为它是处理资源清理的首选方法。但是,该模式确实比单个示例更复杂,因为正确的实现取决于 为什么 您要实现 IDisposable,因为确实有 4 种截然不同的场景可以而且应该是用过的。见:reedcopsey.com/series/idisposable
【解决方案4】:

很难为这个问题添加一些尚未被这里的好答案触及的东西。

然后,我将尝试提供 MSDN 上提倡的 dispose 模式的替代方案。我从来没有真正喜欢过Dispose(bool) 方法,所以我认为这种模式is better 如果你确实需要一个析构函数:

public class BetterDisposableClass : IDisposable {

  public void Dispose() {
    CleanUpManagedResources();
    CleanUpNativeResources();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

但是,既然你已经发现你真的不需要一个,你的模式是much simpler:

public class ManagedDisposable : IDisposable {

  // ...

  public virtual void Dispose() {
    _otherDisposable.Dispose();
  }

  IDisposable _otherDisposable;

}

【讨论】:

  • +1 托管/非托管的明确分离是简洁和合乎逻辑的。但是您的方法缺少 TasteJusticeException 来强制执行代码合同(请参阅上面 Eric Lippert 的回答以了解它是如何完成的)。
  • 您链接到的文章很有趣也很有帮助。
  • @PeterMarks:谢谢!我的方法没有那个例外,因为我缺乏 Lippert 的才华!不过你可以添加它!
【解决方案5】:
 Or if you know that your object that you are trying to dispose of Implements IDisposable
why not do something like this

StreamWriter streamWrt = null
try
{
streamWrt = new StreamWrite();
... do some code here
}
catch (Exception ex)
{
 Console.WriteLine(ex.Message)
}
Finally
{
  if (streamWrt != null)
  {
    ((IDisposable)streamWrt).Dispose();
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-17
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多