Click here to Skip to main content
16,021,209 members
Articles / Programming Languages / C#

The Dispose(bool disposing) Pattern is Broken

Rate me:
Please Sign up or sign in to vote.
4.66/5 (17 votes)
29 Apr 2020CPOL7 min read 42.6K   20   52
SafeHandles came to solve the problem, but the MSDN documentation on them is also broken.
In .NET, the relationship between Dispose() and finalizers is confusing. In this post, we take a look at the Dispose pattern, the snowball effect of the pattern and why the pattern is broken. Then, we take a look at one of the solutions, namely, SafeHandles. Finally, we reanalyze the problem.

Introduction

Since .NET appeared, one of the most confusing things was the relationship between Dispose() (from the IDisposable interface) and finalizers.

Original Idea

The original idea was very simple: The Garbage Collector does the memory and resource cleanup for us, so we don't need to manage memory and resources manually.

In practice, things got much more complicated. The Garbage Collector might not run when we need it to run (as it is said, it is "non-deterministic"). Also, static fields keep objects alive and our code can interact with native code, and we need more control on objects and memory lifetime.

This justified having finalizers to release external resources when the Garbage Collector ran, and also Dispose() methods to release any resource "as soon as possible" without waiting for the Garbage Collector to run. And that created a snowball effect, where we change entire class hierarchies to implement a hard pattern, as usually we want to have control when resources are freed (the Dispose() method and the IDisposable interface) and also a finalizer, as any "unmanaged" data needs to be released even if we (or users of our code) don't invoke Dispose().

The Original Solution - Dispose Pattern

The original solution is the Dispose pattern. It is not really easy, as it consists of:

  • having a finalizer (calling Dispose(false););
  • having a Dispose() (calling Dispose(true); and GC.SuppressFinalize(this););
  • having an overloaded Dispose(bool disposing); which decided what to really do according to disposing. This overload could be virtual or not, increasing the complexity of the pattern.

The "Snowball" Effect of the Dispose Pattern

Having to decide if an object needs to be disposable or not is, in itself, problematic. And the Dispose pattern interacts really badly with base-classes and frameworks.

If a framework object might ever need "predictable" destruction, that means we need a Dispose() or similar method. But, as a framework or base class, that also means objects from sub-classes might possibly have unmanaged data, meaning we "need" the finalizer.

Now, the entire pattern needs to be used on any base class that can possibly have a sub-class dealing with unmanaged data.

So, instead of having something as simple as:

C#
public abstract class MyBaseClass:
  IDisposable
{
  public virtual void Dispose()
  {
  }
}

We need to have something like the following instead:

C#
public abstract class MyBaseClass:
  IDisposable
{
  ~MyBaseClass()
  {
    Dispose(false);
  }

  public void Dispose()
  {
    GC.SupressFinalize(this);
    Dispose(true);
  }

  protected virtual void Dispose(bool disposing)
  {
  }
}

It is important to notice that in the first block of code, Dispose() was virtual. In the second, Dispose() isn't supposed be virtual and, instead, Dispose(bool disposing) needs to be virtual... and also not-public, as it is not supposed to be invoked by user code.

What is "disposing"?

When I first saw Dispose() with a "disposing" parameter, I got really confused. What is "disposing" in a method already named "Dispose"?

I really think that if the Dispose pattern was named Release pattern and we had an "isFromManualDispose" argument, things would be less confusing. It would still be a problematic pattern, but it would be a little easier to understand what the argument means.

Why Is the Pattern Broken?

"Broken" is too strong, to be honest, but I wanted something that grabbed the reader's attention. It is bad because it depends on too many methods and concepts, and it's also confusing. Even if "it works" when implemented correctly, it:

  • is hard for new developers;
  • means any framework class needs to have a Dispose(bool) to deal with possibly "unsafe" data even when the entire framework doesn't use any unsafe code;
  • means that anybody inheriting those classes needs to know what to do with that "hateful" disposing argument;
  • means it breaks the Single Responsibility Principle. A framework class (or just any base class) is not supposed to deal with all of that "just in case" a sub-class needs it.

The Solution: SafeHandles

Sometime later, Microsoft noticed the bad pattern and tried to fix it. That's when we got SafeHandles.

The entire idea of having SafeHandles is that, instead of having our own classes dealing with "managed and unmanaged memory", our classes should just deal with managed memory or, if really needed, deal with a "safe handle", which is the object that will have a finalizer and will really manage the lifetime of the unmanaged data.

I really think Microsoft documented it right in the beginning, but when I tried to find their good example, I just found the latest document, which is completely "busted". They explain why SafeHandles are good and help us avoid the bad Disposable pattern but then show a class that uses a SafeHandle and also implements the Dispose pattern for no real benefit!

After reading the document, it seems that now we have a new pattern on top of the hard pattern. But that is just plain wrong. The new pattern came to replace the old, and hard, pattern. Not to add to it.

For those who are curious, I am referring to this page.

In that page, on the source code, there's even this comment:

C#
// No finalizer is needed. The finalizer on SafeHandle
// will clean up the MySafeFileHandle instance,
// if it hasn't already been disposed.
// Howerver, there may be a need for a subclass to
// introduce a finalizer, so Dispose is properly implemented here.
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
protected virtual void Dispose(bool disposing)
  • The comment about a subclass introducing a finalizer is another issue with the Dispose pattern. Some people argue that we shouldn't add a finalizer until we need it, but then if the base class ever decides to use a finalizer, we might have double-disposes happening. We should avoid all that confusion;
  • The word "Howerver" is misspelled in that sample. Editors, please don't fix it here in the article;
  • They say "Dispose is properly implemented here", but they actually forgot to to check the disposing argument. They were not supposed to call _handle.Dispose() when disposing is false.

The "new" Pattern

SafeHandles were created with the purpose of simplifying the pattern. When using SafeHandles, we should not worry about the entire Dispose pattern. Instead, we should just need to know if we are implementing Dispose() or not. Just the simple Dispose(), not that weird Dispose(bool disposing).

Then, if we are dealing with Windows Handles, we use the appropriate safe-handles and, if an object is never disposed and gets Garbage Collected, the SafeHandles do the work for us.

This, actually, is the base for a new and improved pattern, even when we don't have SafeHandles for the kind of unmanaged memory or data we are dealing with.

What Exactly Is the New Pattern When Not Using SafeHandles?

Simple rules:

  • Public classes should not have a destructor or a Dispose(bool). If they are disposable, they just implement IDisposable with the standard Dispose();
  • If they use any data that might need a destructor, they should use a helper class to hold that data. That's exactly what SafeHandles are: Helper classes that hold the data and deal with the destructor for you (and nothing else).

In a way, that's all.

How to Implement One of the Helper Classes?

The helper classes will need to have a destructor, and possibly a Dispose() to allow for an early release of the resources. But those helper classes can possibly be sealed and avoid any logic to deal with managed + unmanaged data. They exist with the sole purpose of dealing with the release of unmanaged data, so there's no need to check for that. They should not do anything else, as that would be the work of the main class. They are just simple helpers.

Reanalyzing the Problem

  • Dispose pattern:

    Class with Dispose(bool), a finalizer that calls Dispose(false), an overloaded Dispose() that calls Dispose(true), and that bool disposing makes many developers wonder what is really going on, even if their classes never use unmanaged data.

  • New pattern:

    Just a simple implementation of the IDisposable interface if the class needs deterministic cleanup, which should be virtual if the class can be inherited. If any unmanaged data is used by the class, a helper class (which can be the same for all unmanaged data, like a SafeHandle) is used. That's all. No "disposing" argument and no odd implementations because of such an argument.

    By default, base classes and sub-classes will be much simpler when they don't hold unmanaged data. They will still be able to hold unmanaged data if needed, but will delegate the "release" of that data to a helper class.

History

  • 30th April, 2020: Changed the title to include the bool(disposing). Introduced a comment that "Howerver" is misspelled in the original document;
  • 29th April, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
GeneralMy vote of 2 Pin
GerVenson30-Apr-20 0:34
professionalGerVenson30-Apr-20 0:34 
GeneralRe: My vote of 2 Pin
spi30-Apr-20 0:46
professionalspi30-Apr-20 0:46 
GeneralRe: My vote of 2 Pin
Stephen Russell AAI MCT30-Apr-20 3:03
professionalStephen Russell AAI MCT30-Apr-20 3:03 
GeneralRe: My vote of 2 Pin
Paulo Zemek30-Apr-20 4:23
Paulo Zemek30-Apr-20 4:23 
GeneralRe: My vote of 2 Pin
Paulo Zemek30-Apr-20 8:13
Paulo Zemek30-Apr-20 8:13 
GeneralRe: My vote of 2 Pin
Enigmativity30-Apr-20 16:29
Enigmativity30-Apr-20 16:29 
GeneralRe: My vote of 2 Pin
wkempf6-May-20 4:36
wkempf6-May-20 4:36 
GeneralRe: My vote of 2 Pin
Enigmativity6-May-20 22:10
Enigmativity6-May-20 22:10 
GeneralRe: My vote of 2 Pin
wkempf7-May-20 2:30
wkempf7-May-20 2:30 
QuestionInteresting article, but I have some questions Pin
Stephen Russell AAI MCT29-Apr-20 21:31
professionalStephen Russell AAI MCT29-Apr-20 21:31 
AnswerRe: Interesting article, but I have some questions Pin
spi30-Apr-20 0:53
professionalspi30-Apr-20 0:53 
GeneralRe: Interesting article, but I have some questions Pin
Stephen Russell AAI MCT30-Apr-20 2:53
professionalStephen Russell AAI MCT30-Apr-20 2:53 
GeneralRe: Interesting article, but I have some questions Pin
spi30-Apr-20 3:38
professionalspi30-Apr-20 3:38 
GeneralRe: Interesting article, but I have some questions Pin
wkempf6-May-20 4:44
wkempf6-May-20 4:44 
AnswerRe: Interesting article, but I have some questions Pin
Paulo Zemek30-Apr-20 4:22
Paulo Zemek30-Apr-20 4:22 
AnswerRe: Interesting article, but I have some questions Pin
Paulo Zemek30-Apr-20 5:47
Paulo Zemek30-Apr-20 5:47 
AnswerRe: Interesting article, but I have some questions Pin
Enigmativity30-Apr-20 16:30
Enigmativity30-Apr-20 16:30 
GeneralRe: Interesting article, but I have some questions Pin
Paulo Zemek1-May-20 16:57
Paulo Zemek1-May-20 16:57 
QuestionSafeHandles are not an attempt to fix `IDisposable` Pin
Enigmativity29-Apr-20 21:21
Enigmativity29-Apr-20 21:21 
AnswerRe: SafeHandles are not an attempt to fix `IDisposable` Pin
Paulo Zemek30-Apr-20 4:18
Paulo Zemek30-Apr-20 4:18 
GeneralRe: SafeHandles are not an attempt to fix `IDisposable` Pin
spi30-Apr-20 4:23
professionalspi30-Apr-20 4:23 
My 2 cents: the difference between "Dispose Pattern" and "IDispsable" is not clear enough.

I'd propose to use "Standard Dispose Pattern" to denote the "recommended" implementation with finalizer + disposing parameter. This is what your article is really about.
GeneralRe: SafeHandles are not an attempt to fix `IDisposable` Pin
Enigmativity30-Apr-20 13:38
Enigmativity30-Apr-20 13:38 
GeneralRe: SafeHandles are not an attempt to fix `IDisposable` Pin
spi30-Apr-20 23:51
professionalspi30-Apr-20 23:51 
GeneralMy vote of 1 Pin
Enigmativity29-Apr-20 21:07
Enigmativity29-Apr-20 21:07 
GeneralRe: My vote of 1 Pin
spi30-Apr-20 0:55
professionalspi30-Apr-20 0:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.