![]() |
Languages »
C# »
General
Intermediate
Garbage collection and resource deallocationBy Cristian PratsAn article about IDisposable and the Garbage Collector in .NET. |
C#, Windows, .NET 1.0, .NET 1.1VS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
In non-garbage collected languages like C++, programmers must take care of memory and resource management, you know exactly when your objects are destroyed, implicitly (scope rules) or explicitly (object deletion). The counterpart is that working with pointers commonly leads to memory leaks and resources never freed. Opposed to this, the .NET environment implements a garbage collector that automates memory management and handles unused object destruction, making the programmer's life simpler. Objects are not destroyed when they are out of scope, and you can�t destroy them explicitly. It�s the GC�s job to destroy them and free the memory used, this is called �collecting� and it is done under certain circumstances that won�t be explained here. This means that the GC collects when needed, and you have no control of the destruction of your objects.
Therefore, there are no special considerations to take when writing a class that doesn't make use of resources. But when your class holds a critical resource, you must release, you just can't rely on the GC because you can�t determine when it will run. The GC, upon collection, calls the finalizer of the object (in C#, the finalizer is the object destructor), thus, this is not a good place to release the resources owned by an object. In the .NET class library, the garbage collector is implemented as System.GC in the assembly mscorlib.
There is an interface in .NET you must implement to "mark" your class in order to provide a mechanism to release resources. This interface is IDisposable (its full name is System.IDisposable and can be found in assembly mscorlib). And it has only one method you must implement: Dispose(). This method does the cleaning work in your class. The destructor should call Dispose() to ensure a clean exit in case you forgot to explicitly call it, but not if it has already been called. A proposed implementation is:
public class A : IDisposable
{
protected bool disposed = false;
public A()
{
// acquire resource
}
~A()
{
Dispose(false);
}
public void doSomething()
{
if (disposed)
throw new ObjectDisposedException("object's name");
else
{
// do something
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Call Dispose() on managed objects
}
// release unmanaged resource(s) held by this object
disposed = true;
}
}
}
A virtual method Dispose(bool) manages resource deallocation for both managed and unmanaged resources. disposing is a parameter to tell who called the method. Dispose() will call Dispose(true) and the destructor will call Dispose(false). When instantiating class A, all resource acquisition is made in the constructor, and when we are done with the object, we need to call Dispose(true) from the public method Dispose() to release all (both managed and unmanaged) resources. The destructor, called by the GC, also calls Dispose(false) to clean unmanaged resources to ensure there are no leaks in our application. Calling GC.SuppressFinalize() tells the GC not to call the object's destructor, for it just does what we have already done. Dispose(bool disposing) is declared as virtual so our derived classes can call it. disposed is a flag to avoid calling Dispose() more than once and to avoid using a released resource, an ObjectDisposedException is thrown if this happens. Note that this implementation is not thread-safe, race conditions can happen.
The need to implement Dispose(bool) arises because an object may hold both managed objects that need to be disposed and unmanaged resources. So, if you call Dispose(true), you take care of all resources owned and that's all, but if you don't, the GC will take care and the owner object should no longer call Dispose(true) on each of the owned managed objects because they may be already finalized. But you must always clean unmanaged resources by calling Dispose(false).
Derived classes can easily extend this implementation:
public class B : A
{
public B()
{
// acquire more resources
}
~B()
{
}
protected override void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Call Dispose() on our managed objects
}
// release unmanaged resources acquired in our constructor
base.Dispose(disposing);
}
}
}
Creating an instance of B calls B's constructor, which in turn calls A's default constructor before exiting. B's destructor does nothing, it only follows the destruction chain calling A's destructor (in fact, we can obviate it, and when the GC claims the object, it will call A's destructor because it's inherited by B). Anyway, we reach A's destructor where Dispose(false) is called. But Dispose(bool) is overridden, it was declared as virtual in A so the right Dispose(bool) in the right class is called in A's destructor. If it weren't declared as virtual, A.Dispose(bool) would be called thereby not calling B.Dispose(bool) and never releasing B's unmanaged resources.
Taking a closer look to our example, the output of Ildasm.exe says:
(~A() is translated as A.Finalize() when compiled to IL).
.method family hidebysig virtual instance void Finalize() cil managed
{
// Code size 17 (0x11)
.maxstack 2
.try
{
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: callvirt instance void Test.A::Dispose(bool)
IL_0007: leave.s IL_0010
} // end .try
finally
{
IL_0009: ldarg.0
IL_000a: call instance void [mscorlib]System.Object::Finalize()
IL_000f: endfinally
} // end handler
IL_0010: ret
} // end of method A::Finalize
The compiler automatically generates code to control exceptions, it puts our code inside a try/finally block ensuring chain destruction by calling the base class destructor inside the finally block. Also, if the virtual call to Dispose(bool) throws an exception and it is not handled, the program continues with the execution flow. If exceptions are not properly handled inside every Dispose() method, resource leaks can occur.
C# has the using statement that allows you to acquire one or multiple resources, use them in a block, and automatically call Dispose() in each of them.
public class AppClass
{
public static void Main()
{
using (B a = new B())
{
Console.WriteLine(a.ToString());
};
}
}
This block of code translates to IL in the following manner:
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 30 (0x1e)
.maxstack 1
.locals init ([0] class Test.B a)
IL_0000: newobj instance void Test.B::.ctor()
IL_0005: stloc.0
.try
{
IL_0006: ldloc.0
IL_0007: callvirt instance string [mscorlib]System.Object::ToString()
IL_000c: call void [mscorlib]System.Console::WriteLine(string)
IL_0011: leave.s IL_001d
} // end .try
finally
{
IL_0013: ldloc.0
IL_0014: brfalse.s IL_001c
IL_0016: ldloc.0
IL_0017: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_001c: endfinally
} // end handler
IL_001d: ret
} // end of method AppClass::Main
That is the same as coding:
B a = new B();
try
{
Console.WriteLine(a.ToString());
}
finally
{
if (a != null)
a.Dispose();
}
using also allows declaring more than one identifier of the same type:
using (B a1 = new B(), a2 = new B())
{
// do something
}
The using statement executes the statement block disposing the objects when the end of the statement is reached or when an exception is thrown.
The non-deterministic object destruction provided by the GC forces us to take special care when working with objects wrapping resources:
IDisposable. IDisposable provides the contract a class must implement for correct resource use.
Dispose() calls Dispose(true) to release all resources and tells the GC not to call the destructor.
Dispose(bool) as virtual and calling the inherited method from the derived classes.
Dispose() invocation through the using statement.
Dispose(false) in the destructor. Just in case...
Dispose() more than once shouldn�t do anything.
ObjectDisposedException. | You must Sign In to use this message board. | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 2 Nov 2004 Editor: Smitha Vijayan |
Copyright 2004 by Cristian Prats Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |