【问题标题】:Is there any benefit to implementing IDisposable on classes which do not have resources?在没有资源的类上实现 IDisposable 有什么好处吗?
【发布时间】:2012-03-17 08:32:38
【问题描述】:

在 C# 中,如果一个类(例如管理器类)没有资源,那么拥有它有什么好处: IDisposable

简单示例:

public interface IBoxManager
{
 int addBox(Box b);
}

public class BoxManager : IBoxManager
{
 public int addBox(Box b)
 {
  using(dataContext db = new dataContext()){
   db.Boxes.add(b);
   db.SaveChanges();
  }
  return b.id;
 }
}

如果 BoxManager 也实现了 IDisposable,那么在使用内存方面会有什么好处吗? public class BoxManager : IBoxManager , IDisposable

例如:

BoxManager bm = new BoxManager();
bm.add(myBox);
bm.dispose();//is there benefit to doing this?

【问题讨论】:

  • 这取决于你将如何实现Dispose()
  • 当我不再需要对象时,我使用 Dispose() 从事件中取消订阅。

标签: c# idisposable


【解决方案1】:

在一个类型上实现IDisposable只有两个原因

  • 该类型包含在不再使用该类型时必须释放的本机资源
  • 该类型包含IDisposable 类型的字段

如果这些都不成立,则不要实现IDisposable

编辑

一些人提到IDisposable 是实现开始/结束或预定操作的好方法。虽然这不是 IDisposable 的初衷,但它确实提供了一个非常好的模式。

class Operation {
  class Helper : IDisposable {
    internal Operation Operation;
    public void Dispose() {
      Operation.EndOperation();
    }
  }
  public IDisposable BeginOperation() {
    ...
    return new Helper() { Operation = this };
  }
  private void EndOperation() {
    ...
  }
}

注意:实现此模式的另一种有趣方法是使用 lambda。不要将IDisposable 回馈给用户并希望他们不要忘记调用Dispose,而是让他们给你一个lambda,他们可以在其中执行操作,然后你关闭操作

public void BeginOperation(Action action) {
  BeginOperationCore();
  try {
    action();
  } finally {
    EndOperation();
  }
}

【讨论】:

  • 我认为这太过分了。有时,using 构造完美地捕捉到了“开始/结束”范式。例如,Stack exchange 自己的 MiniProfiler 使用这种模式来表示没有资源管理基础的代码“块”。 MVC 框架与 HTML 元素(特别是 <form>)类似。
  • 嗯,实际上还有第三种情况:语法糖。示例:ASP.NET MVC Html.BeginForm 帮助器返回 IDisposable,它在 Dispose 方法中所做的只是呈现关闭 </form> 标记。
  • @KirkWoll,达林同意了。我会在上面添加那个用例
  • 我讨厌我不能在同一个评论中回复你们两个。很烦人。
  • 还有第四个(也是非常重要的)用例。 “从事件处理程序中取消订阅” - 这会导致内存泄漏,最好在 Dispose 中明确执行。
【解决方案2】:

如果您不明确使用Dispose() 方法,一次性和非一次性版本之间不会有一点区别。

【讨论】:

  • 很好的答案。你用它做什么并不重要,只要 (a) 你在 Dispose() 方法中做了一些有用的事情,并且 (b) using(){} 语法在它的上下文中是有意义的(无论是在类或在使用它的类上)。
【解决方案3】:

虽然您的代码不会从实现 IDisposable 中受益,但我不同意这里的其他观点,即 IDisposable 仅用于(直接或间接)释放本机资源。只要对象需要在其生命周期结束时执行清理任务,就可以使用 IDisposable。和using一起使用非常有用。

一个非常流行的例子:在 ASP.NET MVC 中 Html.BeginForm 返回一个 IDisposable。创建时,对象打开标签,调用 Dispose 时将其关闭。不涉及本机资源,但仍然可以很好地使用 IDisposable。

【讨论】:

  • +1:此外,当观察者向被观察者注册时,被观察对象(在 IObserver/IObservable 模式中)返回 IDisposable。
【解决方案4】:

不,如果你不做一些有用的事情,比如释放你的类可能在 Dispose 方法中持有的非托管资源,那将没有任何好处。

【讨论】:

    【解决方案5】:

    一个主要的混淆点可能不适用于您的情况,但经常出现,即究竟什么是“资源”。从对象的角度来看,非托管资源是外部实体 () 代表它“做”(*) 的事情,该外部实体将继续做这些事情——损害其他人的利益实体——直到被告知停止。例如,如果一个对象打开一个文件,托管该文件的机器可能会授予该对象独占访问权限,拒绝宇宙中的其他所有人使用它的机会,除非或直到它被通知不再需要独占访问权限。

    (*) 可以是任何地方,任何地方;甚至可能不在同一台计算机上。

    (**) 或以某种方式改变外部实体的行为或状态

    如果一个外部实体正在代表一个被遗弃并消失的对象做某事,而没有事先让该实体知道其服务不再需要,则外部实体将无法知道它应该停止代表不再存在的对象。 IDisposable 通过提供一种在不需要对象的服务时通知对象的标准方法,提供了一种避免此问题的方法。不再需要其服务的对象通常不需要向任何其他实体请求任何进一步的帮助,因此可以请求任何代表其行事的实体停止这样做。

    为了避免在没有首先调用IDisposable.Dispose() 的情况下放弃对象的情况,系统允许对象注册一个名为Finalize() 的“故障安全”清理方法。因为无论出于何种原因,C# 的创建者都不喜欢 Finalize() 这个术语,因此该语言需要使用一种称为“析构函数”的构造,它的作用大致相同。请注意,一般情况下,Finalize() 会掩盖而不是解决问题,并且可能会产生自己的问题,因此如果有的话,应该非常谨慎地使用它。

    “托管资源”通常是为实现 IDisposable 的对象指定的名称,并且通常(但并非总是)实现终结器。

    【讨论】:

      【解决方案6】:

      不,如果没有(托管或非托管)资源,则也不需要IDisposable

      小警告:有些人使用 IDisposable 来清理事件处理程序或大型内存缓冲区,但是

      • 你好像没用过那些
      • 无论如何,这是一个有问题的模式。

      【讨论】:

      • 事件处理程序在很多情况下需要清理,否则会泄漏。需要清理的类应该在调用IDisposable.Dispose 时执行此类清理,如果在此之前它们没有这样做的话,除非在没有Dispose 方法无法获得的信息的情况下清理是不切实际的。那么,为什么IDisposable.Dispose 不是清理事件处理程序的完全正确的方法呢?
      • @supercat - 我们以前经历过这个。这是一个你一开始就不应该建立的老鼠窝。
      • @HenkHolterman:如果有机会,您能否详细说明一下“老鼠窝”的评论?这是否意味着一个类不应该私下附加它的事件处理程序,而是将其委托给另一个类似控制器的对象?
      • @Groo - 是的,或多或少。当订阅者生命周期
      • @HenkHolterman:在事件链中添加中间对象当然有好处,但是如果创建它们的对象在其@中没有这样做,谁会告诉这些中间对象取消订阅987654325@方法?
      【解决方案7】:

      根据我的个人经验(通过此处的讨论和其他帖子确认)我会说,在某些情况下,您的对象可能会使用大量事件,或者不是大量但频繁订阅和取消订阅事件,这有时会导致对象不是垃圾收集的。在这种情况下,Dispose 中取消订阅我的对象之前订阅的所有事件。

      希望这会有所帮助。

      【讨论】:

        【解决方案8】:

        如果您想使用using () {} 语法,IDisposable 也很棒。

        在带有 ViewModels 的 WPF 项目中,我希望能够暂时禁用 NotifyPropertyChange 引发的事件。为了确保其他开发人员能够重新启用通知,我编写了一些代码以便能够编写如下内容:

        using (this.PreventNotifyPropertyChanges()) {
            // in this block NotifyPropertyChanged won't be called when changing a property value
        }
        

        语法看起来不错并且易于阅读。要让它工作,需要编写一些代码。您将需要一个简单的 Disposable 对象和计数器。

        public class MyViewModel {
            private volatile int notifyPropertylocks = 0; // number of locks
        
            protected void NotifyPropertyChanged(string propertyName) {
                if (this.notifyPropertylocks == 0) { // check the counter
                    this.NotifyPropertyChanged(...);
                }
            }
        
            protected IDisposable PreventNotifyPropertyChanges() {
                return new PropertyChangeLock(this);
            }
        
            public class PropertyChangeLock : IDisposable {
                MyViewModel vm;
        
                // creating this object will increment the lock counter
                public PropertyChangeLock(MyViewModel vm) {
                    this.vm = vm;
                    this.vm.notifyPropertylocks += 1;
                }
        
                // disposing this object will decrement the lock counter
                public void Dispose() {
                    if (this.vm != null) {
                        this.vm.notifyPropertylocks -= 1;
                        this.vm = null;
                    }
                }
            }
        }
        

        这里没有可处理的资源。我想要一个带有 try/finally 语法的干净代码。 using 关键字看起来更好。

        【讨论】:

          【解决方案9】:

          拥有它有什么好处:IDisposable?

          在您的具体示例中看起来并非如此,然而:即使您没有任何 IDisposable 字段,也有一个很好的理由来实现 IDisposable:您的后代可能会这样做。

          这是IDisposable: What your mother never told you about resource deallocation 中突出显示的IDisposable 的主要架构问题之一。基本上,除非您的班级是密封的,否则您需要确定您的后代是否可能拥有IDisposable 成员。这不是您可以实际预测的事情。

          因此,如果您的后代可能有 IDisposable 成员,那么这可能是在基类中实现 IDisposable 的好理由。

          【讨论】:

          • 您的后代可能实现 IDisposable 的事实只有在存在实际可能性时才相关,即最后一个拥有您的后代类型之一实例的实体将其视为您的类型(或某些不会实现 IDisposable 的中间类型,如果您不这样做)。在某些情况下确实如此(例如io.stream),但这并不是真正的常态。
          【解决方案10】:

          简短的回答是否定的。但是,您可以在对象生命周期结束时巧妙地使用 Dispose() 执行的性质。有人已经给出了一个很好的 MVC 示例 (Html.BeginForm)

          我想指出IDisposableusing() {} 声明的一个重要方面。在 Using 语句的末尾,Dispose() 方法会自动在 using 上下文对象上调用(当然它必须实现 IDisposable 接口)。

          【讨论】:

            【解决方案11】:

            还有一个没有人提到的原因(尽管它是否真的值得争论): 约定说如果有人使用实现IDisposable 的类,它必须调用它的Dispose 方法(显式地或通过'using' 语句)。 但是,如果一个类的 V1(在您的公共 API 中)不需要 IDisposable,但 V2 需要,会发生什么?从技术上讲,将接口添加到类并不会破坏向后兼容性,但由于约定,它确实如此!因为您代码的旧客户端不会调用它的 Dispose 方法,并且可能会导致资源无法释放。 (几乎)避免它的唯一方法是在您怀疑将来需要它的任何情况下实现 IDisposable,以确保您的客户始终调用您的 Dispose 方法,这可能真的需要有一天。 另一种(可能更好)的方法是在 API 的 V2 中实现上面 JaredPar 提到的 lambda 模式。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-11-11
              • 2011-11-28
              • 1970-01-01
              • 2013-10-29
              • 2012-03-28
              • 1970-01-01
              • 2010-11-04
              • 1970-01-01
              相关资源
              最近更新 更多