Example #1
0
        /// <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;
        }
Example #2
0
        /// <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");
                }
            }
        }