5

After reading the answer here, I decided to mark my class as sealed in order to simplify the IDisposable implementation. Why does sealed affect the implementation of IDisposable (e.g. GC.SuppressFinalize(this); does not need to be called)? Please explain what is happening. I need to be able to explain to a fellow developer why I made the class sealed.

Community
  • 1
  • 1
O.O
  • 11,077
  • 18
  • 94
  • 182

3 Answers3

2

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.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

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.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • Yes I understand the sealed keyword. Please explain why I do not need to call GC.SuppressFinalize(this); – O.O Jun 13 '12 at 22:44
  • I don't know why not. You also didn't ask this in your question. If you want to know the answer about `GC.SuppressFinalize`, then you should edit your question to include that. – John Saunders Jun 13 '12 at 22:46
  • Thought my question specifically asked about the difference in implementation, isn't GC.SuppressFinalize a difference? – O.O Jun 13 '12 at 22:47
  • Can you point to the documentation in which you read that you do not need to call `GC.SupressFinalize` if you seal the class? – Nope Jun 13 '12 at 22:49
  • @FrançoisWahl - In addition to the link in the question? – O.O Jun 13 '12 at 22:50
  • 1
    The documentation states `Leave out the finalizer altogether if this class doesn't own unmanaged resources itself` and the notes in the stackoverflow post you linked states `you should only implement a finalizer if you have actual unmanaged resources to dispose.` None of them mention this being related to sealing a class. they are referring to unmanaged resources. So where do you get it from that sealing a class has to do with calling the finalizer? – Nope Jun 13 '12 at 22:53
  • @FrançoisWahl - I see what you mean. – O.O Jun 13 '12 at 22:58
  • Ah I see, because he didn't implement it you assumed it is because you do not have to. Here is a post where a sealed class implements it and uses unmanaged resources (http://stackoverflow.com/questions/1418970/implementing-idisposable-on-a-sealed-class) I think the post you linked was only giving an example. In general, as far as I know finalization only matters when referring to unmanaged resources. – Nope Jun 13 '12 at 22:59
  • 1
    白ジェームス: `GC.SuppressFinalize` should generally be called on classes which override `Finalize`, after disposal is complete. If a sealed class overrides `Finalize`, it should call `GC.SuppressFinalize`. Likewise if a class isn't sealed and a derived class overrides `Finalize`. Note that only the non-virtual `Dispose` wrapper knows when disposal is really complete, so `GC.SuppressFinalize` must be called from the base. Note also that it's cheaper for the base class to unconditionally call `GC.SuppressFinalize` than it would be to only call it when a derived class overrides `Finalize`. – supercat Jun 14 '12 at 20:04
1

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523