Spec shouldn't promise that finalizers run on application termination · Issue #291 · dotnet/csharpstandard (original) (raw)

In .Net Framework, when an application exits, finalizers for finalizable objects that were not yet garbage collected are called. On the other hand, on .Net Core, this does not happen, see https://github.com/dotnet/corefx/issues/5205.

The .Net Framework behavior is consistent with the draft C# 6.0 specification from MS, but the .Net Core behavior isn't:

Prior to an application's termination, destructors for all of its objects that have not yet been garbage collected are called, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example).

The latest draft of the C# 5.0 specification from ECMA weakens this requirement:

Prior to an application’s termination, an implementation should make every reasonable effort to call finalizers (§16.13) for all of its objects that have not yet been garbage collected are called, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example). The implementation should document any conditions under which this behavior cannot be guaranteed.

This means that .Net Core follows the letter of this version of the spec (as long as it documents that this behavior is never guaranteed). But I think it does not follow the spirit of the spec: .Net Core does not make any effort to call finalizers and so I think that the spec should be weakened even further.

Is the new wording in the ECMA draft good enough? Could someone from the ECMA TC49-TG2, which is currently updating the ECMA version of the spec, comment on this?

This was inspired by an SO question.


To show this behavior:

  1. Create an application consisting of Program.cs:
    using System;
    class C
    {
    static void Main()
    {
    new C();
    Console.WriteLine("Exiting.");
    }
    ~C() => Console.WriteLine("Finalizing.");
    }
    and app.csproj: Exe net47;netcoreapp1.0
  2. Run the app on .Net Framework and .Net Core to observe the difference:

    dotnet run -f netcoreapp1.0 # finalizer not called
    Exiting.
    dotnet run -f net47 # finalizer called
    Exiting.
    Finalizing.