次の方法で共有


The (often non-) difference between Close and Dispose

Some classes in the .NET framework, such as System.IO.FileStream, have both a Close() and Dispose() method. The natural question is what's the difference, and when you should use one versus the other.

The framework guidelines refer to Close and Dispose in the following context: occasionally you may prefer to use a domain-specific word instead of Dispose; for example, for files you may prefer to expose a method called Close(), because this word may be easier for users to discover. But even in that case, Close should just call Dispose.

That's pretty straightforward, but confusion abounds. A few months ago, while investigating a bug innocently titled "ResourceReader doesn't provide a Dispose method", I realized the cause of the confusion: understanding the variations of Dispose and Close throughout the framework, as well as the guidance going forward, requires a surprising amount of background. If you're confused by conflicting (or seemingly conflicting) information about Close/Dispose in forums, etc, I hope this will help make sense of it all.

What is the difference between Close and Dispose?

In most .NET framework classes, there is no difference between Close() and Dispose(). For example, these methods do the same thing in the System.IO.Stream hierarchy, and it doesn't matter which of the two methods you call. You should call one but not both.

There are exceptions; for example, System.Windows.Forms.Form and System.Data.SqlClient.SqlConnection have different behavior for Close() and Dispose().

But if you're looking for a rule of thumb, it's best if you think of Close() and Dispose() as the same in general, and others as special cases. We'll discuss these exceptions later.

Terminology note: I've been referring to Dispose() to distinguish it from Dispose(bool). Subsequently, when I use "Dispose" I mean Dispose().

But Close sounds less invasive than Dispose...?

This is a common perception about the difference between Close and Dispose, and it's a reasonable guess. We could have set the expectation up front that Dispose is always the name of the method that performs deterministic cleanup. Classes could have added a Close method to do something domain-specific without necessarily implying disposal. But as it stands, they generally do the same thing.

But why would they do the same thing?

This is where it gets weird. Before Whidbey, many classes used this guidance: if there's a domain-specific word that's more discoverable to users than Dispose, hide Dispose by explicitly implementing it, and instead expose the domain-specific word. This guidance was primarily intended for file-related classes, and the domain-specific word in that case is Close.

Any class that hid Dispose and instead exposed Close established that Close and Dispose were equivalent for that class (and its subclasses). Note that it's not obvious that Close and Dispose should do the same thing in general. For Sockets, some users initially guess that Close just closes the Socket (but you can reopen). But Socket.Close does the same thing as Dispose. (FYI, Socket.Disconnect is the method that makes it reopenable.)

Around Whidbey, it was realized that the rules around Dispose needed to be tightened up. C++ destructors -- which are deterministic -- needed to map to Dispose (the general Framework solution for deterministic cleanup). This motivated some investigation in which we realized some practices made it easy for classes to break disposal chains in a class hierarchy: these included hiding Dispose() and making Dispose() virtual.

To help prevent this problem, many framework classes (such as the Stream hierarchy) were cleaned up to ensure Dispose() was public and non-virtual, and the subclasses were properly chained via Dispose(bool).

There are still classes such as ResourceReader in which Dispose is hidden. This is because sealed classes were considered lower priority; the interesting problems happen for class hierarchies, in which Dispose(bool) needs to be chained.

Even after this cleanup, there remain at least 2 classes in which Close and Dispose are different. This is because these classes weren't hiding Dispose for Close: Close and Dispose really did something different for these classes.

Hiding Dispose, explicit implementation...what does that mean?

Hiding Dispose through explicit interface implementation means doing this:

void IDisposable.Dispose()
{
Dispose(true);
}

...as opposed to this:

public void Dispose()
{
Dispose(true);
}

Let's look at the consequences of using a class that's explicitly implements Dispose. ResourceReader explicitly implements Dispose, so let's use that as an example.

ResourceReader rr = new ResourceReader(resourceStream);

The following yields a compile error: 'System.Resources.ResourceReader' does not contain a definition for 'Dispose'

rr.Dispose();    

Casting to IDisposable lets you call Dispose:

((IDisposable)rr).Dispose();

Since Close and Dispose do the same thing, the above is equivalent to:

rr.Close();

Unfortunate side-effects of hiding Dispose

The guidance about hiding Dispose got applied in interesting ways. For example, in some crypto classes, Dispose is hidden and the domain-specific word for disposal is Clear. Shawn Farkas pointed out that this has caused problems: some people don't recognize Clear as a disposal method, and they don't even know they're supposed to dispose the object.

Hiding Dispose causes confusion for many users, who think of Dispose as the go-to method for disposal. From that perspective, hiding Dispose in favor of the domain-specific word can actually make the disposal method less discoverable. The need to cast it to IDisposable is unintuitive to many users.

What about the classes where Close and Dispose are different?

In my search, I only confirmed that 2 classes have different behavior for Close and Dispose: System.Windows.Forms.Form and System.Data.SqlClient.SqlConnection. There could be more because more because my search wasn't exhaustive; I identified likely candidates and only looked into those.

Let's look at System.Windows.Forms.Form. Here are some interesting excerpts from the docs for Form.Close:

When a form is closed, all resources created within the object are closed and the form is disposed.

The one condition when a form is not disposed on Close is when it is part of a multiple-document interface (MDI) application, and the form is not visible. In this case, you will need to call Dispose manually to mark all of the form's controls for garbage collection.

The second part seems to indicate they're different, but it's a bit buried in the description. Let's open it in reflector to be sure.

 public void  Close ()
{
    if (base.GetState(0x40000))
    {
        throw new InvalidOperationException(SR.GetString("ClosingWhileCreatingHandle", new object[] { "Close" }));
    }
    if (base. IsHandleCreated ) 
    {
        this.closeReason = CloseReason.UserClosing;
        base.SendMessage(0x10, 0, 0);
    }
    else
    {
        base.Dispose();
    }
}

So indeed, it won't go down the Dispose path if base.IsHandleCreated is true.

For System.Data.SqlClient.SqlConnection, the primary difference between Close and Dispose is that Dispose nulls out _userConnectionOptions and _poolGroup, as shown below. While I'm not familar with the code in System.Data.dll, by browsing around in reflector it looks like this has the effect of making the SqlConnection object unusable; i.e. it throws if you try to re-open. Anyone familiar with this class can feel free to chime in if there's a better way to describe this. :)

        this._userConnectionOptions = null;
        this._poolGroup = null;

Guidance now

Joe Duffy's blog has the complete updated Dispose Design Guidelines. This is great reading for detailed guidance around Dispose and finalization (including when they should be used, implications for class hierarchies, etc):

https://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae

Based on this experience, I'd encourage you to think very carefully before attempting to use a domain-specific name instead of Dispose -- we're now at a place where users are familiar with Dispose and it is the most discoverable method name.

I think we should also impose some order by making it very clear in our docs when Close and Dispose are the same, and clearly call out any cases that differ.

Thanks to Krzysztof Cwalina, Joe Duffy, and Brian Grunkemeyer, who provided valuable input to my barrage of emails on this topic. :)

Comments

  • Anonymous
    March 15, 2008
    PingBack from http://www.mydomains.co.cc/the-often-non-difference-between-close-and-dispose

  • Anonymous
    March 16, 2008
    Excellent article, Kim!

  • Anonymous
    March 17, 2008
    I made some quick updates in my recent post to describe the difference between Close and Dispose for

  • Anonymous
    March 18, 2008
    The comment has been removed

  • Anonymous
    March 18, 2008
    Hi Koji, The recommendation is that the Close()/Dispose()/ Dispose(true) path should only throw in critical situations, but as you mentioned, the finalize path -- Dispose(false) -- should never throw exceptions. In fact, the ordering of Dispose(true) and GC.SuppressFinalize can be used to handle such situations, where some critical error happens during Dispose(), but you get a last-ditch attempt to clean up during finalize. Section 1.1.3 of the detailed Dispose guidelines discusses this issue: http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae Thanks, Kim