Jump to content

C# Programming/Object Lifetime

From Wikibooks, open books for an open world

Introduction

[edit | edit source]

All 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

[edit | edit source]

The 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

[edit | edit source]

Managed 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

[edit | edit source]

There 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:

  1. 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)
  2. 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

[edit | edit source]

Often, 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

[edit | edit source]

If 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

[edit | edit source]

The 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).