如果它们是IDisposable类的字段,我们是否应该只读标记对象

时间:2013-11-18 15:28:21

标签: c# idisposable

问题描述:

  1. 我有这个实现IDisposable的类(FooClass),它接受另一个一次性(OtherDisposableClass)作为其构造函数的参数。
  2. 我创建了一个OtherDisposableClass的实例
  3. 我创建了一个FooClass实例并将实例OtherDisposableClass传递给它
  4. OtherDisposableClass的实例不应该是可变的,所以我将它标记为只读
  5. 我无法摆脱Dispose方法中对OtherDisposableClass实例的引用
  6. 问题

    这里最好的事情是什么?我们应该

    1. 不将OtherDisposableClass的实例标记为只读,因此我们可以在Dispose方法中将其设置为“null”
    2. 将OtherDisposableClass标记为只读,不要再删除对它的引用。
    3. 其他东西
    4. 请详细说明你的答案。谢谢

      public class FooClass : IDisposable
      {
          private readonly OtherDisposableClass _disposable;
          private readonly string _imageSource;
          private readonly string _action;
          private readonly string _destination;
      
          private bool _isInitialized;
      
          public FooClass(OtherDisposableClass disposable)
          {
              _disposable = disposable;
          }
          ~FooClass() 
          {
              // Finalizer calls Dispose(false)
              Dispose(false);
          }
      
          public void Dispose()
          {
              Dispose(true);
              GC.SuppressFinalize(this);
          }
      
          protected virtual void Dispose(bool disposing)
          {
              // Not possible because _disposable is marked readonly
              _disposable = null;
          }
      }
      

4 个答案:

答案 0 :(得分:4)

创建OtherDisposableClass的事情应该是处理它...在FooClass中单独留下(如果你愿意,你可以拥有它readonly。)

将它设置为null无论如何都不会产生任何影响,它只会将FooClass中的引用设置为null(无论如何都要处理)。

修改

从给出的代码看起来你甚至不需要在这里实现IDisposable - 这只是让事情变得复杂。如果您在OtherDisposableClass的构造函数中创建了FooClass的实例,那么您可以实现IDisposable,只需在OtherDisposableClass.Dispose()内调用FooClass.Dispose()

这里创建的FooClass依赖于OtherFooClass - OtherFooClass将比FooClass更长 - 因此FooClass应首先进行GC,然后不会引用剩余的OtherDisposableClass - 这将允许它为GC'd

答案 1 :(得分:0)

在VB.NET中,如果实现IDisposable的类型具有任何声明为WithEvents的变量,那么将这些变量设置为“通常是个好主意 - 有时非常重要” null方法中的Dispose,因为如果不这样做将意味着这些变量引用的对象将保留对已处置对象的反向引用。在许多短期对象从长期存在的对象订阅事件的情况下,这可能导致严重的内存泄漏。

虽然C#不允许VB.NET样式的WithEvents声明,但是可以在C#中编写具有类似行为的属性:

// Handle a property Wobbler which identifies an object whose Wibbled event
// I want to handle with my WibbleWobble method.
Woozle _wobble;
Woozle Wobbler {
  get { return _wobbler; }
  set {
    var wasWobbler = _wobbler
    if (wasWobbler == value) return; // Don't unsubcribe and resubscribe same object
    if (wasWobbler != null)
      wasWobbler.Wibbled -= WibbleWobble; // Unsubscribe old event
    if (value != null)
      value.Wibbled += WibbleWobble; // Subscribe new event
    _wobbler = value;
  }
};

如果一个人使用这样的模式(这可能是确保订阅与unsubscribes配对的好方法),那么应该在一个人的Wobbler方法中将null设置为Dispose ,以确保附加到它的任何事件都被分离。

除了前面提到的场景之外,Dispose是否将事物设置为null通常无关紧要,因为对于这些字段引用的对象,唯一应该处理的是处理它们和取消他们的事件,在编码良好的对象上多次调用Dispose应该是无害的。但是,在少数情况下,在设置中将字段设置为null可能会有所帮助。一些集合使用数组存储数据,并指示哪些插槽保存有意义的数据;如果一个集合通过指示一个插槽没有任何有意义的东西来“删除”一个项目,但不会使该引用无效,该项目以及它拥有直接或间接引用的任何内容将由GC保持活力。如果处置的对象使其对其他对象的引用无效,则可以最小化集合保持活动的数据量。

顺便提一下,请注意上面的代码不是线程安全的,并且在没有锁定的情况下不能非常干净地成为线程安全的,即使subscribe / unsubscribe方法是线程安全的,并且只使用_wobbler通过{{{ 1}}方法。如果在处理订阅/取消订阅之前更新Interlocked,那么如果_wobbler是一个线程中的某个对象George而另一个线程中的其他对象,则第二个线程可能会在第一个线程之前向George发送取消订阅请求已发送其订阅请求(导致悬空订阅)。如果在处理订阅/取消订阅之前没有更新,那么两次同时尝试将Wobbler设置为George可能会导致George连续两次订阅请求(最终可能导致订阅悬空)。

答案 2 :(得分:0)

IDisposable接口允许您以确定的方式释放非托管资源。非托管资源可以是文件句柄,数据库连接等。处置对象不会释放任何内存。内存只能由垃圾收集器回收,而您几乎无法控制何时以及如何完成。 处置对象不会释放任何内存

垃圾收集和IDisposable之间仍然存在联系。如果您的类直接控制非托管资源,则应在最终确定期间释放此资源。但是,在类上使用终结器会对性能产生影响,应尽可能避免使用。 FooClass不直接控制非托管资源,也不需要终结器。但是,要强制执行非托管资源的确定性发布,FooClass.DisposeOtherDisposableClass.Dispose被明确调用时(Disposedisposing时)调用true非常重要。如果从终结器调用Dispose,则不应在其他托管对象上调用方法,因为这些对象可能已经完成。

总结一下:

  • 垃圾收集和回收内存不同于释放非托管资源。

  • Dispose方法应始终释放非托管资源。

  • Dispose方法应该在聚合对象上调用Dispose,但仅在直接调用时(不是从终结器调用时)。

  • 应该可以多次调用Dispose而不会出现任何问题。

  • IDisposable方法中将null字段设置为Dispose没有意义。

  • 如果您不直接控制任何非托管资源,请避免实施终结器。

  • 实现IDisposable时,必须考虑继承,因为基类和派生类可能希望在字段上调用Dispose和/或释放非托管资源。封闭课程可以避免很多这种复杂性。

详细说明将字段设置为null

的要点

我认为你的代码是理智的。例如,只有FooClass具有对OtherDisposableClass的永久引用,并且处置后的两个对象都将无法使用(例如,在除ObjectDisposedException之外的所有公共方法中抛出Dispose

如果将引用设置为OtherDisposableClassnull,那么该实例就有资格进行垃圾回收,因为没有对该实例的引用。但是,在处置FooClass之后,您肯定不会保留对该实例的引用,因为它不可用。这意味着FooClassOtherDisposableClass都无法再从GC root到达,整个对象图现在可以进行垃圾回收。因此,无需切断FooClassOtherDisposableClass之间的链接,因为它们将同时符合垃圾回收条件。

答案 3 :(得分:0)

您在IDisposable中将成员Dispose(bool disposing)设置为空是正确的,请参阅:https://msdn.microsoft.com/en-us/library/ms244737.aspx

如果字段标记为readonly,则无法解决问题,我担心会删除readonly