Fortsätt till huvudinnehåll

C# Finalize, Dispose, and Deconstruct

Introducing the Concepts

It is easy to get a bit confused when trying to figure out what resources you need to free explicitly and how to release the resources that your class has created or is holding references to. You will find that there is something called Finalizer, a clean-up pattern called The Dispose Pattern, and a special Deconstruct method that you can declare. In most cases though, you don't have to do anything and just let the Garbage Collector (GC) do its thing.

However, if your class allocates unmanaged memory, open file handles, network sockets, database connections or similar, you need to ensure that these are properly closed and freed before the references are removed. I will go through the different concepts and hopefully clear out how to ensure that all resources are properly freed in your application.

The Deconstruct method

You might say that it is incorrect to even mention the Deconstruct keyword/method in this post, since it actually have nothing to do with freeing up resources or Garbage Collecting. However, that is exactly the reason why I choose to include it. If you are used to working with OOP in any other language you are most likely familiar with the concept of Constructors and Destructors. Whenever you create a new instance of a class it's Constructor is called and when the object is freed the Destructor is called before the object is removed, right?

In C# you can define a special Deconstruct method with the following signature:

public void Deconstruct(out Type1 var1, out Type2 var2, ..., out TypeN, varN);

However, this method is not a Destructor, it is a DeCONstructor, and is not automatically invoked when the object is removed. It is just a helper method to deconstruct a user defined type into a Tuple. However, it's name makes it easy to confuse with a Destructor (now you can avoid doing this). If you want to know more about Deconstruct you can find an example on how to use it in the C# documentation. Otherwise, just remember that it has nothing to do with freeing resources and let's move on.

The Finalizer

In C# the Destructor is called Finalizer. If you define a Finalizer in your class it will be called as part of the Garbage Collection procedure. However, you do not have control over exactly when the Finalizer is called so do not assume it has been called just because any variables holding references to an object goes out of scope. In C# the Finalizer method must have the same name as the class, must not have any access level attributes (such as private or public) or return type, cannot take any input parameters, and must be prefixed with a tilde (~) character.

~MyClass()
{
...
}

You cannot call the Finalizer from your application code since it is done automatically by the GC. Also, do not add a Finalizer to your class unless you really need one. Doing so will keep the object in memory for an additional amount of time which may have negative impact on your application's performance.

You can simply put all of your clean-up code in the Finalizer, but most of the time, if your class keeps resources that needs to be cleaned up, you want to give the user the opportunity to do so as soon as he is done with the object. This can be done by making your class Disposable.

 The Dispose Pattern

Part of the .NET framework is the interface IDisposable. You can simply make your class Disposable by implementing the IDisposable interface which requires you to implement a method:

public class MyClass : IDisposable
{
  public void Dispose()
  {
    // Clean-up code goes here
  }
}

which allows the application to wrap your class in a using directive:

using (var myObj = new MyClass())
{
  // Do things with myObj
}

In the code above myObj is only accessible within the scope of the using block and as soon as it gets out of scope its Dispose method is automatically invoked.

Now, assume you put all the clean-up code in the Dispose method and remove the Finalizer. This works fine, as long as any user of MyClass either wraps it in a using directive or remember to call the Dispose method explicitly. There is however, a way to ensure that all resources are freed even in the case that a user of MyClass forgets to Dispose it. The solution is called The Dispose Pattern.

Microsofts official documentation on the Dispose Pattern can be a bit overwhelming. I will try to summarize it in a way so that it is easy to understand how to implement it. First let's look at an implementation of the full Dispose Pattern (don't worry if you don't understand all parts of it, I will explain it later):

public class MyClass : IDisposable
{
  private readonly IntPtr buffer; // unmanaged memory buffer that needs to be freed explicitly
  private readonly SafeHandle resource; // resource that also implements the Disposable Pattern
  private bool disposed;

  public MyClass()
  {
    buffer = ...; // allocate memory for the buffer
    resource = ...; // allocate the resource
  }

  ~MyClass()
  {
    Dispose(false);
  }

  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize();
  }

  protected virtual void Dispose(bool disposing)
  {
    if (disposed) return;
    
    ReleaseBuffer(buffer);
    if (disposing)
    {
      resource?.Dispose();
    } 
    disposed = true;
  } 

  public void MyMethod()
  {
    if (disposed) throw new ObjectDisposedException(...);
    ...
  }
}

Let's go through the code top to bottom and cover the interesting parts. First you will notice that the class implements the IDisposable interface, hence it must implement a public Dispose() method.
Next you'll find three different private fields, a boolean indicating if the object has been disposed or not, a pointer to unmanaged memory, and a reference to a class that is also Disposable. These have to be treated a bit differently, I'll explain why and how later.
After that comes the Constructor where the unmanaged memory buffer and the SafeHandle resources are allocated.

Then comes the Finalizer, which will be called automatically by the GC, but only if the Dispose method hasn't been called. This happens since the Dispose method implements a call to GC.SuppressFinalize() which tells the GC that it can collect the memory directly and skip the invocation of the Finalizer. Both the Finalizer and the Dispose method calls a custom Dispose method that takes a boolean as input. This boolean indicates if the method was called from the Dispose method or from the Finalizer.

All the clean-up code is put inside the Dispose(bool disposing) method. The purpose of the disposing flag is to determine whether you should call the Dispose method of any resources you have allocated that also are Disposable. You should not do that if the method is invoked from the Finalizer, and the reason for that is that Finalizers are called in an non-deterministic order, so the Finalizer of the underlying resource may already have been invoked. Non-disposable resources, like the unmanaged memory buffer, should however be freed in both cases.

Finally I have included a method MyMethod that checks if the object has been disposed and in that case throws an exception. This is to protect from bugs in the application that uses MyClass that might lead to unexpected behavior.

Final thoughts

I hope the text and code above helps clear out how to properly clean up unmanaged resources in your .NET application. Remember though that in most cases you will be using managed resources and can safely let the GC clean up resources that are no longer referenced.

Kommentarer

Populära inlägg i den här bloggen

C# Enum as bit field

Bit field enum Whenever you wish to express combinations of properties of an object, bit fields are a good way to accomplish this. As a simple example, consider a file in the file system. It can be Readable , Writable , Hidden or a combination these. The different attributes can be defined as an enum : [Flags] public enum FileAttribute {   None      = 0b0000;   Readable  = 0b0001;   Writeable = 0b0010;   Hidden    = 0b0100; } To indicate that this enum is expected to be used as a bit field I have defined it with the FlagsAttribute . It is important to understand that the FlagsAttribute does nothing more than making some changes to how the ToString method of the enum works, making it possible to print out all flags. It does not introduce any validation or special treatment of the enum in any other way. I have defined the values of the different fields of the enum using binary representation, this should make it even more clear that this is a bit field and which bi

Codility tasks - Part I

I was recently faced with two codility tasks when applying for a job as an Embedded Software Engineer. For those of you who arn't familiar with Codility you can check out their website here:  www.codility.com Task one - Dominator The first task was called Dominator. The goal was to, given a std::vector of integers, find an integer that occurs in more than half of the positions in the vector. If no dominator was found -1 should be returned. My approach was to loop through the vector from the first to the last element, using a std::map to count the number of occurences of each integer. If the count ever reached above half the size of the vector I stopped and returned that integer and if I reached the end without finding a dominator I returned -1. So was that a good approach? Well, the reviewer at the company rated the solution as 'pretty ok'. His preferred solution was store the first integer in the array and set a counter to 1. Then loop through the remaining i