An example illustrating the problem
Imagine a class that has an object that publishes events and an object that consumes the events. Just by removing the references
the object will never be collected, since the eventhandler still connects the two. 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);
}
}
Publishing events - Summary
To publish events with a weak reference, so that the eventhandler does not cause objects to remain
uncollected by the garbage collector, use a QWeakEvent. The only differences when using a
QWeakEvent are:
-
The publisher should be marked as IQWeakEventPublisher
-
The publisher should implement a boolean property WeakEventHandlers to indicate if newly added
event handlers should be weak referenced or not.
-
Instead of declaring an event like:
public event EventHandler MyEvent
declare the event as a QWeakEvent:
[QWeakEvent]
public event EventHandler MyWeakEvent
{
add { m_oDelegate = QWeakDelegate.Combine(m_oDelegate, value, this.WeakEventHandlers); }
remove { m_oDelegate = QWeakDelegate.Remove(m_oDelegate, value); }
}
-
When raising the event instead of:
if (MyEvent != null) MyEvent(this,e);
use:
m_oDelegate = QWeakDelegate.InvokeDelegate(m_oDelegate, 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 with 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"
)
);
Consuming events - Summary
Use QWeakEventConsumers for strong (normal) events from third party objects or objects from the
.NET library.
The only differences when using a QWeakEventConsumers are:
-
The consumer gets a QWeakEventConsumerCollection that will contain all eventhandlers.
-
Instead attaching the event as:
Publisher.Event += new EventHandler(Method)
We attach the event by using the QWeakEventConsumers Collection:
Consumer.EventConsumers.Add(new QWeakEventConsumer(new EventHandler(Method), Publisher, "Event"))
|