I have a class which provides an adapter to a COM object. Here's a much simplified version of it:
public class DocumentWrapper
{
private COMDocument doc;
public DocumentWrapper(COMDocument doc)
{
this.doc = doc;
this.doc.OnClose += OnClose;
}
private void OnClose()
{
this.doc.OnClose -= OnClose;
this.doc = null;
}
public bool IsAlive { get { return this.doc != null; } }
}
The problem is that the OnClose event handler is keeping both objects alive. Both objects should have the same lifetime, so when one goes away the other should too, i.e. nobody is keeping one alive and expecting the other to go away.
I did experimenting with weak references:
COMDocument com = CreateComDocument();
var doc = new DocWrapper(com);
WeakReference weak1 = new WeakReference(com);
WeakReference weak2 = new WeakReference(doc);
GC.Collect(2, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced);
if (weak1.Target != null)
Console.WriteLine("COM was not collected");
if (weak2.Target != null)
Console.WriteLine("Wrapper was not collected");
If OnClose is not registered, both objects get collected. If OnClose is registered, neither object gets collected. How do I ensure the pair of objects is collectible without losing the event?
The actual solution is much more complex because I have to worry about multiple threads (the finalizer runs on a background thread, and unregistering COM event has to invoke onto the COM thread, which can trigger deadlock if I'm not careful). But here's the basic idea of how to structure things to make sure it can get garbage collected:
public class DocumentWrapper
{
private COMDocument doc;
private WeakHandler closeHandler;
public DocumentWrapper(COMDocument doc)
{
this.doc = doc;
this.closeHandler = new WeakHandler(this);
}
~DocumentWrapper()
{
if (closeHandler != null)
closeHandler.Unregister();
}
public bool IsAlive { get { return this.doc != null; } }
private class WeakHandler : IDisposable
{
private WeakReference owner;
public WeakHander(DocumentWrapper owner)
{
this.owner = new WeakReference(owner);
owner.doc.OnClose += Unregister();
}
private void Unregister()
{
if (owner == null)
return;
var target = owner.Target as DocumentWrapper;
if (target != null && target.doc != null)
{
target.doc.OnClose -= OnClose;
target.closeHandler = null;
GC.SupressFinalize(target);
}
this.owner = null;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With