pmunin
3/26/2017 - 5:00 AM

WeakDelegate (weak event handling)

WeakDelegate (weak event handling)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace WeakDelegates
{
    /// <summary>
    /// Allows to create weak event handlers, that will unsubscribed itself when source handler object is destroyed by GC
    /// </summary>
    public static class WeakDelegate
    {
        /// <summary>
        /// Create weak delegate object
        /// </summary>
        /// <typeparam name="TDelegate"></typeparam>
        /// <param name="targetDelegate">target delegate</param>
        /// <param name="generateWrapper">Should return a TDelegate wrapper that simply uses GetTarget method and if it's not null - invoke it with parameters of the delegate</param>
        /// <returns></returns>
        public static WeakDelegate<TDelegate> CreateWeakDelegate<TDelegate>(TDelegate targetDelegate, Func<WeakDelegate<TDelegate>, TDelegate> generateWrapper)
            where TDelegate : class
        {
            var res = new WeakDelegate<TDelegate>(targetDelegate);
            res.Delegate = generateWrapper(res);
            return res;
        }
        public static TDelegate CreateWeakDelegate<TDelegate>(TDelegate target, Func<WeakDelegate<TDelegate>, TDelegate> generateWrapper, Action<WeakDelegate<TDelegate>> configure)
            where TDelegate : class
        {
            var res = new WeakDelegate<TDelegate>(target);
            res.Delegate = generateWrapper(res);
            configure?.Invoke(res);
            return res.Delegate;
        }

        public static WeakDelegate<Action> Create(Action action)
        {
            return CreateWeakDelegate(action, w => ()=>w.GetTarget()?.Invoke());
        }
        public static WeakDelegate<Action<T>> Create<T>(Action<T> action)
        {
            return CreateWeakDelegate(action, w=>(p1) => w.GetTarget()?.Invoke(p1));
        }
        public static WeakDelegate<Action<T1,T2>> Create<T1,T2>(Action<T1,T2> action)
        {
            return CreateWeakDelegate(action, wr=> (p1,p2) => wr.GetTarget()?.Invoke(p1,p2));
        }
        public static WeakDelegate<Action<T1, T2,T3>> Create<T1, T2,T3>(Action<T1, T2,T3> action)
        {
            return CreateWeakDelegate(action, wr => (p1, p2, p3) => wr.GetTarget()?.Invoke(p1, p2, p3));
        }

        public static WeakDelegate<Func<TRes>> Create<TRes>(Func<TRes> action)
        {
            return CreateWeakDelegate(action, wr => () =>
            {
                var target = wr.GetTarget();
                if(target!=null) return target();
                return default(TRes);
            });
        }
        public static WeakDelegate<Func<T, TRes>> Create<T, TRes>(Func<T, TRes> action)
        {
            return CreateWeakDelegate(action, wr => (p1) =>
            {
                var target = wr.GetTarget();
                if (target != null) return target(p1);
                return default(TRes);
            });
        }
        public static WeakDelegate<Func<T1, T2, TRes>> Create<T1, T2, TRes>(Func<T1, T2, TRes> action)
        {
            return CreateWeakDelegate(action, wr => (p1,p2) =>
            {
                var target = wr.GetTarget();
                if (target != null) return target(p1,p2);
                return default(TRes);
            });
        }
        public static WeakDelegate<Func<T1, T2, T3, TRes>> Create<T1, T2, T3, TRes>(Func<T1, T2, T3, TRes> action)
        {
            return CreateWeakDelegate(action, wr => (p1,p2,p3) =>
            {
                var target = wr.GetTarget();
                if (target != null) return target(p1,p2,p3);
                return default(TRes);
            });
        }

    }

    public class WeakDelegate<TDelegate> where TDelegate : class
    {
        /// <summary>
        /// Wrapped delegate that should be used instead
        /// </summary>
        public TDelegate Delegate;

        public WeakReference TargetWeakReference { get; }
        MethodInfo targetMethod = null;

        public WeakDelegate(TDelegate targetDelegate)
        {
            var targDelegate = targetDelegate as Delegate;
            if (targDelegate == null)
                throw new ArgumentException("Target must be delegate");
            targetMethod = targDelegate.Method;
            var targetObject = targDelegate.Target;
            TargetWeakReference = new WeakReference(targetObject);
        }

        Action ifDead;
        public void IfDead(Action ifDead)
        {
            this.ifDead = ifDead;
        }

        /// <summary>
        /// Returns target delegate and triggers ifDead in case reference is not alive anymore
        /// </summary>
        /// <returns></returns>
        public TDelegate GetTarget()
        {
            TDelegate res = default(TDelegate);
            //var isAlive = Target.TryGetTarget(out target);
            var isAlive = TargetWeakReference.IsAlive;
            if (!isAlive)
            {
                ifDead?.Invoke();
                return res;
            }
            var targetObj = TargetWeakReference.Target;
            res = System.Delegate.CreateDelegate(typeof(TDelegate), targetObj, targetMethod, true) as TDelegate;
            return res;
        }
    }

}