by Geert
29. December 2011 16:01
Recently I needed weak actions for the message mediator implementation of Catel. I looked at a few examples and most seem to do something like this:
1: public class WeakAction
2: {
3: private readonly Action _action;
4: private WeakReference _reference;
5:
6: public WeakAction(object target, Action action)
7: {
8: _reference = new WeakReference(target);
9: _action = action;
10: }
11:
12: public bool IsAlive
13: {
14: get { return (_reference != null) ? _reference.IsAlive : false; }
15: }
16:
17: public void Execute()
18: {
19: if (_action != null && IsAlive)
20: {
21: _action();
22: }
23: }
24: }
There are 2 downsides of this approach:
- It doesn’t allow static handlers
- It causes memory leaks
The problem
So if you are using such an implementation of a weak action, all your alarm bells should be ringing right now. The issue is in the storage of the Action object. The action is a method handler. To be able to call the handler, it must store the reference (as Target property on the Action class) of the target.
However, this means that there is now a reference to the target, so the target can never be garbage collected, so you are now dealing with a big issue in your application because you think that you are using weak events, but you aren’t.
The Solution
I already wrote a true weak event listener once, so this one should be easy. What you basically need to do is to write an open instance delegate, create a handler without a target for it and late-bind the handler. Sounds complex, but once you get the grip of it, it’s pretty simple. Below is the code for a non-generic Action. All this code (and the generic implementation) can be found at http://catel.codeplex.com.
1: public class WeakAction
2: {
3: public delegate void OpenInstanceAction<TTarget>(TTarget @this);
4: private Delegate _action;
5: private WeakReference _reference;
6:
7: public WeakAction(object target, Action action)
8: {
9: _reference = new WeakReference(target);
10:
11: var targetType = (target != null) ? target.GetType() : typeof(object);
12: var delegateType = typeof(OpenInstanceAction<>).MakeGenericType(targetType);
13:
14: _action = Delegate.CreateDelegate(delegateType, null, action.Method);
15: }
16:
17: public bool IsAlive
18: {
19: get { return (_reference != null) ? _reference.IsAlive : false; }
20: }
21:
22: public void Execute()
23: {
24: if (_action != null && IsAlive)
25: {
26: _action.DynamicInvoke(_reference.Target);
27: }
28: }
29: }