views:

238

answers:

1

I have read a lot of articles on the net about releasing RCW's safely, and it seems to me that no one can agree on exactly what needs to be done in what order, so I'm asking you guys for your opinions. For example, one could do this:

object target = null;
try {
    // Instantiate and use the target object.
    // Assume we know what we are doing: the contents of this try block
    // do in fact represent the entire desired lifetime of the COM object,
    // and we are releasing all RCWs in reverse order of acquisition.
} finally {
    if(target != null) {
        Marshal.FinalReleaseComObject(target);
        target = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

However, some people advocate doing the garbage collection before Marshal.FinalReleaseComObject, some after, and some not at all. Is it really necessary to GC every single RCW manually, especially after it has already been detached from its COM object?

To my mind, it would be simpler and easier to just detach the RCW from the COM object and leave the RCW to expire naturally:

object target = null;
try {
    // Same content as above.
} finally {
    if(target != null) {
        Marshal.FinalReleaseComObject(target);
    }
}

Is it sufficient to do that?

+4  A: 

To have your reference to the target COM object released, it is sufficient and preferred to just call Marshal.FinalReleaseComObject and not force a collect. In other words, you've met your responsibility to release your reference as soon as you were done with it. I won't touch the issue of FinalReleaseComObject vs ReleaseComObject.

This leaves the bigger question of why do people advocate calling GC.Collect() and WaitForPendingFinalizers()?

I'm guessing because they're looking at it from the COM objects point of view. This would ensure all unreachable (non-rooted) managed references, through other RCWs, would be released as well. But then there is no need to call FinalReleaseComObject to release your reference, it'll be done by the finalizer, but again, forcing a collect is usually an act of desperation and is considered bad form.

Another reason could be because sometimes COM objects are poorly designed and have dependencies on one another where the order of Release() calls matters. I've seen it with containment relationships when the contained entities hold pointers but not references to their container, the container must live longer than its children but for .NET, the finalize order is non-deterministic. People who advocate forcing collect may have had this "fix" their destruction ordering problems by strategically placing them throughout their code.

An additional note is that setting target to null is usually unnecessary, and specifically is unnecessary in your sample code. Setting objects to nothing is common practice for VB6 since it uses a reference count based garbage collector. The compiler for C# is clever enough (when building for release) to know that target is unreachable after its last use and could be GC'd, even before leaving scope. And by last use, I mean last possible use so there are cases where you might set it to null. You can see this for yourself with the code below:

   using System;
   class GCTest
   {
       ~GCTest() { Console.WriteLine("Finalized"); } 
       static void Main()
       {
           Console.WriteLine("hello");
           GCTest x = new GCTest();
           GC.Collect();
           GC.WaitForPendingFinalizers();
           Console.WriteLine("bye");
       }
   }

If you build release (e.g., CSC GCTest.cs), "Finalized" will print out between "hello" and "bye". If you build debug (e.g., CSC /debug GCTest.cs), "Finalized" will print out after "bye" whereas setting x to null prior to Collect() would have "fixed" that.

Tony Lee
Excellent and detailed analysis, thanks Tony!
Christian Hayter