每次都可以有效地创建引用不同环境对象的正常闭包。如下例所示:
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);
}
答案 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
。