Introduction
When using usual C# events, registering an event handler creates a strong reference from the event source to the listening object. If the source object has a longer lifetime than the listener, and if the listener no longer needed (there are no other references), to avoid memory leak the listener object must unsubscribe from the source event, if it does not, the use normal .NET events causes a memory leak.There are lots of different approaches to resolve this problem. This article will explain some of them and discuss their advantages and disadvantages.
Method 1. Using weak-delegate without code-generation.
This method is very simple, we need to create wrapper for a weak-delegate:public class WeakDelegate<TDelegate> : IEquatable<TDelegate> where TDelegate : class { private readonly MethodInfo _method; private readonly WeakReference _targetReference; public WeakDelegate(Delegate realDelegate) { _targetReference = realDelegate.Target != null ? new WeakReference(realDelegate.Target) : null; _method = realDelegate.Method; } public bool IsAlive { get { return _targetReference == null || _targetReference.IsAlive; } } public TDelegate GetDelegate() { //If it's non-static create delegate with target. if (_targetReference != null) return Delegate.CreateDelegate(typeof(TDelegate), _targetReference.Target, _method) as TDelegate; return Delegate.CreateDelegate(typeof(TDelegate), _method) as TDelegate; } public bool Equals(TDelegate other) { var d = (Delegate)(object)other; return d != null && d.Target == _targetReference.Target && d.Method.Equals(_method); } public void Invoke(params object[] args) { var handler = (Delegate) (object) GetDelegate(); handler.DynamicInvoke(args); } }
and for a weak-event:
public class WeakEvent<TEventHandler> where TEventHandler : class { private readonly List<WeakDelegate<TEventHandler>> _handlers; public WeakEvent() { _handlers = new List<WeakDelegate<TEventHandler>>(); } public void AddHandler(TEventHandler handler) { var d = (Delegate)(object)handler; lock (_handlers) _handlers.Add(new WeakDelegate<TEventHandler>(d)); } public void RemoveHandler(TEventHandler handler) { // also remove "dead" (garbage collected) handlers lock (_handlers) _handlers.RemoveAll(wd => !wd.IsAlive || wd.Equals(handler)); } public void Raise(params object[] values) { lock (_handlers) { for (int index = 0; index < _handlers.Count; index++) { var weakDelegate = _handlers[index]; if (weakDelegate.IsAlive) weakDelegate.Invoke(values); else { _handlers.Remove(weakDelegate); index--; } } } } }
public class Alpha { private readonly WeakEvent<Action<object>> _myEvents; public Alpha() { _myEvents = new WeakEvent<Action<object>>(); } public event Action<object> MyEvent { add { _myEvents.AddHandler(value); } remove { _myEvents.RemoveHandler(value); } } protected virtual void OnMyEvent(object value) { _myEvents.Raise(value); } }
Advantages
- Works on all platforms
- Simple and effective
- Slow, because to raise an event creates new instance of delegate
Method 2. Using weak-delegate created by DynamicMethod.
The second method uses the DynamicMethod to generate a weak version of delegate. The code below demonstrates one approach to generate weak delegate using emit.The first step it's create class that represents a information about weak event:
public sealed class WeakEventInfo { #region Const public static readonly FieldInfo TargetFieldInfo = typeof(WeakEventInfo).GetField("Target"); public static readonly FieldInfo UnsubcribeDelegateFieldInfo = typeof(WeakEventInfo).GetField("UnsubcribeDelegate"); public static readonly FieldInfo DelegateFieldInfo = typeof(WeakEventInfo).GetField("Delegate"); public static readonly MethodInfo ClearMethodInfo = typeof(WeakEventInfo).GetMethod("Clear"); #endregion #region Fields public Delegate Delegate; public WeakReference Target; public Action<Delegate> UnsubcribeDelegate; public MethodInfo OriginalMethod; #endregion #region Methods public void Clear() { if (UnsubcribeDelegate != null) UnsubcribeDelegate(Delegate); Delegate = null; Target = null; UnsubcribeDelegate = null; OriginalMethod = null; } #endregion }
- Delegate - contains weak version of delegate, used to unsubscribe when target was garbage collected.
- Target - contains a delegate target as WeakReference.
- UnsubcribeDelegate - contains a delegate to unsubscribe when target was garbage collected.
- OriginalMethod - contains information about method that should be invoked.
internal static DynamicMethod ConvertToWeakDelegate(Delegate originalAction) { MethodInfo originalMethodDelegate = originalAction.Method; ParameterInfo[] parameterInfos = originalMethodDelegate.GetParameters(); var types = new Type[parameterInfos.Length + 1]; types[0] = typeof(WeakEventInfo); for (int i = 0; i < parameterInfos.Length; i++) { types[i + 1] = parameterInfos[i].ParameterType; } var dynamicMethod = new DynamicMethod( string.Format("_DynamicMethod_{0}_{1}", originalMethodDelegate.DeclaringType.Name, Guid.NewGuid().ToString("N")), originalMethodDelegate.ReturnType, types, originalMethodDelegate.DeclaringType.Module, true); ILGenerator ilGenerator = dynamicMethod.GetILGenerator(); Label originalDelegateIsNotNull = ilGenerator.DefineLabel(); Label returnNullLabel = ilGenerator.DefineLabel(); LocalBuilder declareLocal = ilGenerator.DeclareLocal(originalAction.Target.GetType()); for (Int16 i = 0; i < parameterInfos.Length; i++) { ParameterInfo parameterInfo = parameterInfos[i]; if (!parameterInfo.IsDefined(typeof(OutAttribute), true)) continue; Type elementType = parameterInfo.ParameterType.GetElementType(); ilGenerator.Emit(OpCodes.Ldarg, i + 1); ilGenerator.Emit(OpCodes.Initobj, elementType); } ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldfld, WeakEventInfo.TargetFieldInfo); ilGenerator.Emit(OpCodes.Brfalse_S, returnNullLabel); //if (weakEventInfo.Target == null) // return null; ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldfld, WeakEventInfo.TargetFieldInfo); ilGenerator.Emit(OpCodes.Callvirt, typeof(WeakReference).GetProperty("Target").GetGetMethod()); ilGenerator.Emit(OpCodes.Castclass, declareLocal.LocalType); ilGenerator.Emit(OpCodes.Stloc, declareLocal); //var target = weakEventInfo.OriginalDelegate.Target; ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Brtrue_S, originalDelegateIsNotNull); //if (target == null){ ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Callvirt, WeakEventInfo.ClearMethodInfo); //weakEventInfo.Clear(); //} ilGenerator.MarkLabel(returnNullLabel); if (dynamicMethod.ReturnType != typeof(void)) { if (dynamicMethod.ReturnType.IsValueType) { LocalBuilder result = ilGenerator.DeclareLocal(dynamicMethod.ReturnType); ilGenerator.Emit(OpCodes.Ldloca_S, result); ilGenerator.Emit(OpCodes.Initobj, dynamicMethod.ReturnType); ilGenerator.Emit(OpCodes.Ldloc_S, result); } else ilGenerator.Emit(OpCodes.Ldnull); } ilGenerator.Emit(OpCodes.Ret); //return null; //} ilGenerator.MarkLabel(originalDelegateIsNotNull); ilGenerator.Emit(OpCodes.Ldloc_0); for (Int16 i = 1; i < types.Length; i++) { ilGenerator.Emit(OpCodes.Ldarg_S, i); } ilGenerator.Emit(OpCodes.Callvirt, originalMethodDelegate); ilGenerator.Emit(OpCodes.Ret); //return target.Invoke(arg1, arg2, .....); return dynamicMethod; } public static Delegate ConvertToWeakDelegate(Delegate originalAction, Action<Delegate> unsubcribeDelegate) { if (originalAction == null) throw new ArgumentNullException("originalAction"); if (originalAction.Target == null || originalAction.Target is WeakEventInfo) return originalAction; DynamicMethod weakMethodInternal = ConvertToWeakDelegate(originalAction); var weakEventInfo = new WeakEventInfo { Target = new WeakReference(originalAction.Target), OriginalMethod = originalAction.Method }; if (unsubcribeDelegate != null) weakEventInfo.UnsubcribeDelegate = unsubcribeDelegate; Delegate weakDelegate = weakMethodInternal.CreateDelegate(originalAction.GetType(), weakEventInfo); if (unsubcribeDelegate != null) weakEventInfo.Delegate = weakDelegate; return weakDelegate; }
Now we can use this methods to make weak-delegates:
public class Beta { public event Action<object> MyEvent; protected virtual void OnMyEvent(object value) { Action<object> action = MyEvent; if (action != null) action(value); } } .... private void Method() { var beta = new Beta(); beta.MyEvent += (Action<object>) ConvertToWeakDelegate(new Action<object>(OnMyEvent), @delegate => { beta.MyEvent -= (Action<object>) @delegate; }); } private void OnMyEvent(object value) { }
public static T ConvertToWeakDelegate<T>(T originalAction, Action<T> unsubcribeDelegate) where T : class { var @delegate = originalAction as Delegate; if (@delegate == null) throw new ArgumentNullException("originalAction", "Parameter can only be a delegate."); Action<Delegate> uns = null; if (unsubcribeDelegate != null) uns = d => unsubcribeDelegate((T)((object)d)); return ConvertToWeakDelegate(@delegate, uns) as T; }
private void Method() { var beta = new Beta(); beta.MyEvent += ConvertToWeakDelegate(new Action<object>(OnMyEvent), @delegate => beta.MyEvent -= @delegate); }
Advantages
- Fast, nearly no code overhead.
- Does not work in partial trust because it uses reflection on private methods, read this article for more information.
Method 3. Using weak-delegate created by LambdaExpression.
This method is very similar to the method 2, different only in way to create the weak-delegate. To make delegate as weak this method uses the LambdaExpression and the WeakEventInfo class.internal static Delegate ConvertToWeakDelegate(WeakEventInfo weakEventInfo, Delegate method) { var methodInfo = weakEventInfo.OriginalMethod; Type targetType = method.Target.GetType(); LabelTarget resultLabel = Expression.Label(); ConstantExpression constantExpression = Expression.Constant(weakEventInfo); ParameterExpression resultVariable = Expression.Variable(methodInfo.ReturnType == typeof(void) ? typeof(object) : methodInfo.ReturnType); Expression.Assign(resultVariable, Expression.Default(resultVariable.Type)); ParameterExpression localField = Expression.Variable(targetType); ParameterExpression[] methodCallParameters = methodInfo.GetParameters() .Select( info => Expression.Parameter(info.ParameterType)) .ToArray(); Expression methodCallExpression = Expression.Call(localField, methodInfo, methodCallParameters); if (methodInfo.ReturnType != typeof(void)) methodCallExpression = Expression.Assign(resultVariable, methodCallExpression); MemberExpression weakReferenceField = Expression.Field(constantExpression, WeakEventInfo.TargetFieldInfo); BlockExpression body = Expression.Block (new[] { resultVariable }, Expression.IfThenElse(Expression.Equal(weakReferenceField, Expression.Constant(null)), Expression.Return(resultLabel), //if (weakEventInfo.Target == null) // return null; //else Expression.Block ( new[] { localField }, Expression.Assign(localField, Expression.Convert( Expression.Property(weakReferenceField, typeof(WeakReference) .GetProperty("Target")), targetType)), //var target = weakEventInfo.OriginalDelegate.Target; //if (target == null){ Expression.IfThenElse( Expression.Equal(localField, Expression.Constant(null)), Expression.Block ( Expression.Call(constantExpression, WeakEventInfo.ClearMethodInfo), Expression.Return(resultLabel) ), //weakEventInfo.Clear(); //return null; //} //else //return target.Invoke(arg1, arg2, .....); Expression.Block(methodCallExpression, Expression.Return(resultLabel))))), Expression.Label(resultLabel), resultVariable); return Expression.Lambda(method.GetType(), body, methodCallParameters).Compile(); } public static Delegate ConvertToWeakDelegate(Delegate originalAction, Action<Delegate> unsubcribeDelegate) { if (originalAction == null) throw new ArgumentNullException("originalAction"); if (originalAction.Target == null) return originalAction; var weakEventInfo = new WeakEventInfo { Target = new WeakReference(originalAction.Target), OriginalMethod = originalAction.Method }; Delegate result = ConvertToWeakDelegate(weakEventInfo, originalAction); if (unsubcribeDelegate != null) weakEventInfo.UnsubcribeDelegate = unsubcribeDelegate; if (unsubcribeDelegate != null) weakEventInfo.Delegate = result; return result; }
Advantages
- Fast, nearly no code overhead.
- Work in partial trust because it has access to non public members.
- The overhead of creating delegate, because the expression is difficult to cache.
Performance
Here are the benchmark results of an event with two registered delegates (one instance method and one static method):- Original event: 1000000 calls times is 0.0278125 seconds.
- Method 1: 1000000 calls times is 12.1321455 seconds.
- Method 2: 1000000 calls times is 0.0370756 seconds.
- Method 3: 1000000 calls times is 0.0692381 seconds.
MugenInjection
The MugenInjection, also provides methods to create weak-delegates, it supports the second and the third method. This code demonstrates how it works:private void Method() { var beta = new Beta(); //You can change the type of ReflectionAccessProvider. //InjectorUtils.ReflectionAccessProvider = new EmitReflectionAccessProvider(); //InjectorUtils.ReflectionAccessProvider = new ExpressionReflectionAccessProvider(); beta.MyEvent += ReflectionExtension.ConvertToWeakDelegate(new Action<object>(OnMyEvent), @delegate => beta.MyEvent -= @delegate); }
Conclusion
If you do not need the high performance you can use the first method, otherwise, your choice will be the second or the third method. The third method can work in partial trust, which is very important if you are using Silverlight.Moreover, there are lots of different approaches to this problem and this article explain a few of them.