David Mortenson gave more details on an internal discussion list:
The reason we didn’t implement IDisposable on RCWs is because of the risks associated with eagerly releasing RCWs. Yves blog link below describes in detail what these risks are.
But then someone asked:
Ok. I don't contest the decision on not using IDisposable interface for RCWs. I agree that ReleaseComObject should not be called if not needed.
I see two other points made by Yves, that I don't agree with:
Code that keeps a reference to RCW will not be able to use the object after ReleaseComObject.
If you call ReleaseComObject from one thread while another thread is doing something on the same object, it could AV or corrupt memory.
#1 is not something unique for COM objects. Any object that encapsulates access to unmanaged resource has the same behavior. E.g. after you close a FileStream object, you won't be able to read or write to the file. However it doesn't prevent FileStream from implementing IDisposable.
I don't understand #2. ReleaseComObject calls IUnknown::Release once, doesn't it? If so, how could other threads be affected? When you marshal COM interface between threads you usually call CoMarshalInterThreadInterfaceInStream then CoGetInterfaceAndReleaseStream. As a result of this ref count of the object is incremented by 1. Even if object is free-threaded and you marshal between two threads in the same multi-threaded apartment, you will get the same physical interface pointer, but the ref count will still be incremented. Now imagine one thread is calling ReleaseComObject while second thread is executing some calls on the object. The ref count is decremented by 1 as a result of ReleaseComObject, object is still alive, second thread continues on normally. I don't see how this could lead to AV or memory corruptions.
So Dave gave additional information:
I can answer these (since I wrote the original email which Yves graciously offered to put in blog form ;-)). Regarding #1, you are correct that this is quite similar to other managed components, however for optimized stubs we don’t want to incur the cost of checking to see if the RCW has been disconnected from it’s underlying COM component on every call so calling a method that uses an optimized x86 stub on an RCW that has had ReleaseComObject called on it will cause an AV. Because of this, it’s actually worse than the behavior for most managed components that implement IDisposable.
Regarding #2, if both threads are MTA then we don’t need to obtain a proxy and for perf reasons, we don’t AddRef the COM component and Release it for every call that occurs. Because of this, calling ReleaseComObject from an MTA thread while another MTA thread has a call in flight will most likely cause an AV inside the COM component and furthermore most likely corrupt process wide state.