An example illustrating the problem
Imagine a class that has an object that publishes events and an object that consumes the events. If you want the the publisher to remain and clean up the consumer. Just by removing the references to the publisher the consumer will never be collected, since via the eventhandler the publisher still has a reference to the consumer. We have to remove the eventhandler manually if we want the consumer to be collected by the garbage collector.
Show Code Snippet - Handling default events
public class MyExample
{
private MyConsumer m_oConsumer = new MyConsumer();
private MyPublisher m_oPublisher = new MyPublisher();
public void IllustrateProblem()
{
m_oPublisher.MyNormalEvent +=
new EventHandler(m_oConsumer.MyPublisher_MyNormalEvent);
m_oPublisher.RaiseNormalEvent();
m_oConsumer = null;
GC.Collect();
}
}
An example illustrating the transparency of the solution
It can become a time-consuming process to use a memory profiler to check which objects in your application aren't being collected and which reference is the cause. This is why we have developed a QWeakDelegate. By merely changing the event of the MyPublisher class the eventhandler will become a WeakReference and the consumer will be freed normally. The following example uses a QWeakDelegate to publish events and it shows that no changes are neccessary for consuming events.
Show Code Snippet - Handling QWeakEvents
public class MyExample
{
private MyConsumer m_oConsumer = new MyConsumer();
private MyPublisher m_oPublisher = new MyPublisher();
public void IllustrateSolution()
{
m_oPublisher.MyWeakEvent +=
new EventHandler(m_oConsumer.MyPublisher_MyWeakEvent);
m_oPublisher.RaiseWeakEvent();
m_oConsumer = null;
GC.Collect();
}
}
How it works
We see now in the example above that for the consumer nothing actually changes. The change is completely transparent to the consumer. Of course, the publisher needs some code changes, but it's not a drastic change. The code snippet below shows the way a normal event is published:
Show Code Snippet - Publishing a default event
public class MyPublisher
{
public MyPublisher() {}
public event EventHandler MyNormalEvent;
protected virtual void OnMyNormalEvent(EventArgs e)
{
if (MyNormalEvent != null)
{
MyNormalEvent(this, e);
}
}
}
To change the event structure so that any consumers can be collected normally the following changes are neccesary:
Show Code Snippet - Publishing a QWeakEvent
public class MyPublisher : IQWeakEventPublisher
{
private bool m_bWeakEventHandlers = true;
private QWeakDelegate m_oMyWeakEventDelegate;
public MyPublisher()
{
m_oMyWeakEventDelegate = new QWeakDelegate();
}
public bool WeakEventHandlers
{
get { return m_bWeakEventHandlers; }
set { m_bWeakEventHandlers = value; }
}
[QWeakEvent]
public event EventHandler MyWeakEvent
{
add
{
m_oMyWeakEventDelegate = QWeakDelegate.Combine(m_oMyWeakEventDelegate, value,
this.WeakEventHandlers);
}
remove
{
m_oMyWeakEventDelegate = QWeakDelegate.Remove
(m_oMyWeakEventDelegate, value);
}
}
protected virtual void OnMyWeakEvent(EventArgs e)
{
m_oMyWeakEventDelegate =
QWeakDelegate.InvokeDelegate(
m_oMyWeakEventDelegate, this, e);
}
}
Consuming events
The publisher in the code snippet above publishes weak events, but you don't have control over all events. For instance, you can't change the way a button publishes the Click event. For these situations we have created the QWeakEventConsumer class. The code snippet below shows the MyConsumer class from the examples above.
Show Code Snippet - Consuming default events
public class MyConsumer
{
public MyConsumer() {}
public void MyPublisher_MyNormalEvent(
object sender,
EventArgs e)
{
}
}
m_oPublisher.MyWeakEvent +=
new EventHandler(m_oConsumer.MyPublisher_MyWeakEvent);
Both the consumer and publishers use the default way of consuming and publishing an event. To use a QWeakConsumer we change the code to the following:
Show Code Snippet - Consuming default events via a QWeakConsumer
public class MyWeakConsumer
{
public QWeakEventConsumerCollection EventConsumers;
public MyConsumer()
{
EventConsumers = new EventConsumers();
}
public void MyPublisher_MyNormalEvent(
object sender,
EventArgs e)
{
}
}
m_oConsumer.EventConsumers.Add(new QWeakEventConsumer(
new EventHandler(
tmp_oWeakConsumer.MyPublisher_MyNormalEvent),
m_oPublisher,
"MyNormalEvent")
);