26

I'm trying to learn how finalization and destructor works in C#, I tried to run the code in the System.Object.Finalize example(code copy-pasted, no changes made), but the output is not the same as expected, it shows that the destructor is never called.

The code is:

using System;
using System.Diagnostics;

public class ExampleClass
{
   Stopwatch sw;

   public ExampleClass()
   {
      sw = Stopwatch.StartNew();
      Console.WriteLine("Instantiated object");
   } 

   public void ShowDuration()
   {
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                    this, sw.Elapsed);
   }

   ~ExampleClass()
   {
      Console.WriteLine("Finalizing object");
      sw.Stop();
      Console.WriteLine("This instance of {0} has been in existence for {1}",
                    this, sw.Elapsed);
   }
}

public class Demo
{
   public static void Main()
   {
      ExampleClass ex = new ExampleClass();
      ex.ShowDuration();
   }
}

Update:

When I use visual studio and .net framework 4.5, the code works as expected: Output same as example:

The example displays output like the following:
   Instantiated object
   This instance of ExampleClass has been in existence for 00:00:00.0011060
   Finalizing object
   This instance of ExampleClass has been in existence for 00:00:00.0036294

When I use dotnet core app, the code does not work: The actual output is:

PS C:\ws\test> dotnet run
    Instantiated object
    This instance of ExampleClass has been in existence for 00:00:00.0056874

So why this is different in .NET Core?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
fluter
  • 13,238
  • 8
  • 62
  • 100
  • 1
    _"this is a language feature, right?"_ -- There is no language feature that guarantees a finalizer would be called. That's why no code should assume that one ever will be called. Finalizers are there strictly as a backup, to mitigate the harm done by buggy code. – Peter Duniho Jun 24 '17 at 04:13
  • @PeterDuniho please check out https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/destructors, finalizer should and will be called. – fluter Jun 24 '17 at 04:19
  • I agree that under normal circumstances, a finalizer should be called. From the C# specification: _"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"_. And the code example you now include in your question appears to fall under that description (cleanup has not been suppressed, and the program exits normally). But it is still important to understand that there's no guarantee a program will exit normally, and thus no guarantee a finalizer will ever be called. – Peter Duniho Jun 24 '17 at 04:35
  • What does "exit normally" mean? There is no exception and the program reaches the last line of the main function, so my understanding is that it is guaranteed to exit normally, right? Also, in terminal, `echo $?` returns True, means the program run successfully. – fluter Jun 24 '17 at 04:42
  • _"What does "exit normally" mean?"_ -- as I wrote, your program appears to exit normally. But processes can be terminated without warning, and without allowing any further execution of code in the process. That would be an "abnormal exit". I am not saying your example terminates abnormally; I'm saying you should not be under the misconception that finalizers are always executed, even if in your example above, it appears the finalizer should have been. – Peter Duniho Jun 24 '17 at 04:44
  • 1
    And for the record, using your code example, I reproduce the problem you describe, and no I don't have any idea why .NET Core would fail to finalize objects on a normal program exit. Sounds like a bug to me. The specification seems reasonably clear, so unless there's something about .NET Core that, by design, finalization of objects is "suppressed" before normal program termination, .NET Core is in violation of the specification. – Peter Duniho Jun 24 '17 at 04:49
  • 1
    I think it's a [feature](https://github.com/dotnet/corefx/issues/5205) – H H Jun 24 '17 at 08:51
  • 1
    @fluter - the specs you refer to is about the C# language. Looks to me like it's overstepping its bounds, this is about behaviour of the platform, not the language. – H H Jun 24 '17 at 08:55
  • 2
    [The latest draft of the ECMA C# specification weakens this requirement.](https://www.ecma-international.org/activities/Languages/C%20sharp%20Standard%20WD3.16.pdf#%5B%7B%22num%22%3A1738%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C40%2C534%2C0%5D) But I think it's not enough, so I have created [a csharplang issue about this](https://github.com/dotnet/csharplang/issues/700). – svick Jun 24 '17 at 10:10

2 Answers2

25

Putting together information from comments by Peter Duniho and Henk Holterman and expanding on it further:

This behavior is in violation of the C# 5.0 spec from Microsoft and the current draft of the C# 6.0 spec from Microsoft, which say:

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

But it's not a bug, .Net Core intentionally diverged from the .Net Framework behavior, as explained in a corefx issue:

Currently, a best-effort attempt is made to run finalizers for all finalizable objects during shutdown, including reachable objects. Running finalizers for reachable objects is not reliable, as the objects are in an undefined state.

Proposal

Don't run finalizers on shutdown (for reachable or unreachable objects)

Under this proposal, it is not guaranteed that all finalizable objects will be finalized before shutdown.

Presumably due to this, the C# 5.0 spec from ECMA weakened this requirement, so .Net Core does not violate this version of the spec:

Prior to an application’s termination, an implementation should make every reasonable effort to call finalizers (§15.13) for all of its objects that have not yet been garbage collected, 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.

Community
  • 1
  • 1
svick
  • 236,525
  • 50
  • 385
  • 514
  • They disabled the finalizer thread timeout. Gack. Well, that had no other way to go from there than "don't do it all". Weak sauce. – Hans Passant Jun 24 '17 at 10:48
  • _"the current draft of the C# 5.0 spec from ECMA"_ -- tangential, but: what? I looked at the link, and it does seem that that's the spec for C# 5 (no C# 6 features in the spec). ECMA seems like they are running behind on C# versions. _".Net Core does not violate this version of the spec"_ -- I'm not sure I agree with that. The changes suggest why the behavior might be different, but there's no evidence .NET Core makes _"every reasonable effort"_ here, nor that the behavior is documented. Maybe that's the direction they are headed, but don't seem to be there quite yet. :( – Peter Duniho Jun 24 '17 at 18:58
  • 1
    @PeterDuniho Yeah, ECMA has lots of catching up to do. But the C# Task Group is lead by Jon Skeet, so I trust it won't take that long. And I agree the ECMA draft wording is not completely in line with the .Net Core behavior, which is why I created [this csharplang issue](https://github.com/dotnet/csharplang/issues/700). – svick Jun 24 '17 at 19:09
  • 1
    You can add that this is now fully documented: "In .NET Framework applications (but not in .NET Core applications), finalizers are also called when the program exits." https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/destructors – Loudenvier Feb 09 '19 at 10:42
  • @PeterDuniho Nice effort, but looks like all that happened from your attempt was they just changed the spec further to match the implentation rather than the other way around. I think the thread issues argument was pretty weak since it wouldn't be doing anything that wasn't being done before for the finalizers, before "shutdown starts". Thanks for trying. – Beeeaaar Dec 13 '22 at 18:28
-3

Finalization won't occur until the garbage collector runs. Garbage collection doesn't run unless it needs to (e.g. you're low on memory), or if you force it to run.

Try adding

System.GC.Collect();

to your code and see if the finalizer runs in that situation.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Adding `GC.Collect` does not make a difference. But otherwise my confusion was the MSDN example does not use GC.Collect either yet it showed the finalizer was called as expected. – fluter Jun 24 '17 at 03:25
  • 1
    The GC is supposed to run before the program exits. From the C# specification: _"Prior to an application’s termination, destructors for all of its objects that have not yet been garbage collected are called"_. The question seems clear, and is _"why doesn't .NET Core comply with the specification?"_, and is _not_ "how can I work-around this issue?" – Peter Duniho Jun 24 '17 at 08:13
  • 1
    Adding `GC.Collect()` does make a difference, but only in Release mode. That's because in Debug mode, local variables are not collected while they're still in scope. Though this does not really answer the question. – svick Jun 24 '17 at 08:43