A good rule of thumb to live by is that long-lived objects should avoid referencing short-lived objects.
The reason for this is that the .NET garbage collector uses a mark and sweep algorithm to detemine if it can delete and reclaim an object. If it determines that a long-lived object should be kept alive (because you are using it, or because it's in a static field somewhere), it also assumes anything it references is being kept alive.
Conversely, going the other way is fine - a short-lived object can reference a long-lived object because the garbage collector will happily delete it if nothing else uses it.
For example:
- You shouldn't add items to a static collection, if those items won't be around for a while
- You shouldn't subscribe to static events from a short-lived object
The second example often throws people not familiar with how events work in .NET. When you subscribe to an event, the event handler keeps a list of subscribers. When the event is raised, it loops through the subscribers and notifies each one - it's a simple form of the observer pattern.
If you do find yourself needing to write this kind of code, and there isn't a good alternative design, then you generally need to have an unhook option. You might have a way to "remove" the short-lived object from the collection managed by the long-lived object, or you might unsubscribe from an event.
When unsubscribing isn't an option (because you don't trust people to call your Dispose/Unsubscribe method), you can make use of weak event handlers. WPF has its own implementation, but it's too complex for my feeble mind. Here's a simple snippet that I use:
[DebuggerNonUserCode]
public sealed class WeakEventHandler<TEventArgs> where TEventArgs : EventArgs
{
private readonly WeakReference _targetReference;
private readonly MethodInfo _method;
public WeakEventHandler(EventHandler<TEventArgs> callback)
{
_method = callback.Method;
_targetReference = new WeakReference(callback.Target, true);
}
[DebuggerNonUserCode]
public void Handler(object sender, TEventArgs e)
{
var target = _targetReference.Target;
if (target != null)
{
var callback = (Action<object, TEventArgs>)Delegate.CreateDelegate(typeof(Action<object, TEventArgs>), target, _method, true);
if (callback != null)
{
callback(sender, e);
}
}
}
}
When subscribing to events, instead of writing:
alarm.Beep += Alarm_Beeped;
Just write:
alarm.Beeped += new WeakEventHandler<AlarmEventArgs>(Alarm_Beeped).Handler;
Your subscriber can now be garbage collected without needing to manually unsubscribe (and without having to remember to). Here are some tests:
[TestFixture]
public class WeakEventsTests
{
#region Example
public class Alarm
{
public event PropertyChangedEventHandler Beeped;
public void Beep()
{
var handler = Beeped;
if (handler != null) handler(this, new PropertyChangedEventArgs("Beep!"));
}
}
public class Sleepy
{
private readonly Alarm _alarm;
private int _snoozeCount;
public Sleepy(Alarm alarm)
{
_alarm = alarm;
_alarm.Beeped += new WeakEventHandler<PropertyChangedEventArgs>(Alarm_Beeped).Handler;
}
private void Alarm_Beeped(object sender, PropertyChangedEventArgs e)
{
_snoozeCount++;
}
public int SnoozeCount
{
get { return _snoozeCount; }
}
}
#endregion
[Test]
public void ShouldHandleEventWhenBothReferencesAreAlive()
{
var alarm = new Alarm();
var sleepy = new Sleepy(alarm);
alarm.Beep();
alarm.Beep();
Assert.AreEqual(2, sleepy.SnoozeCount);
}
[Test]
public void ShouldAllowSubscriberReferenceToBeCollected()
{
var alarm = new Alarm();
var sleepyReference = null as WeakReference;
new Action(() =>
{
// Run this in a delegate to that the local variable gets garbage collected
var sleepy = new Sleepy(alarm);
alarm.Beep();
alarm.Beep();
Assert.AreEqual(2, sleepy.SnoozeCount);
sleepyReference = new WeakReference(sleepy);
})();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.IsNull(sleepyReference.Target);
}
[Test]
public void SubscriberShouldNotBeUnsubscribedUntilCollection()
{
var alarm = new Alarm();
var sleepy = new Sleepy(alarm);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
alarm.Beep();
alarm.Beep();
Assert.AreEqual(2, sleepy.SnoozeCount);
}
}

Observant readers will note that this example does keep a small "sacrifice" object alive in the form of the weak event handler wrapper, but it allows the subscriber to be collected. A more complicated API would allow you to unsubscribe the weak handler when the target is null. In my case, I'll keep the simple API and sacrifice the small object.
Discussion
Charles Strahan
Nice post, Paul.
I see you're using reflection there. You may want to look into using delegates instead; for the performance gain you get, I think it's worth the trivial amount of added complexity. Check out Jon Skeet's blog post:
http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx
If you're targeting .NET 3.5 or greater, you could use expressions to clean up the code a little:
Using an "open delegate", as discussed in Skeet's post, will give you the same functionality as the MethodInfo, only faster.
With that said, I am a bit of a perf addict, so perhaps most sane people would consider that a premature optimization.
Cheers,
-Charles
Mike Strobel
Charles,
Depending on how frequently the subscribed event is actually raised, I suspect you might incur a much greater performance penalty for the lambda compilation than you would for using reflection to invoke the handler.
My approach to weak event handling was to create a 'generic' weak event manager based on those used by WPF, which would work with any event type. Subscribing to an event looks like this:
My original implementation of GenericWeakEventManager used runtime-compiled lambda expressions much like yours, but having spent a lot of time in the LINQ/DLR sources (and its lambda compiler), I began to realize just how complex the lambda compilation process is. Since I'm a "perf addict" too, I went back and replaced the lambda expression compilation with a simple call to Delegate.Create, like the one Paul uses. The only significant difference is that my implementation only creates the delegate once per subscription, where his recreates the delegate for each invocation.
Just some food for thought, from one obsessive-compulsive perf nut to another :).
Cheers, Mike
Mike Strobel
Whoops, my code snippet above is actually wrong--as written, the DelegatingWeakEventHandler would likely get garbage collected prematurely. The subscriber would need to retain a reference to it like so:
The field required to hold the weak event listener reference is the price I pay not to use a 'sacrifice' object as Paul does. It's a trade-off either way.
tobi
You can let the weakreference track the delegate. Then you don't need to recreate it when the event fires.
tobi
Which would not work because the target does not reference the delegate^^
Charles Strahan
@Mike Strobel
Very neat. What does your implementation look like?
For those who are interested, Daniel Grunwald wrote an awesome article about Weak Events on CodeProject here:
http://www.codeproject.com/KB/cs/WeakEvents.aspx
Cheers,
-Charles
Mike Strobel
Hi Charles,
You can find my implementation here. The accompanying implementation of
DelegatingWeakEventListeneris written in C++/CLI, but here's a C# version for you:Here's a simple rundown:
DelegatingWeakEventListenerholds a reference to the subscriber's handler, but theGenericWeakEventManagerholds a weak reference to theDelegatingWeakEventListener. This prevents the listener from keeping the subscriber alive.GenericWeakEventManagerwhen the listener has been garbage collected. This purging process is automatically kicked off after garbage collection.GenericWeakEventManagerto subscribe to any type of event, as long as the signature has exactly two arguments: a 'sender' argument and anEventArgs-derived argument.GenericWeakEventManagerthat you can remove; they simply provide some hints to ReSharper's code analysis engine.Cheers,
Mike