【问题标题】:Disposing of Constructor Injected object构造函数注入对象的处理
【发布时间】:2010-12-29 21:07:21
【问题描述】:

假设我有一个与另一个类相关联的类。它看起来像下面这样:

public class DisposableClassOne : IDisposable
{
   private class mDisposableClassTwo;

   public DisplosableClassOne(DisposableClassTwo dcTwoInjected)
   {
      mDisposableClassTwo = dcTwoInjected;
   }

   public void Dispose()
   {
      // Should I dispose here? or make caller dispose of dcTwoInjected
      //mDisposableClassTwo.Dispose();
   }
}

我应该调用mDisposableClassTwoDispose方法还是让调用者这样处理?

using(DisposableClassTwo dcTwoInjected = new DisposableClassTwo())
using(DisposableClassOne dcOne = new DisposableClassOne(dcTwoInjected))
{
   // do stuff with dcOne
}

我认为让调用者处理它是最好的方法,但我认为通过将调用放在 Dispose 方法中可以保证它会被调用。有没有更好的方法来处理这个问题?

【问题讨论】:

标签: c# winforms dependency-injection idisposable


【解决方案1】:

如果你正在创建的类逻辑上拥有(1)构造函数注入的资源,那么它应该在内部处理它。如果它不拥有资源,那么它应该什么都不做,并依赖消费者来决定何时处置它。

如果您的类拥有对非托管资源的引用,您可能还需要实现终结器(析构函数),因为根本无法保证任何人都会调用您的 Dispose 方法。

通常,您希望避免调用者必须决定何时处置传递给类的构造函数的对象的情况。调用者可能会过早地处理资源(或持有它超过必要的时间)。在某些设计中,这并不总是一件容易实现的事情......遗憾的是,一次性对象模式并不总是与自身完美组合。


(1) 所有权 我的意思是你的类控制它所传递的资源的生命周期,并且没有其他代码持有对它的引用。如果任何其他代码(其生命周期与您的类的生命周期无关)持有对该资源的引用并将独立使用它,那么您不是该资源的所有者

【讨论】:

  • +1 你的第一句话是一个很好的规则。我确实试图避免这样做,但我不确定是否有办法围绕它进行设计。感谢您的回答
  • 当资源从外部注入时,消费者永远无法“逻辑拥有”它,因为它不知道它是如何创建的。它可能是一个共享实例,但消费者无法知道这一点。
【解决方案2】:

我认为这是你必须在设计中做出的终生决定。

DisposableClassOne 是一次性的,因为它引用了 DisposableClassTwo?

还有,

DisposableClassTwo 的生命周期是否独立于 DisposableClassOne?

对我来说,这两个问题的答案因每个班级设计而异。 StreamReader/Writer 是第一个问题是“是”而第二个问题是“否”的一个很好的例子——一旦 Reader 完成它,没有人会期望在 StreamReader 中使用 Stream,因此 Reader 会处理它。

但是,如果 DisposableClassTwo 是其他一些资源——也许是一个文件引用,您依次传递给几个类以“做某事”。在这种情况下,您不希望在准备好之前将其处理掉,而 DisposableClassOne 可能早就不复存在了。

【讨论】:

  • 除了有时我想在阅读器完成后使用流。这类假设总是有风险的。
【解决方案3】:

我熟悉的标准是使用 Disposable 模式。有一个虚拟的Dispose(bool disposing) 方法。 Dispose() 方法调用Dispose(true),终结器调用Dispose(false)

那么,在主要的处置方法中:

if (disposing)
{
    // Dispose of the referenced object, as well.
}

StreamWriter 和 StreamReader 遵循这种模式。如果你明确地调用Dispose(),他们也会处理底层的Stream。如果你让终结器来做你的清理工作,他们不会理会它。

或者,您可以添加一个属性:DisposeChild,如果为 true,则处置子对象,如果不是,则不理会它。

我也同意 rcravens。在几乎所有情况下,一个对象都应该在它被实例化的同一个作用域内被释放。

【讨论】:

  • +1 StreamWriter/Reader 处理底层流的好点(因为 StreamWriter/Reader 有一个构造函数,您可以在流中传递)。我想这将取决于关系,因为如果处理了 StreamWriter/Reader,则处理下属流是有意义的。感谢您的回答
【解决方案4】:

要正确使用 IDisposable 对象,必须遵循“最后一个出来请关灯”的原则。有一些不错的场景,还有一个令人讨厌的场景:

  1. 对象的创建者将其提供给另一个对象(“接收者”),但第一个对象的创建者控制接收者的生命周期,并且可以知道接收者何时不再需要传递的对象。在这种情况下,原始对象的创建者会处理 Dispose。
  2. 对象的创建者将其交给接收者,而接收者知道一旦对象被移交,其他人将不会使用该对象。在这种情况下,接收者将在对象完成后调用 Dispose。
  3. 对象接收者有时可能会以匹配#1 的方式使用,有时也会以匹配#2 的方式使用,但对象的提供者将知道何时给出它适用于哪种情况。这种情况可以通过告诉接收者是否应该处置它所提供的对象来处理。
  4. 供应商和接收者都想使用该对象,但可能不知道哪个会比对方更长寿。这是最艰难的情况。

我倾向于喜欢 #3 中建议的方法。不过,有时情况 #4 也适用。一种处理方法是对象的接收者在对象完成时触发事件。该事件可以互锁。递减一个计数器,指示有多少对象仍在使用传递的对象。一旦该计数器达到零,该对象可能会被删除。

【讨论】:

    【解决方案5】:

    最好不要释放任何外部引用,它们会由调用者自己自动释放。

    看到这个帖子,建议只释放成员变量:

    Finalize/Dispose pattern in C#

    【讨论】:

      【解决方案6】:

      “由所有者清理”的概念太常见了,不容忽视。所有权绝不仅仅是巧合。

      如果您想要更易于维护的软件,请避免可能被错误解释的副作用。 Stream 与 StreamReader 的例子是一个很好的例子......两者都是 IDisposable,作为调用者,我正在制作两者,所以我绝不应该让自己通过处理其中任何一个。这样做会违反抽象,将调用代码的知识与流类实现的细节联系起来(从学术上讲,这可能会随着时间而改变)。

      最简单的答案是拥有两个 usings(),即使您随后制作(并记录!)第二个 IDisposable 将处理第一个。

      【讨论】:

        猜你喜欢
        • 2013-01-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-24
        • 2016-11-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多