Skip to content

Instantly share code, notes, and snippets.

@Torvin
Last active November 12, 2015 23:26
Show Gist options
  • Save Torvin/157b2a3e2f34d3491c8c to your computer and use it in GitHub Desktop.
Save Torvin/157b2a3e2f34d3491c8c to your computer and use it in GitHub Desktop.
WeakDelegeate
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public class WeakDelegate<T>
where T : class
{
private static readonly Func<WeakDelegate<T>, T> InvokerFactory = CreateInvokerFactory();
/// <summary>
/// Creates and returns a delegate in this form: (WeakDelegate{T} wd) => ((arg1, arg2,..) => wd.DoInvoke(new object[] { arg1, arg2,.. })).
/// We can afterwards pass a reference to WeakDelegate and obtain a working invoker
/// </summary>
/// <remarks>
/// This method is called only once for each T.
/// </remarks>
private static Func<WeakDelegate<T>, T> CreateInvokerFactory()
{
var type = typeof(T);
if (!typeof(Delegate).IsAssignableFrom(type))
throw new ArgumentException("T should be a delegate returning void");
var wd = Expression.Parameter(typeof(WeakDelegate<T>));
// `invoker` is a lambda in form: (arg1, arg2,..) => wd.DoInvoke(new object[] { arg1, arg2,.. })
var method = type.GetMethod("Invoke"); // Delegate.Invoke() has parameters types matching delegate parameters types
var pars = method.GetParameters().Select(pi => Expression.Parameter(pi.ParameterType)).ToArray();
var invoker =
Expression.Lambda<T>(
Expression.Call(wd, GetMethod(x => x.DoInvoke(null)), Expression.NewArrayInit(typeof(object), pars.Select(p => Expression.Convert(p, typeof(object))))),
pars
);
return (Func<WeakDelegate<T>, T>)Expression.Lambda(invoker, new[] { wd }).Compile();
}
// clever way to get MethodInfo without Type.GetMethod()
private static System.Reflection.MethodInfo GetMethod(Expression<Action<WeakDelegate<T>>> action)
{
return ((MethodCallExpression)action.Body).Method;
}
private readonly T _invoker;
private readonly List<WeakReference<T>> _actions = new List<WeakReference<T>>();
public WeakDelegate()
{
_invoker = InvokerFactory(this);
}
public void Add(T action)
{
lock (_actions)
_actions.Add(new WeakReference<T>(action));
}
private void DoInvoke(object[] args)
{
lock (_actions)
{
var dead = new List<WeakReference<T>>();
foreach (var reference in _actions)
{
T action;
if (reference.TryGetTarget(out action))
{
((Delegate)(object)action).DynamicInvoke(args);
}
else
{
dead.Add(reference);
}
}
foreach (var reference in dead)
_actions.Remove(reference);
}
}
/// <summary>
/// Looks like a property, but acts like a method!
/// </summary>
public T Invoke
{
get { return _invoker; }
}
}
class Demo
{
public static void Main()
{
var wd1 = new WeakDelegate<Action<int>>();
var wd2 = new WeakDelegate<EventHandler>();
wd1.Add(x => Console.WriteLine("Action<int> called with: " + x));
wd2.Add((sender, args) => Console.WriteLine("EventHandler called"));
// type-safe invoke
wd1.Invoke(123);
wd2.Invoke(null, EventArgs.Empty);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment