Reputation:
I'm reading a book (CLR via C#) which says:
CLR doesn’t make any guarantees as to the order in which Finalize methods are called. So, you should avoid writing a Finalize method that accesses other objects whose type defines a Finalize method; those other objects could have been finalized already. However, it is perfectly OK to access value type instances or reference type objects that do not define a Finalize method.
I kind of get the idea but I'm not 100% sure why it is dangerous, so I will picture some different scenarios to verify whether or not my understanding on Finalize is correct
Let's say we have the following code:
class ClassA
{
public ClassB Item;
public ClassA(ClassB item) { this.Item = item };
~ClassA {
... // might contain some time-consuming operations
item.Call();
}
}
class ClassB {
public void Call() {
}
~ClassB {
Console.Write("Finalize run"); // simple statment, error-free
}
}
...
static void Main()
{
ClassB b = new Class B();
ClassA a= new ClassA(b);
... // do sth else
GC.Collect(); // first GC
... // do sth else
GC.Collect(); // second GC
... // do sth else
}
We know that The CLR uses a special, high-priority dedicated thread(let's call it Fthread) to call Finalize methods, and let's assume only two manual GC occurs, no auto triggered GC occurs.
So after first GC occurs, a
and b
marked as unreachable
Case OK 1:
Fthread calls ~ClassA
, followed by ~ClassB
, b
exists on the heap before second GC occurs
Case OK 2:
Fthread calls ~ClassB
, followed by ~ClassA
, b
exists on the heap before second GC occurs
Case Not OK:
Fthread calls ~ClassB
, followed by ~ClassA
, but ~ClassA
executes some time-confuming operations before executing Call() on its member b
. Second GC occurs, b is collected by GC, ~ClassA
finishes the time-consuming operations, start to execute Call() on b
, but b
no longer exists in heap.
Based on the Case Not OK example, although it is rare, we still need to avoid writing a Finalize method that accesses other objects whose type defines a Finalize method. Is my understanding correct? I certainly won't writing a Finalize method that accesses other objects whose type defines a Finalize method, I know what is "ok" is complex; All I want to do is make sure my understanding is correct so that I know my understanding on Finalize method is correct
Upvotes: 1
Views: 83
Reputation: 1062865
These problems are rarely interesting with trivial examples, because the problems only show in non-trivial ones, but if you want a few trivial examples:
null
in as item
on the constructor; your item.Call()
throws an exception on the finalizer thread, and your app explodes in a shower of sparksnull
checking in ClassA
's constructor; someone uses RuntimeHelpers.GetUninitializedObject
to create an instance of ClassA
that is all zeros/nulls; your item.Call()
throws an exception on the finalizer thread, and your app explodes in a shower of sparksClassB
instance in a state that Call
doesn't anticipate, perhaps because of an exception, or perhaps because it was disposed (or perhaps it was being accessed concurrently by two threads in ways that left it so horribly mangled that it would need dental records, or perhaps someone used reflection to poke at it); now your item.Call()
throws an exception - sparks resumeBut ultimately, there is a much more important point:
What you're asking about is not interesting because that isn't the intended and correct use of finalizers. This is a really advanced topic that is needed almost never (I've been using .NET since the earliest releases, and I think I might have used one, maybe two real genuine production finalizers - talking to unmanaged C/C++ APIs via P/Invoke). When you're already doing something inadvisable, asking "exactly how much can I get away with?" is the wrong question.
Upvotes: 2