3

I know the difference in meaning and use of destructors and finalisers in c#.

However, typically the answer to a "should I ..." is answered by "don't use destructors, rather use the dispose pattern as shown in MSDN". Eric Lippert writes quite strongly against using destructors unnecessarily.

But, that "pattern" advocates writing a destructor as such ~T() { Dispose(false); }. The stated reason is that it is a "fallback" which is called in case the programmer forgets to call Dispose(). Of course this ignores the fact that finalisers are indeterminate in their operation, and may never even run.

Hence:

  1. If I use the dispose pattern, should I also provide the destructor? Incidentally, I am only disposing managed resources (the Entity Framework DataContext for example).

  2. If I do provide a destructor: if my class is derived from an IDisposable, which may already provide a destructor, then should I provide one too? I thought that one never writes a destructor in such a case, however docs say that it will call the base class' destructor automatically anyway.

Community
  • 1
  • 1
Peter Marks
  • 989
  • 2
  • 8
  • 15
  • 1
    There are dozens of similar questions already on SO. – H H Nov 14 '11 at 20:53
  • 4
    And do you have an _unmanaged_ resource to begin with? – H H Nov 14 '11 at 20:53
  • No there are questions relating to one or the other. I am asking why there is a catch-22 in these answers. – Peter Marks Nov 14 '11 at 20:53
  • PS: that MSDN dispose "pattern" feels like a catch-all approach to tame as many edge cases as possible. But it is overly complex and the docs are sometimes confusing. – Peter Marks Nov 14 '11 at 20:57
  • 2
    @PeterMarks The problem is the MSDN documentation - it only documents the proper implementation when you are working with unmanaged resources. It mentions that `IDisposable` should be used in other cases, but the "reference implementation" is not appropriate in those cases. – Reed Copsey Nov 14 '11 at 20:58
  • @ReedCopsey and since it doesn't warn against mixing managed and unmanaged resources in the same class to begin with, the anti-pattern is even worse than that in encouraging this. – Jon Hanna Nov 17 '11 at 14:46

5 Answers5

18

I won't actually answer your two questions, but I will provide an opinion on:

The stated reason is that it is a "fallback" which is called in case the programmer forgets to call Dispose().

If it is a requirement that a caller of a method pass, say, a non-null string, then you are perfectly within your rights to throw an exception if they pass null, right? The caller violated the contract; that's exceptional behaviour so you throw an exception. You don't think, oh, the caller "forgot" to pass a valid argument, I think I'll accept the bad input and soldier on. Doing so is effectively changing the contract of the method from "null is unacceptable and will produce an exception" to "null is acceptable and is treated as an empty string", for example.

If it is a requirement that the user calls Dispose when they are done, and they did not, then that's no different than a caller failing to fulfill the contract when calling a method. The caller failed to fulfill a requirement, so crash their program. Have the destructor throw an informative exception if it encounters a non-disposed object. Just as callers quickly learn that passing bad arguments to a method hurts, they'll learn that failing to dispose your object hurts too.

Either explicitly disposing the object is necessary or it is not. If it is necessary, then make sure the user does so. Doing otherwise is concealing their bug.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 5
    +1 - While I agree with this completely, I'd also suggest that it can be a good option to do this conditionally built to only include the destructor in DEBUG builds... There's no real reason to include the extra overhead of having the destructor in a release build, if it's purely to help correct usage, as it does have an impact on your GC performance. – Reed Copsey Nov 14 '11 at 21:07
  • 2
    @Eric: "crash their program"... expressive as always! :-) Maybe I'll include the message "You violated my contract!" in the exception. – Peter Marks Nov 14 '11 at 21:10
  • 1
    @PeterMarks: Good point. Reed makes a great point above; if the purpose of the destructor is only to catch the user's error, then perhaps making it debug-only code is the right thing to do. That's what I would be inclined to do. I wouldn't typically write a destructor for a class that has no unmanaged resources. – Eric Lippert Nov 14 '11 at 21:11
  • 20
    @PeterMarks: You could call the exception "TasteJusticeException", and have the error message be "You violated my contract, now you will TASTE JUSTICE!" That would be awesome. – Eric Lippert Nov 14 '11 at 21:12
  • @Eric: thanks for your comments, I shall formally adopt ``TasteJusticeException`` in our internal design guides. I am going to mark Reed's answer though, as he's provided some fantastic links which may interest future readers. – Peter Marks Nov 14 '11 at 21:16
  • I'm pretty sure the `TasteJusticeException` should be the base class of a huge number of framework exceptions. (`InvalidOperationException`? *Taste Justice*. `ArgumentNullException`? *Tastier Justice*.) – dlev Nov 14 '11 at 21:26
  • 2
    One more point if you want to avoid strange crashes on shutdown related to finalizers, you probably also want to wrap your throw in Finalizer with an if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload()) – Kevin Pilch Nov 14 '11 at 21:39
  • I appreciate that one should avoid having the error go undetected, but an immediate non-deterministic crash doesn't seem like a good course of action, especially since the class that discovers the problem has no way of knowing whether something like Environment.FailFast would itself create far worse problems or data loss than the non-disposed object. Would it be possible/preferable to arrange attach an exit handler, which would itself attach an exit handler to invoke the Windows Error Reporting dialog? I would think all normal cleanup should be executed by that point. – supercat Nov 15 '11 at 16:49
12

If I use the dispose pattern, should I also provide the destructor? Incidentally, I am only disposing managed resources (the Entity Framework DataContext for example).

In this case, no. The reason is that, by the time your class is caught by the GC, all of those objects are also going to be handled by the GC. There is no reason to add the overhead of a destructor in this case.

This is part of the complexity of IDisposable - there really should be more than the standard implementation, depending on the usage. In this case, you're encapsulating a resource that implements IDisposable. As such, its important to allow your user to (indirectly) dispose those resources, but you don't need to handle the destructor, as there is no unmanaged resource you directly "own". I cover this in Part 3 of my series on IDisposable if you want more details.


if I do provide a destructor: if my class is derived from an IDisposable, which may already provide a destructor, then should I provide one too? I thought that one never writes a destructor in such a case, however docs say that it will call the base class' destructor automatically anyway.

In this case, the base class should expose a protected method of the form protected virtual void Dispose(bool disposing). You would put your resource cleanup logic there, as the base class destructor handles the call to this method for you. For details, see Part 2 of my series on IDisposable.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    I think your above comment that "MSDN documentation...only documents the proper implementation when you are working with unmanaged resources" is the real issue. That pattern feels safe because it's MS vetted, but it isn't fully meaningful for a managed resources use case. – Peter Marks Nov 14 '11 at 21:05
2

If you're writing a class, you can't force everyone that uses that class to follow the expected IDisposable pattern. That's why you need the destructor fallback.

Even if "everyone" is "just you", you are human and will sometimes make mistakes.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • True. But keep in mind that it is not guaranteed to even run. – Peter Marks Nov 14 '11 at 20:55
  • 2
    Not when writing "a class". Only for very rare and very special classes. – H H Nov 14 '11 at 20:57
  • In this case, this is not true. There is no reason to add the overhead of a finalizer, as there is no unmanaged resource "owned" by this class. The Entity Framework's context class should manage this itself, if required. – Reed Copsey Nov 14 '11 at 20:57
  • @Reed: yes that's my thinking as well. But then why does that "pattern" get so much airtime. Finalisers should be avoided whenever possible (heck, I can't remember the last time I wrote one for some genuine need). – Peter Marks Nov 14 '11 at 20:59
  • @PeterMarks The pattern is an absolute requirement, as it's the preferred means of handling resource cleanup. However, the pattern really is more complicated than a single example, as the proper implementation depends on *why* you're implementing `IDisposable`, as there are really 4 distinctly different scenarios in which it can and should be used. See: http://reedcopsey.com/series/idisposable/ – Reed Copsey Nov 14 '11 at 21:02
2

It's very hard to add something to this question that hasn't been touched already by the great answers here.

I'll try then to give an alternative to the dispose pattern that's advocated on MSDN. I never really liked that Dispose(bool) method, so I think this pattern is better if you definitely need a destructor:

public class BetterDisposableClass : IDisposable {

  public void Dispose() {
    CleanUpManagedResources();
    CleanUpNativeResources();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

But, since you already found out that you really don't need one, your pattern is much simpler:

public class ManagedDisposable : IDisposable {

  // ...

  public virtual void Dispose() {
    _otherDisposable.Dispose();
  }

  IDisposable _otherDisposable;

}
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • +1 The explicit separation of managed/unmanaged is neat and logical. But your approach lacks a ``TasteJusticeException`` to enforce code contracts (see Eric Lippert's answer above to see how it's done). – Peter Marks Nov 15 '11 at 01:01
  • 1
    The articles you linked to are interesting and helpful. – Peter Marks Nov 15 '11 at 01:06
  • @PeterMarks: thanks! My approach lacks that exception because I lack Lippert's brilliance! You can add it though! – Jordão Nov 15 '11 at 01:09
-1
 Or if you know that your object that you are trying to dispose of Implements IDisposable
why not do something like this

StreamWriter streamWrt = null
try
{
streamWrt = new StreamWrite();
... do some code here
}
catch (Exception ex)
{
 Console.WriteLine(ex.Message)
}
Finally
{
  if (streamWrt != null)
  {
    ((IDisposable)streamWrt).Dispose();
  }
}
MethodMan
  • 18,625
  • 6
  • 34
  • 52