/// <summary> /// Creates a weak EventHandler wrapper /// </summary> /// <param name="eventHandler">EventHandler to create a weak wrapper for</param> /// <param name="unregisterMethod">Lambda expression for unregistration (i.e. "h => obj.Event -= h")</param> /// <exception cref="ArgumentNullException"><paramref name="eventHandler"/> is null.</exception> /// <exception cref="NotSupportedException"><typeparamref name="TEventHandler"/> is not a delegate type.</exception> /// <exception cref="ArgumentException"><paramref name="eventHandler"/> is a generated closure and may go out of scope prematurely. -or- <paramref name="unregisterMethod"/> references the <paramref name="eventHandler" /> target.</exception> /// <remarks> /// The <paramref name="unregisterMethod"/> is held on to with a strong reference, so anything referenced in /// the lambda expression will not be garbage collected until the <paramref name="eventHandler"/> target goes /// out of scope and the event is unregistered. It is recommended that you store the object containing the /// event in a local variable and use the local variable for unsubcribing to minimize object retention /// caused by property chains (i.e. "h => obj1.Property1.Property2.Event -= h" will hold on to obj1, which /// will presumably hold on to whatever is in Property1 and Property2. Furthermore, if Property1 or /// Property2 changes before the event is unsubscribed, the unsubscription will occur on the wrong /// Property2 instance). /// </remarks> public WeakEventHandler(TEventHandler eventHandler, Action <TEventHandler> unregisterMethod) { if (eventHandler == null) { throw new ArgumentNullException("eventHandler"); } // Make sure TEventHandler is a Delegate since we can't enforce it in a where clause Delegate eventHandlerDelegate = eventHandler as Delegate; if (eventHandlerDelegate == null) { throw new NotSupportedException(typeof(TEventHandler).Name + " is not a delegate type."); } // if there is no eventHandler target to create a weak reference to, the method will never go out of scope if (eventHandlerDelegate.Method.IsStatic) { throw new ArgumentException("Cannot create weak event from static method, there is no object to reference that would go out of scope", "eventHandler"); } // we create a weak reference to the eventHandler target, but hold a strong reference to the unregisterMethod // if the unregisterMethod is on the eventHandler target, the target cannot be garbage collected and will be leaked. if (unregisterMethod != null && eventHandlerDelegate.Target != null && eventHandlerDelegate.Target == unregisterMethod.Target) { throw new ArgumentException("Unregister method exists on same object as event handler, which will prevent the object from being garbage collected. This is typically the result of using a class variable instead of a local variable in the unregister method.", "unregisterMethod"); } // the lifetime of a generated closue is only the lifetime of the delegate itself. Closures are not // generated if the anonymous delegate only references variables passed in to it, or the "this" psuedo-variable. if (OpenAction.HasClosureReference(eventHandlerDelegate)) { throw new ArgumentException("Cannot create weak event from generated closure", "eventHandler"); } if (_dispatchEventMethod == null) { // expression is slightly faster than reflection, and prevents FxCop from saying DispatchEvent is not referenced. Expression <Action <object, TEventArgs> > expression = (o, e) => DispatchEvent(o, e); var dispatchEvent = expression.Body as MethodCallExpression; if (dispatchEvent == null) { throw new InvalidOperationException("Unable to locate DispatchEvent method"); } _dispatchEventMethod = dispatchEvent.Method; } // create a TEventHandler delegate pointing at DispatchEvent Handler = (TEventHandler)(object)Delegate.CreateDelegate(typeof(TEventHandler), this, _dispatchEventMethod); // create weak reference to listener _methodInfo = eventHandlerDelegate.Method; _target = new WeakReference(eventHandlerDelegate.Target); // create strong reference to unregister method - if it goes away, we can't unregister. // this requires that the unregister method does not reference the listener, or the listener // will never go out of scope! _unregisterMethod = unregisterMethod; }
/// <summary> /// Constructs a new WeakAction from an existing Action /// </summary> /// <param name="action">Action to convert into a WeakAction.</param> /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="action"/> points at a generated closure.</exception> public WeakAction(Action <T> action) : base(action != null ? action.Target : null) { if (action == null) { throw new ArgumentNullException("action"); } Method = action.Method; if (Method.IsStatic) { Target = Method.DeclaringType; _openAction = (target, param) => action(param); } else { // Closures are generated internal classes used to pass local variables to an anonymous delegate. The lifetime of // the closure is determined by the lifetime of the delegate. Attempting to create a weak reference to the delegate // will not keep the closure around without some external reference. Since we can't validate the presence of an // external reference, we err on the side of caution and throw an exception. Closures are not generated if the // anonymous delegate only references variables passed in to it, or the "this" psuedo-variable. if (OpenAction.HasClosureReference(action)) { throw new ArgumentException("Cannot create weak reference from generated closure"); } } }
/// <summary> /// Calls the method pointed with the provided parameter. /// </summary> /// <param name="param">Parameter to pass to method.</param> /// <returns>True if the method was called, false if the target has been garbage collected.</returns> public bool Invoke(T param) { object target = Target; if (target == null || !IsAlive) { return(false); } if (_openAction == null) { _openAction = OpenAction.CreateOpenAction <T>(Method); } _openAction(target, param); return(true); }
private void DispatchEvent(object sender, TEventArgs e) { // if the target is still alive, call the event handler, otherwise detach from the event var target = _target.Target; if (target != null && _target.IsAlive) { if (_delegate == null) { _delegate = OpenAction.CreateOpenAction <object, TEventArgs>(_methodInfo); } _delegate(target, sender, e); } else if (_unregisterMethod != null) { _unregisterMethod(Handler); } }
/// <summary> /// Creates a static delegate from an Action, allowing the action's target to be provided at a later time. /// </summary> /// <param name="methodInfo">The method to create an OpenAction from.</param> /// <returns>An OpenAction not ties to any specific target.</returns> public Action <object, TParam1, TParam2> CreateOpenAction <TParam1, TParam2>(MethodInfo methodInfo) { Action <object, TParam1, TParam2> handler = null; if (_lastMethod == methodInfo) { handler = _lastDelegate as Action <object, TParam1, TParam2>; } if (handler == null) { var helper = new OpenAction <TTarget, TParam1, TParam2>(methodInfo); handler = helper.Dispatch; _lastMethod = methodInfo; _lastDelegate = handler; } return(handler); }