Why does 'sealed' affect the implementation of IDisposable?

亡梦爱人 提交于 2019-12-01 08:59:28

If a class which implements IDisposable is not sealed, it is likely that a derived class will need to do something in response to Dispose, but the base-class actions for Dispose should be performed as well. If the class exposes a public Dispose member which will always be synonymous with IDisposable.Dispose, the necessary semantics could be achieved in C# by simply using implicit interface implementation with a public virtual Dispose method.

There are two problems with that approach:

  1. It would require derived classes use a different method in cases where the parent exposes a public `Dispose` method, than in cases where it does not; things could get very muddy if a class which doesn't expose a public `Dispose` method is inherited by an unsealed class which does.
  2. Some base disposal code should run before the derived-class disposal code (e.g. the code that suppresses repeated `Dispose` attempts), and some should run after (e.g. `GC.SuppressFinalize()`). The only way to achieve that is to have a non-virtual wrapper call a protected virtual function. Note, btw, that Microsoft's wrapper doesn't properly suppress repeated-`Dispose`, but the wrapper is the only good place for such suppression code.

Note that Microsoft seems to have intended its Dispose pattern to be used in the cases where a base class does not override Finalize, but a derived class uses Finalize for cleanup. While that may have been the intent, it is not a good pattern for that purpose. With very few exceptions, the only classes which should only override Finalize for cleanup are those which derive from trivial classes like Object. If a class implements IDisposable but does not override Finalize, the only purpose for which a derived class should override Finalize is to sound an alarm if Finalize ever gets called, and even that usage is debatable (a better pattern would be:

class whatever:IDisposable
{
  IDisposable DisposedStatusObject;
  // Generate a static dummy object instance we can use as a sentinel value
  // It needs to be `IDisposable`, but shouldn't actually hold any resources.

  static IDisposable DisposedStatusDisposed = new List<int>().GetEnumerator();

  public bool Disposed {get {return (DisposedStatusObject == DisposedStatusDisposed);} }

  whatever()
  {
    DisposedStatusObject = new DisposalAlarm(); // First thing in constructor
  }
  void Dispose()
  {
    IDisposable prevStatus;
    prevStatus = Interlocked.Exchange(DisposedStatus, DisposedStatusDisposed);
    if (prevStatus != DisposedStatusDisposed)
    {
      Dispose(true);
      prevStatus.Dispose();
    }
  }
}

The DisposalAlarm() class is assumed to be a class with an overridden Finalize() method which sounds an alarm if that Finalize() method gets called without its Dispose() method being called first. The Dispose method for whatever will ensure that, if the derived-class method returns properly, the alarm will get cancelled. Note that if an instance of whatever has an unsuppressed finalizer, everything to which whatever holds a direct or indirect reference will have to be kept around until that finalizer has either run or been suppressed. By contrast, the addition of a DisposalAlarm object does not extend the lifetime of anything in whatever.

Making a class sealed means that there can be no classes derived from it. That means that the implementation of IDisposable doesn't need to take into account the behavior (or misbehavior) of derived classes.

A sealed class is not intended to be used as a base class, while an unsealed class is. Hence lies the distinction: an unsealed class needs to provide a way for its derived classes to implement Dispose() of their own, while a sealed class is free of this responsibility, because it cannot be extended.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!