Compartilhar via


If I'm living in a managed world, why do I need Dispose()?

It's inevitable that at some point a C/C++ programmer migrating to .NET and C# will run into the need to use Dispose() or IDisposable and ponder the reason for their existence. After all - the idea of a managed environment like .NET and a language like C# is that you don't have to worry about memory management problems any more right? While that is, generally speaking true, there are more things an application has to keep track of then just memory. This is what Dispose is really for but the reasons are not clearly understood by most C/C++ developers, particularly those used to controlling things very closely in embedded devices. Furthermore, the correct implementation of Dispose() is often a source of considerable confusion. This article is intended to help clarify that and make life easier for developers to create code that does the right thing with regard to resource management.

Memory is just one type of a broader class of things often called "resources". While the .NET framework manages memory for you it doesn't manage all the other types of resources (and, quite frankly, can't ) Other resource types can include record locks in a database, a reservation for exclusive use of some hardware, a requirement that a piece of hardware maintains some power state until the application is done using it etc.. All of these things are outside the realm of what the run-time knows how to handle. Now us old time C++ programmers would say - "Isn't that what a destructor is for?" In C++, that's correct. But not in the managed realm of .NET or C#. To understand that we must first get a basic understanding of what a garbage collector actually does.

Garbage collection is an automated mechanism for reclaiming the memory of objects that are no longer needed in an application. There are a number of algorithms used for determining if an object isn't used anymore but the basic idea is to scan objects in the heap, the global namespace and any executing stack frames to find any objects (or chain of objects) that are not referenced by something in the global namespace or an active execution stack. When it does find such an object it can release the memory for it.  At the time the collector is about to release the memory it can call an optional finalization method on the object to release any resources that aren't managed by the garbage collecting heap. This sort of acts like calling delete on a pointer in C++ which calls the destructor before releasing the memory. The critical distinction is that the finalization function may never be called! It is entirely possible that a system doesn't use the heap to a significant enough extent that the garbage collector never runs. This is classically referred to as non-deterministic finalization because when, or if, the finalizer runs is not something you can determine ahead of time. This means you don't know when or if the additional resources will be properly released. That could be a bad thing!

Of course the designers of the .NET Framework were aware of the problem of non-deterministic finalization and provided a solution in the form of the IDisposable interface. (The designers of C# leveraged that to bring the issue to the forefront of the language in the form of the "using" statement we'll see later. For details on the thinking process that led to this solution see: Resource Management). The IDisposable interface contains one single method called Dispose(). The implementation of Dispose is responsible for releasing all of the unmanaged resources owned by a class and it's contained fields. The idea is that you call Dispose() on the class as soon as you are done with using it so it can release those resources - sort of like calling the destructor without releasing the memory. The garbage collector will eventually get around to releasing the memory if it needs to at some later point. Just before releasing the memory the garbage collector will call the finalizer to release any other resources.

The call to the finalizer after Dispose() is already called introduces the catch that confuses most developers as the finalizer would run at some no-deterministic point and try to release resources that were no longer valid - usually resulting in an exception that is out of context with what else is going on in the application. To deal with that a standard pattern has emerged for implementing IDisposable. To illustrate the pattern let's look at the code from my BLOG post about interop. The Dispose() method will call PWM.Reserve(false); to release the allocated resources as does the finalizer. To prevent a duplicate release of the pin resources the Dispose() method calls GC.SupressFinalize(this). That call tells the garbage collector not to run the finalizer when it gets around to releasing the memory as it is not needed anymore. Unfortunately this implementation has a small issue which in some designs could be a larger one. The problem is that both Dispose() and the finalizer are releasing the managed resource directly. If there are multiple resources to release this ends up duplicating a bunch of code and makes for errors where you handle a resource correctly in one place but not another. In the PWM sample that's no big deal but larger classes it can be a problem. 

The standard pattern for solving the duplication of code is to introduce a new overloaded version of Dispose() that takes a single bool parameter. (e.g. void Dispose(bool Disposing) ) Then IDisposable,Dispose() just calls Disposing(true) and the finalizer calls Dispose(false). The Disposing flag identifies the context of the call. If Disposing is true then Dispose is being called directly by application code and it should call Dispose on any fields that are disposable and it should release all unmanaged resources. If it is being called from the finalizer then it should just release the unmanaged resources as the finalizers of all the fields and base class will take care of themselves if needed. (If the class is not sealed then this method should be virtual. ) 

There is yet another issue to consider with regard to calling Dispose() on fields of an object that is - ownership. Who owns an object instance? e.g. Who is responsible for calling Dispose()? This is something you need to think about in the design of your object model and document carefully. If your object creates all of of the object instances used in it's fields and it never hands out a reference to it's internal objects,then it can Dispose of them entirely at it's own discretion. However, if it ever exposes those instances you now have an issue where there are multiple references and it's hard to know who is using the reference and if it is safe to release the resource. Thus it is important to prevent exposing such objects when possible and, if required, always document the expectations. This is often done by simply allowing the object to be disposed and any external code that managed to retrieve an instance has to deal with an exception for trying to access a disposed object some time later. To support that any object that supports IDisposable should also contain support for itself to generate an exception when any method is called after the object has been disposed.

If all of this has your head spinning, fear not! The FusionWare.SPOT library has a nice implementation of the bulk of the dispose functionality that you can derive a class from to get the correct behavior. The DisposableObject class has most of the standard handling rolled up into one nice class that allows you to simply override the virtual Dispose(bool) method and most of the rest is covered for you. If your class is derived from something else and you can't use DisposableObject you can at least learn from its implementation and incorporate that same support in your own classes.

Now that we've covered a lot about implementing the insides of the IDisposable interface let's have a look at what you do with it from the outside. One option in C# is to do this:

using System;

using Microsoft.SPOT;

using Freescale.iMXS;

using System.Threading;

namespace PWMTester

{

    public class Program

    {

        public static void Main()

        {

            PWM p = new PWM();

        

            p.Configure(100,50);

            Thread.Sleep(100);

            p.Disable();

            p.Dispose();

        }

    }

}

While this code does call Dispose() to release the unmanaged resources there are a few problems with it. The first is that it makes the call to dispose a manual operation. If the code between the new and the Dispose() is anything but trivial then the liekelihood of forgetting to call Dispose() in some case is rather high. The second and more sinister issue is that it doesn't handle calling dispose in the face of an exception. If an exception is thrown after new but before Dispose() is called then the resource remains locked up until the garbage collector gets around to finalizing it, which could be never. To resolve these issues in a clean and simple fashion the C# language designers developed the "using" statement. By applying "using" in C# you can effectively provide an exception safe scope where an object will always be properly disposed before the execution exits that scope. The follwoing is a modified version of the sample app above with the "using" support.

using System;

using Microsoft.SPOT;

using Freescale.iMXS;

using System.Threading;

namespace PWMTester

{

    public class Program

    {

        public static void Main()

        {

            using(PWM p = new PWM())

            {

                p.Configure(100, 50);

                Thread.Sleep(100);

                p.Disable();

            }

        }

    }

}

 

WIth this new version the Dispose() method of "p" will always be called even if an exception occured and no matter how the code exits the enclosing scope of the using statement. (This design pattern is formally known as Resource Acquisition is Initialization or RAII. I'll give bonus points to anyone that can explain where they came up with that name as it's not so much about initialization as it is about the clean up...)  

 

Resource management is potentially a tricky area of programming, however, the use of Dispose() and the using statement in C# can help manage the problems in a consistent manner. Hopefully, at this point, no-one's heads have exploded (as that would make quite a mess on the computer screen for someone to clean up) and you've got a grip on why we need Dispose() and how to use it. There are a lot of rules that go onto using Dispose() fortunately there is a tool called FxCop that is available for analyzing this sort of thing in your code automatically. (Unfortunately, the curent versions of FxCop can't process the .NET Micro Framework assemblies so it is not available for the .NET MF right now :-( )

Comments

  • Anonymous
    July 04, 2008
    In working with C# myself and with customers I've discovered an area of confusion and hidden bugs. Resource