C# Programming/Object Lifetime
Introduction
editAll computer programs use up memory, whether that is a variable in memory, opening a file or connecting to a database. The question is how can the runtime environment reclaim any memory when it is not being used? There are three answers to this question:
- If you are using a managed resource, this is automatically released by the Garbage Collector
- If you are using an unmanaged resource, you must use the IDisposable interface to assist with the cleanup
- If you are calling the Garbage Collector directly, by using
System.GC.Collect()
method, it will be forced to tidy up resources immediately.
Before discussing managed and unmanaged resources, it would be interesting to know what the garbage collector actually does.
Garbage Collector
editThe garbage collector is a background process running within your program. It is always present within all .NET applications. Its job is to look for objects (i.e. reference types) which are no longer being used by your program. If the object is assigned to null, or the object goes out of scope, the garbage collector will mark the object be cleaned up at some point in the future, and not necessarily have its resources released immediately!
Why? The garbage collector will have a hard time keeping up with every de-allocation you make, especially at the speed the program runs and therefore only runs when resources become limited. Therefore, the garbage collector has three "generations".
- Generation 0 - the most recently created objects
- Generation 1 - the mid-life objects
- Generation 2 - the long term objects.
All reference types will exist in one of these three generations. They will firstly be allocated to Gen 0, then moved to Gen 1 and Gen 2 depending on their lifetime. The garbage collector works by removing only what is needed and so will only scan Gen 0 for a quick-fix solution. This is because most, if not all, local variables are placed in this area.
For more in-depth information, visit the MSDN Article for a better explanation.
Now you know about the garbage collector, let's discuss the resources that it is managing.
Managed Resources
editManaged resources are objects which run totally within the .NET framework. All memory is reclaimed for you automatically, all resources closed and you are in most cases guaranteed to have all the memory released after the application closes, or when the garbage collector runs.
You do not have to do anything with them with regards to closing connections or anything, it is a self-tidying object.
Unmanaged Resources
editThere are circumstances where the .NET framework world will not release resources. This may be because the object references resources outside of the .NET framework, like the operating system, or internally references another unmanaged component, or that the resources accesses a component that uses COM, COM+ or DCOM.
Whatever the reason, if you are using an object that implements the IDisposable
interface at a class level, then you too need to implement the IDisposable
interface too.
public interface IDisposable
{
void Dispose();
}
This interface exposes a method called Dispose()
. This alone will not help tidy up resources, as it is only an interface, so the developer must use it correctly in order to ensure the resources are released. The two steps are:
- Always call Dispose() on any object that implements IDisposable as soon as you are finished using it. (This can be made easier with the using keyword)
- Use the finalizer method to call Dispose(), so that if anyone has not closed your resources, your code will do it for them.
Dispose pattern
editOften, what you want to clean up varies depending on whether your object is being finalized. For example, you would not want to clean up managed resources in a finalizer since the managed resources could have been reclaimed by the garbage collector already. The dispose pattern can help you implement resource management properly in this situation:
public class MyResource : IDisposable
{
private IntPtr _someUnmanagedResource;
private List<long> _someManagedResource = new List<long>();
public MyResource()
{
_someUnmanagedResource = AllocateSomeMemory();
for (long i = 0; i < 10000000; i++)
_someManagedResource.Add(i);
...
}
// The finalizer will call the internal dispose method, telling it not to free managed resources.
~MyResource()
{
this.Dispose(false);
}
// The internal dispose method.
private void Dispose(bool disposing)
{
if (disposing)
{
// Clean up managed resources
_someManagedResource.Clear();
}
// Clean up unmanaged resources
FreeSomeMemory(_someUnmanagedResource);
}
// The public dispose method will call the internal dispose method, telling it to free managed resources.
public void Dispose()
{
this.Dispose(true);
// Tell the garbage collector to not call the finalizer because we have already freed resources.
GC.SuppressFinalize(this);
}
}
Applications
editIf you are coming to C# from Visual Basic Classic you will have seen code like this:
Public Function Read(ByRef FileName) As String
Dim oFSO As FileSystemObject
Set oFSO = New FileSystemObject
Dim oFile As TextStream
Set oFile = oFSO.OpenTextFile(FileName, ForReading, False)
Read = oFile.ReadLine
End Function
Note that neither oFSO nor oFile are explicitly disposed of. In Visual Basic Classic this is not necessary because both objects are declared locally. This means that the reference count goes to zero as soon as the function ends which results in calls to the Terminate event handlers of both objects. Those event handlers close the file and release the associated resources.
In C# this doesn't happen because the objects are not reference counted. The finalizers will not be called until the garbage collector decides to dispose of the objects. If the program uses very little memory this could be a long time.
This causes a problem because the file is held open which might prevent other processes from accessing it.
In many languages the solution is to explicitly close the file and dispose of the objects and many C# programmers do just that. However, there is a better way: use the using statement:
public read(string fileName)
{
using (TextReader textReader = new StreamReader(filename))
{
return textReader.ReadLine();
}
}
Behind the scenes the compiler turns the using statement into try ... finally and produces this intermediate language (IL) code:
.method public hidebysig static string Read(string FileName) cil managed
{
// Code size 39 (0x27)
.maxstack 5
.locals init (class [mscorlib]System.IO.TextReader V_0, string V_1)
IL_0000: ldarg.0
IL_0001: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(string)
IL_0006: stloc.0
.try
{
IL_0007: ldloc.0
IL_0008: callvirt instance string [mscorlib]System.IO.TextReader::ReadLine()
IL_000d: stloc.1
IL_000e: leave IL_0025
IL_0013: leave IL_0025
} // end .try
finally
{
IL_0018: ldloc.0
IL_0019: brfalse IL_0024
IL_001e: ldloc.0
IL_001f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0024: endfinally
} // end handler
IL_0025: ldloc.1
IL_0026: ret
} // end of method Using::Read
Notice that the body of the Read function has been split into three parts: initialisation, try, and finally. The finally block includes code that was never explicitly specified in the original C# source code, namely a call to the destructor of the Streamreader instance.
See Understanding the 'using' statement in C# By TiNgZ aBrAhAm.
See the following sections for more applications of this technique.
Resource Acquisition Is Initialisation
editThe application of the using statement in the introduction is an example of an idiom called Resource Acquisition Is Initialisation (RAII).
RAII is a natural technique in languages like Visual Basic Classic and C++ that have deterministic finalization, but usually requires extra work to include in programs written in garbage collected languages like C# and VB.NET. The using statement makes it just as easy. Of course you could write the try..finally code out explicitly and in some cases that will still be necessary. For a thorough discussion of the RAII technique see HackCraft: The RAII Programming Idiom. Wikipedia has a brief note on the subject as well: Resource Acquisition Is Initialization.
Work in progress: add C# versions showing incorrect and correct methods with and without using. Add notes on RAII, memoization and cacheting (see OOP wikibook).