Pitfalls and Best Practices to prevent them #4 – Object Finalizers

Let’s help the .NET Garbage Collector! Why this often backfires…

You know that the .NET Framework comes with a Garbage Collector to tidy up unreferenced objects. And you also know that there are so called “Finalizers” (sometimes also called “Destructors”) which are executed before an object’s memory is definitely deallocated.

Examples

Some people may think: “Hey – let’s help the Garbage Collector a little bit” and write code like that (Note: The console output is for a later example only):

public sealed class Foo
{
    public Bar Bar
    {
        get;
        private set;
    }

    public Foo(Bar bar)
    {
        Console.WriteLine("Foo - Constructor");

        this.Bar = bar;
        this.Bar.Event += this.Bar_Event;
    }

    private void Bar_Event(object sender, EventArgs e)
    {
        Console.WriteLine("Foo.Bar_Event");
    }

    ~Foo()
    {
        Console.WriteLine("Foo - Finalizer");

        if (this.Bar != null)
            this.Bar.Event -= this.Bar_Event;

        this.Bar = null;
    }
}

public sealed class Bar
{
    public event EventHandler Event;

    public void RaiseEvent()
    {
        var handler = this.Event;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }
}

What does it do?

The Finalizer of Foo assigns null to Bar to allow its collection. In addition, it unsubscribes from Bar.Event.

What does it actually do?

Well – let’s express it like that: Nothing. You can leave the Finalizer of Foo completely away – that changes “nothing” (means: nothing really visible) on the memory management; no memory is freed earlier.

However, the code affects the application’s performance in a negative manner. You can say that the code backfires.

Doku says

Implementing Finalize and Dispose to Clean Up Unmanaged Resources

Finalizers:

The following rules outline the usage guidelines for the Finalize method:

  • Implement Finalize only on objects that require finalization. There are performance costs associated with Finalize methods.
  • If you require a Finalize method, consider implementing IDisposable to allow users of your class to avoid the cost of invoking the Finalize method.
  • Do not make the Finalize method more visible. It should be protected, not public.
  • An object’s Finalize method should free any external resources that the object owns. Moreover, a Finalize method should release only resources that the object has held onto. The Finalize method should not reference any other objects.
  • Do not directly call a Finalize method on an object other than the object’s base class. This is not a valid operation in the C# programming language.
  • Call the base class’s Finalize method from an object’s Finalize method.

Finalize Methods and Destructors

Implementing Finalize methods or destructors can have a negative impact on performance and you should avoid using them unnecessarily. Reclaiming the memory used by objects with Finalize methods requires at least two garbage collections. When the garbage collector performs a collection, it reclaims the memory for inaccessible objects without finalizers. At this time, it cannot collect the inaccessible objects that do have finalizers. Instead, it removes the entries for these objects from the finalization queue and places them in a list of objects marked as ready for finalization. Entries in this list point to the objects in the managed heap that are ready to have their finalization code called. The garbage collector calls the Finalize methods for the objects in this list and then removes the entries from the list. A future garbage collection will determine that the finalized objects are truly garbage because they are no longer pointed to by entries in the list of objects marked as ready for finalization. In this future garbage collection, the objects’ memory is actually reclaimed.

Problem

Lets look at two usage scenarios:

Raising the event

public static void Test1()
{
    Bar bar = new Bar();

    CreateFoos(bar);

    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine("Raising Event:");
        bar.RaiseEvent();
        Console.WriteLine();
        Console.WriteLine("Now sleeping...");
        Thread.Sleep(1000);
        Console.WriteLine();
        Console.WriteLine("GC...");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        Console.WriteLine();
    }
}
private static void CreateFoos(Bar bar)
{
    for (int i = 0; i < 2; i++)
    {
        Foo foo = new Foo(bar);
    }
}

Some Foos are created and seem to be unreachable. However, as they subscribed to Bar.Event, they are kept alive as long as the appropriate Bar stays alive. Being invoked after the object gets unreachable, the destructor doesn’t change anything here – you can uncomment it therefore.

Making Foo and Bar unreachable

private static void Test2()
{
    DoSomething();

    Console.WriteLine("GC...");
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine();
}
private static void DoSomething()
{
    Bar bar = new Bar();
    Foo foo = new Foo(bar);
}

In this code, a DoSomething is called which creates a Bar and then a Foo. As none of them is returned or saved somewhere, both become unreachable after DoSomething has executed.

If you now run the program and then uncomment the destructor’s content (expect the console output), you will see that the output is not different. Either Foo and Bar are freed – even without the destructor.

How to solve?

Just leave away the finalizer.

Conclusion / Best Practice

Do not use finalizers to set fields / properties to null or to unsubscribe from events.

Use finalizers only to free unmanaged resources; if you have any, prefer using SafeHandles.

DotNetKicks Image
About these ads

One Response to “Pitfalls and Best Practices to prevent them #4 – Object Finalizers”

  1. DotNetShoutout Says:

    Pitfalls and Best Practices to prevent the: Object Finalizers…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…


Comments are closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: