I know the .Net garbage collector cannot guarantee any order when destroying objects but I am really suprised of the following behaviour:
using System;
using System.Collections.Generic;
namespace ConsoleApplication2 {
class Program {
class A {
public A() { i = 1; }
private int i;
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
~A() {
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Console.WriteLine("Disposed A");
}
}
class B {
static int p = 0;
public B(A a) {
this.a = a;
j = p++;
}
~B() {
Console.WriteLine("Disposed " + j.ToString());
}
private int j;
private A a;
}
static void Main(string[] args) {
A a_obj = new A();
List<B> bs = new List<B>();
for (int i = 0; i < 10; i++) {
B b = new B(a_obj);
bs.Add(b);
}
}
}
}
This execution has the following (possible) output:
Disposed 9
Disposed 2
Disposed 1
Disposed 0
Disposed A
Disposed 8
Disposed 7
Disposed 6
Disposed 5
Disposed 4
Disposed 3
I can understand that the order of B objects is not deterministic but how is possible that the garbage collector destroys a_obj, if there are still some B objects that have a reference to a_obj and has not been destroyed still?
.NET garbage collector does not use reference counting, so it doesn't matter how many references to a_obj
there is. What matters is what objects are reachable from roots (like static variables). In your case, all objects (A
and all B
s) become unreachable from root at the same time and so are all eligible to garbage collection, their mutual references do not matter. If A
and B
are both unreachable and will be collected in the same cycle, why bother about order in which this will happen? Also if order was dependent on which object references which - think about how it would then collect objects that have mutual references to each other?
Here is also a quote from documentation which fits your situation exactly:
The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. That is, if Object A has a reference to Object B and both have finalizers, Object B might have already been finalized when the finalizer of Object A starts.
Also note that first all finalizers will run and only then objects will be really dead and memory taken by them might be reused (you can even ressurect object in finalizer via GC.ReRegisterForFinalize
). So you can access A
from B
's finalizer, even though A
's finalizer might have already been run at this point. Obviously you should avoid doing this anyway.