Do you need to worry about memory management in .NET?
Although .NET is a managed environment with a garbage collector, this doesn’t mean you don’t have to worry about memory management. Of course, it’s different then when using a language like C++ where you have to explicitly free memory but in C# you still have to think about memory management.
The garbage collector in .NET is a big help in freeing memory and when you were only dealing with managed objects, that would be enough. However, in most applications you don’t only deal with managed objects. Maybe you access a file on disk, a web service or a database. Those resources are unmanaged and they do have to be freed explicitly.
If you won’t do anything, the garbage collector will eventually kick in and remove unused objects from memory. The classes in the .NET Framework that deal with unmanaged resources implement what’s called a finalizer. The finalizer runs code to close your file or database handle. So with a well-designed class, the finalizer will eventually cleanup your unmanaged resources. But waiting for the garbage collector makes your code unreliable because you can’t tell for sure when an external handle will be closed.
Meet IDisposable
To cleanup memory in a deterministic way, the .NET framework offers the IDisposable interface. This is interface is pretty simple:
public interface IDisposable
{
void Dispose();
}
The interface has only one method called Dispose. Calling this method will free any unmanaged resources that an object has. This way you can explicitly determine when a resource should be freed. A lot of the types in the .NET framework implement IDisposable. For example, when you create a new text file you get back a StreamWriter that implements IDisposable:
IDisposable disposableFile = File.CreateText("temp.txt");
disposableFile.Dispose();
When dealing with disposable objects you should call Dispose as soon as possible. But what if an exception happens before you can call Dispose? To make sure that some code always runs, with or without an exception, C# offers you the finally block:
IDisposable disposableFile = null;
try
{
disposableFile = File.CreateText("temp.txt");
}
finally
{
disposableFile.Dispose();
}
If some exception happens in the try block, the Dispose method will always be called. This way you can make sure that your resources will be released. Writing a try/finally block every time you deal with an IDisposable quickly becomes cumbersome. Fortunately, C# offers some syntactic sugar that can help you.
Desugaring using
When dealing with an IDisposable object, you can use a special statement called the using statement. The previous code with the try/finally block can be changed into the following:
using (var disposableFile = File.CreateText("temp.txt"))
{
// do something with your file
}
This is a much nicer syntax for working with disposable objects. When you look at the IL this generates you will see the following:
method private hidebysig static void Main(string\[\] args) cil managed
{
.entrypoint
// Code size 34 (0x22)
.maxstack 2
.locals init (\[0\] class \[mscorlib\]System.IO.StreamWriter disposableFile, [1] bool CS$4$0000)
IL_0000: nop
IL_0001: ldstr "temp.txt"
IL_0006: call class[mscorlib]System.IO.StreamWriter [mscorlib]System.IO.File::CreateText(string)
IL_000b: stloc.0
.try
{
IL_000c: nop
IL_000d: nop
IL_000e: leave.s IL_0020
} // end .try
finally
{
IL_0010: ldloc.0
IL_0011: ldnull
IL_0012: ceq
IL_0014: stloc.1
IL_0015: ldloc.1
IL_0016: brtrue.s IL_001f
IL_0018: ldloc.0
IL_0019: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_001e: nop
IL_001f: endfinally
} // end handler
IL_0020: nop
IL_0021: ret
} // end of method Program::Main
As you can see, your small using statement generates quite a bunch of IL code. When you look at line 12 and 18 you see the try and finally statements. And in the finally block you see a call to Dispose on the StreamWriter.
And that’s how you can easily work with unmanaged objects in C#. Make sure that you always dispose of objects that implement IDisposable. The using statement is the easiest way to do this and by desugaring it you now understand why.
And if you ever find yourself creating a class that uses unmanaged resources, think of IDisposable and a finalizer.
Feedback? Questions? Please leave a comment!