To get a trial key
fill out the form below
Team License (standard version)
Enterprise License (extended version)
* By clicking this button you agree to our Privacy Policy statement

** This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Request our prices
New License
License Renewal
--Select currency--
USD
EUR
GBP
RUB
* By clicking this button you agree to our Privacy Policy statement

** This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Message submitted.

Your message has been sent. We will email you at


If you haven't received our response, please do the following:
check your Spam/Junk folder and click the "Not Spam" button for our message.
This way, you won't miss messages from our team in the future.

>
>
>
Why using finalizers is a bad idea

Why using finalizers is a bad idea

Oct. 7, 2016
Author:

Not so long ago we worked on a diagnostic rule related to the finalizer check. This provoked an argument on the details of the garbage collector work and the finalization of objects. Although we have been programming in C# for more than 5 years, we haven't achieved any consensus as regards this question, so I decided to study it more thoroughly.

0437_Finalization/image1.png

Introduction

Usually .NET developers encounter a finalizer when they need to free an unmanaged resource. That's when a programmer has to think about a specific question: should we implement in our class IDisposable, or add a finalizer? Then he goes to StackOverflow, for example, and reads answers to questions like the Finalize/Dispose pattern in C#, where he sees a classic pattern of IDisposable implementation, and the definition of the finalizer. The same pattern can be found in the MSDN description of the IDisposable interface. Some consider it quite complicated to understand, and offer other options like implementing the clearing of managed and unmanaged resources in separate methods, or creating a wrapper class especially for freeing unmanaged resource. You can find them on the same page on StackOverflow.

Most of these methods suggest implementing a finalizer. Let's see what the benefits are, and what potential problems it can bring.

The pros and cons of using finalizers

Pros.

  • A finalizer allows the clearing an object before it will be deleted by a garbage collector. If the developer forgot to call Dispose() method of an object, then it will be possible to free the unmanaged resources and thus, avoid the leak.

Well, that's it. That is the only plus, and it is quite controversial; we'll speak about the details later.

Cons.

  • The finalization is not determined. You don't know when the finalizer will be called. Before CLR starts finalizing the objects, the garbage collector should place it in the queue of objects, ready for the finalization, when the next garbage collection starts. But this point is not defined.
  • Due to the fact that an object with the finalizer does not get removed by the garbage collector immediately, the object, and the entire graph of dependent objects, go through the garbage collection and promote to the next generation. They will be removed only when the garbage collector decides to collect objects of this generation, which can take quite a while.
  • Since the finalizers run in a separate thread in parallel with other threads of the application, a programmer may have a situation when the new objects, requiring finalization, will be created faster than the finalizers of old objects will complete the execution. This will lead to increased memory consumption, decreased performance, and perhaps eventually to the crash of the application with OutOfMemoryException. On the developer's machine you may never encounter this situation, for example because you have fewer processors, or the objects are created slower or the application does not work as long as it could and the memory doesn't run out as fast. It may take a lot of time to realize that the reason was the finalizers. Perhaps this minus outweighs the benefits of the only pro.
  • If there is an exception during the finalizer execution, then the application will terminate. Therefore, if you implement a finalizer, you should be especially careful: do not access the methods of other objects for which the finalizer could be called; take into account that a finalizer is called in a separate thread; verify against null all other objects that could potentially be null. The last rule is related to the fact that the finalizer can be called for an object in any of its states, even incompletely initialized. For example, if you always assign in the constructor a new object in the class field and then expect that in the finalizer it should never be null and do access it, then you can get NullReferenceException, if there was an exception in the base class constructor during the creation of an object, and your constructor wasn't executed at all.
  • A finalizer may be not executed at all. Upon the abortion of the application, for example, if there is an exception thrown in somebody's finalizer due to any of the reasons described above, no other finalizers will be executed. If you free unmanaged objects of the operating system, there will be nothing wrong in the way that the operating system returns its resources when the application terminates. But if you put unwritten bytes to the file, you will lose your data. So, perhaps it would be better not to implement the finalizer, but let the data be lost, in case you forgot to call Dispose(), because in this case the problem will be easier to find.
  • We should remember that the finalizer is called only once, and if you resurrect the object in the finalizer by means of assigning a reference to it to a different live object, then perhaps, you should register it for the finalization again with the help of the method GC.ReRegisterForFinalize().
  • You can face the problems of multithread applications; for example, the race condition, even if your application is single-threaded. This would be a very unusual case, but it is theoretically possible. Suppose there is a finalizer in your object, it is referenced by a different object that also has a finalizer. If both objects become eligible for garbage collection, and their finalizers start executing at the same time another object gets resurrected, then that object and your object become alive again. Now we may have a situation where the method of your object will be called from the main thread and from the finalizer at the same time, because it is still in the queue of objects, ready for the finalization. The code that reproduces this example is given below: You can see that first the finalizer of the Root object is executed, then the finalizer of the Nested object, and then the method DoSomeWork() is called from two threads at the same time.
class Root
{
    public volatile static Root StaticRoot = null;
    public Nested Nested = null;

    ~Root()
    {
        Console.WriteLine("Finalization of Root");
        StaticRoot = this;
    }
}
class Nested
{
    public void DoSomeWork()
    {
        Console.WriteLine(String.Format(
            "Thread {0} enters DoSomeWork",
            Thread.CurrentThread.ManagedThreadId));
        Thread.Sleep(2000);
        Console.WriteLine(String.Format(
            "Thread {0} leaves DoSomeWork",
            Thread.CurrentThread.ManagedThreadId));
    }
    ~Nested()
    {
        Console.WriteLine("Finalization of Nested");
        DoSomeWork();
    }
}

class Program
{
    static void CreateObjects()
    {
        Nested nested = new Nested();
        Root root = new Root();
        root.Nested = nested;
    }
    static void Main(string[] args)
    {
        CreateObjects();
        GC.Collect();
        while (Root.StaticRoot == null) { }
        Root.StaticRoot.Nested.DoSomeWork();
        Console.ReadLine();
    }
}

This is what will be be displayed on my machine:

Finalization of Root
Finalization of Nested
Thread 10 enters DoSomeWork
Thread 2 enters DoSomeWork
Thread 10 leaves DoSomeWork
Thread 2 leaves DoSomeWork

If your finalizers called in a different order, try to change the places of the creation of nested and root.

Conclusion

Finalizers in .NET are the easiest way to shoot yourself in the foot. Before you rush into adding finalizers for all the classes that are implementing IDisposable, think first; do you really need them that much? It should be noted that the CLR developers warn against their use on the page the Dispose Pattern: "Avoid making types finalizable. Carefully consider any case in which you think a finalizer is needed. There is a real cost associated with instances with finalizers, from both a performance and code complexity standpoint."

But if you decide to use finalizers anyway, PVS-Studio will help you find potential bugs. We have the V3100 diagnostic, which can indicate all spots in the finalizer where there is a possibility of NullReferenceException.

Popular related articles
Static analysis as part of the development process in Unreal Engine

Date: 06.27.2017

Author: Andrey Karpov

Unreal Engine continues to develop as new code is added and previously written code is changed. What is the inevitable consequence of ongoing development in a project? The emergence of new bugs in th…
How PVS-Studio Proved to Be More Attentive Than Three and a Half Programmers

Date: 10.22.2018

Author: Andrey Karpov

Just like other static analyzers, PVS-Studio often produces false positives. What you are about to read is a short story where I'll tell you how PVS-Studio proved, just one more time, to be more atte…
The way static analyzers fight against false positives, and why they do it

Date: 03.20.2017

Author: Andrey Karpov

In my previous article I wrote that I don't like the approach of evaluating the efficiency of static analyzers with the help of synthetic tests. In that article, I give the example of a code fragment…
PVS-Studio ROI

Date: 01.30.2019

Author: Andrey Karpov

Occasionally, we're asked a question, what monetary value the company will receive from using PVS-Studio. We decided to draw up a response in the form of an article and provide tables, which will sho…
Appreciate Static Code Analysis!

Date: 10.16.2017

Author: Andrey Karpov

I am really astonished by the capabilities of static code analysis even though I am one of the developers of PVS-Studio analyzer myself. The tool surprised me the other day as it turned out to be sma…
PVS-Studio for Java

Date: 01.17.2019

Author: Andrey Karpov

In the seventh version of the PVS-Studio static analyzer, we added support of the Java language. It's time for a brief story of how we've started making support of the Java language, how far we've co…
The Last Line Effect

Date: 05.31.2014

Author: Andrey Karpov

I have studied many errors caused by the use of the Copy-Paste method, and can assure you that programmers most often tend to make mistakes in the last fragment of a homogeneous code block. I have ne…
Technologies used in the PVS-Studio code analyzer for finding bugs and potential vulnerabilities

Date: 11.21.2018

Author: Andrey Karpov

A brief description of technologies used in the PVS-Studio tool, which let us effectively detect a large number of error patterns and potential vulnerabilities. The article describes the implementati…
Free PVS-Studio for those who develops open source projects

Date: 12.22.2018

Author: Andrey Karpov

On the New 2019 year's eve, a PVS-Studio team decided to make a nice gift for all contributors of open-source projects hosted on GitHub, GitLab or Bitbucket. They are given free usage of PVS-Studio s…
Characteristics of PVS-Studio Analyzer by the Example of EFL Core Libraries, 10-15% of False Positives

Date: 07.31.2017

Author: Andrey Karpov

After I wrote quite a big article about the analysis of the Tizen OS code, I received a large number of questions concerning the percentage of false positives and the density of errors (how many erro…

Comments (0)

Next comments

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
This website uses cookies and other technology to provide you a more personalized experience. By continuing the view of our web-pages you accept the terms of using these files. If you don't want your personal data to be processed, please, leave this site.
Learn More →
Accept