I build .NET6 console project under release mode and when it's started from command line finalizer won't be called. While starting it from IDE calls the finalizer successfully.
.NET Framework 4.8 - calls finalizer from cmd and IDE
.NET Core 3.1 - didn't call finalizer both from IDE and cmd
Does it meen that some settings to the project should be made? Or I just can't rely on finalizer even if GC.Collect() is force called?
namespace TestApp
{
public class Program
{
public static void Main(string[] args)
{
MyClass myClass = new MyClass();
var gen = GC.GetGeneration(myClass);
Console.WriteLine(gen);
myClass = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadKey();
}
}
public class MyClass
{
public MyClass()
{
Console.WriteLine("Ctor");
}
~ MyClass()
{
Console.WriteLine("Finalizer");
}
}
}
Output when .exe started from command-line (both cmd and powershell):
Ctor
0
Output when project started from IDE (Visual Studio):
Ctor
0
Finalized
First of all if you want deterministic resource cleanup you should not rely on finalizer being called, check out "When everything you know is wrong" article (p1, p2), implement the IDisposable
and follow the pattern.
Secondary - check out this discussion, it seems that issue is not about finalizer per se but the object not being collected - it seems that JIT somehow prolongs lifetime of the "last" instance of the MyClass
. For example the following will collect (on my machine =)) only the "first" object:
for (int i = 0; i < 2; i++)
{
Thread.Sleep(1000);
NewFunction();
}
Console.WriteLine($"Collected: {MyClass.collected}");
Console.ReadKey();
[MethodImpl(MethodImplOptions.NoInlining)]
void NewFunction()
{
MyClass myClass = new MyClass();
var gen = GC.GetGeneration(myClass);
Console.WriteLine(gen);
myClass = null;
Console.WriteLine(myClass);
Collect();
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Collect()
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
public class MyClass
{
private static int counter;
public static int collected;
public int Id { get; }
public MyClass()
{
Id = Interlocked.Increment(ref counter);
Console.WriteLine("Ctor");
}
~MyClass()
{
collected++;
Console.WriteLine($"Finalizer {Id}");
}
}
But adding the following between the cycle and final Console.WriteLine
:
while (MyClass.collected != 2)
{
Thread.Sleep(500);
Collect();
}
Triggers both instances being collected and finalized.