EDIT: Okay, I have made some progress on this. There are three binary switches involved (at least):
- Is the code optimized? that is, the
/o+ or /o- flag on the command line. It does not seem to make any difference. - Whether the code runs in the debugger or not. It does not seem to make any difference.
- The level of generated debug information, i.e. command line flag
/debug+ , /debug- or /debug:full or /debug:pdbonly . Only /debug+ or /debug:full fail.
Additionally:
- If you separate the
Main code from the TestDestructor code, you can say that this is the Main code compilation mode, which makes the difference - As far as I can tell, the IL generated for
/debug:pdbonly is the same as for /debug:full inside the method itself, so there might be a clear problem ...
EDIT: Alright, now it's really weird. If I make out the "broken" version and then put it together again, it works:
ildasm /out:broken.il Program.exe ilasm broken.il
ilasm has three different debugging settings: /DEBUG , /DEBUG=OPT and /DEBUG=IMPL . Using one of the first two, it fails - using the last, it works. The latter is described as enabling JIT optimization, so apparently it matters here ... although, in my opinion, it should still collect the object anyway.
Perhaps this is due to the memory model in terms of DestructorCalled . This is not mutable, therefore there is no guarantee that the record from the finalizer stream will be "visible" by your test stream.
Finalizers are certainly called in this scenario. After creating the volatile variable, this separate, equivalent example (which is just easier to run for me) certainly prints True for me. This is not proof, of course: without volatile code will not crash; it just isn't guaranteed to work. Can you run the test after a failure by making it a mutable variable?
using System; class TestDestructor { public static volatile bool DestructorCalled; ~TestDestructor() { DestructorCalled = true; } } class Test { static void Main() { TestDestructor testDestructor = new TestDestructor(); var array = new object[] { testDestructor }; array = null; testDestructor = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine(TestDestructor.DestructorCalled); } }
EDIT: I just saw how it happened when building with Visual Studio, but it was fine from the command line. A look at the IL now ...
source share