避免重新编译表达式树以在结果委托中引用不同的目标(self)

时间:2015-04-13 08:07:15

标签: c# .net

每次都可以有效地创建引用不同环境对象的正常闭包。如下例所示:

Action Test() {
    int x = 10;
    return () => x + 1;
}

每次调用Test()时,它都会返回一个绑定到不同环境对象(Delegate.Target)的闭包,当然它可以在不编译新表达式的情况下执行此操作。

我无法通过编译表达式树为我自己创建的代理找到一种方法。我得到的最接近的是在其他参数之前采用显式的“self”参数,然后在包装器中调用结果委托,就像绑定环境对象的示例一样,并将其作为第一个参数转发给已编译的委托。

那么有什么像Expression.Parameter可以用来引用委托的目标吗?如果是这样,有没有办法从现有的委托创建一个新的委托,同时更改其目标?

编辑:

这是我目前使用的一些代码。如果我可以做我要求的,那么我就不必在每个Create()方法中创建包装器函数以便有效地捕获目标,因此我可以用一个单独的方法替换所有的Create()方法TAction generic arg取代了具有T1 ...通用args的Action。

所以在电话网站我可以这样做:

WeakHandler.Create((int x) => Console.WriteLine("result: " + (x + capturedVar)))

而不是:

WeakHandler.Create<int>((int x) => Console.WriteLine("result: " + (x + capturedVar)))

以下是当前的实施:

using System;
using System.Reflection;
using System.Linq.Expressions;
using System.Linq;
using System.Collections.Generic;

namespace Capture.iOS
{
    /// <summary>
    /// Use the methods contained here to create weak event handlers.
    /// 
    /// This is especially useful on iOS where adding a handler function to a class
    /// will create a circular reference, and prevent it from being reclaimed,
    /// if the function references the class itself.
    /// 
    /// These utility functions create wrapper functions which accesses the target (self)
    /// reference through a weak reference, so that the object itself can be reclaimed,
    /// while some of its delegates are still registered event handlers.
    /// 
    /// If the "self" object has been GCed when the handler is invoked then nothing will happen.
    /// </summary>
    public class WeakHandler
    {
        public static Action Create(Action func)
        {
            var d = (Delegate)func;
            if (d.Target == null)
                return func;
            var targetRef = new WeakReference(d.Target);
            var invoker = (Action<object>)GetInvoker(d.GetMethodInfo());
            return () => {
                var target = targetRef.Target;
                if (target != null)
                    invoker(target);
            };
        }

        public static Action<T1> Create<T1>(Action<T1> func)
        {
            var d = (Delegate)func;
            if (d.Target == null)
                return func;
            var targetRef = new WeakReference(d.Target);
            var invoker = (Action<object, T1>)GetInvoker(d.GetMethodInfo());
            return p1 => {
                var target = targetRef.Target;
                if (target != null)
                    invoker(target, p1);
            };
        }

        public static Action<T1, T2> Create<T1, T2>(Action<T1, T2> func)
        {
            var d = (Delegate)func;
            if (d.Target == null)
                return func;
            var targetRef = new WeakReference(d.Target);
            var invoker = (Action<object, T1, T2>)GetInvoker(d.GetMethodInfo());
            return (p1, p2) => {
                var target = targetRef.Target;
                if (target != null)
                    invoker(target, p1, p2);
            };
        }

        private static readonly Dictionary<MethodInfo, Delegate> invokers = new Dictionary<MethodInfo, Delegate>();

        private static Delegate GetInvoker(MethodInfo method)
        {
            Delegate invoker;
            lock (invokers) {
                if (invokers.TryGetValue(method, out invoker))
                    return invoker;
            }

            var targetType = method.DeclaringType;
            if (targetType == null)
                throw new Exception("method.DeclaringType is null");
            var target = Expression.Parameter(typeof(object), "target");

            var inParams = new List<ParameterExpression>();
            var callParams = new List<Expression>();
            inParams.Add(target);
            foreach (var p in method.GetParameters()) {
                var temp = Expression.Parameter(p.ParameterType, p.Name);
                inParams.Add(temp);
                callParams.Add(temp);
            }

            var body = Expression.Call(Expression.Convert(target, targetType), method, callParams);
            var exp = Expression.Lambda(body, inParams);
            invoker = exp.Compile();
            lock (invokers) {
                invokers[method] = invoker;
            }
            return invoker;
        }
    }
}

EDIT2:

我想用这样的单个实现替换Create()方法,如果可能的话(我不需要创建一个包装器闭包来捕获目标,因为我能够绑定直接定位到预编译的Delegate):

public static TAction Create<TAction>(TAction func)
{
    var d = (Delegate)(object)func;
    if (d.Target == null)
        return func;
    return (TAction)GetWeakInvoker(d);
}

1 个答案:

答案 0 :(得分:1)

最后,示例代码:)

不要试图用表达式树重写它,这太过分了。

相反,只需使用代码生成器(例如,集成在Visual Studio中的T4)来生成不同的Create方法并完成它。这也是.NET本身处理委托和相关方法的方式。这样您只需编写一次代码,但您将拥有所有必要的重载。

对于GetInvoker方法,您可以改为使用MethodInfo.CreateDelegate

var invoker = (Action<T1>)d.Method.CreateDelegate(typeof(Action<T1>), target);

请注意,您必须在包装功能中执行此操作,而不是在外部执行此操作 - 当然,您当时并不知道正确的target

LINQPad的完整示例:

public void Main()
{ 
  x = 52;

  var d = (Action)Test;
  var d2 = (Action)Delegate.CreateDelegate(typeof(Action), new UserQuery(), d.Method);

  d();
  d2();
}

int x = 42;

public void Test()
{
  x.Dump();
}

这显示了如何更改捕获的方法委托的目标 - d()调用将显示52,因为我已更改了字段值,而d2()具有新的UserQuery作为目标,因此会打印出默认值42