I'm playing around with finalizers in c# and found this example which shows that the finalizer can be called even if the constructor didn't succeed.
- How correct is this behavior?
- Is there a description of this mechanism somewhere in the documentation?
- Why is this happening?
internal class Program
{
public static void Main(string[] args)
{
Do();
GC.Collect();
Console.ReadLine();
}
public static void Do()
{
var objects = new BigObject[1000];
try
{
for (var i = 0; i < objects.Length; i++)
{
var obj = new BigObject();
objects[i] = obj;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public class BigObject
{
public BigObject()
{
Console.WriteLine(GetHashCode());
}
private Array _array = new int[1_000_000_000];
~BigObject()
{
Console.WriteLine("~" + GetHashCode());
}
}
Output:
58225482
54267293
18643596
33574638
33736294
35191196
48285313
31914638
18796293
34948909
46104728
12289376
43495525
55915408
33476626
System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
at ConsoleApp11.BigObject..ctor() in C:\Users\t.lemeshko\source\repos\ConsoleApp11\Program.cs:line 41
at ConsoleApp11.Program.Do() in C:\Users\t.lemeshko\source\repos\ConsoleApp11\Program.cs:line 22
~55915408
~43495525
~12289376
~7880838
~46104728
~34948909
~18796293
~31914638
~48285313
~35191196
~33736294
~33574638
~18643596
~54267293
~58225482
~33476626
So whe can see there is an object with hash 7880838 has been destroyed, but not cinstructed
From the documentation
Note that object creation is a multi step process, simplified this involve allocating memory for the object, and then initializing it. In this case it is the object initialization that fails when it tries to allocate the array. Note that the array is separate from the
BigObject. YourBigObjectis actually really small, it just contains a single reference.Since the GC has succeeded in allocating memory for the object it needs to clean it up, regardless if the initialization succeeded or not. Otherwise any exception in a constructor would result in a memory leak. And when the object is collected its finalizer need to run according to the quoted rule.
If the failure occurred in the actual allocation step for your object I would not expect the finalizer to run. Since the there was no allocation there is no need for any cleanup, and therefore no finalizer. This could happen if your
BigObjectactually was big, i.e. containing millions of fields instead of just one.Also not that the finalizer is intended for disposing unmanaged resources, you cannot safely access any other managed object within a finalizer, since they may already have been collected. You need to be careful overall when writing finalizers, since mistakes can easily result in memory leaks or other bugs.