表达式树中的访问方法组

Access method group within expression tree

我正在尝试编写一个表达式树,它可以使用 MethodInfo 给出的方法订阅 EventInfo 给出的事件。表达式树应该编译成一个Action<object, object>,其中参数是事件源对象和订阅对象。 EventInfo 和 MethodInfo 保证兼容。

这是我目前的情况:

// Given the following
object Source = /**/;           // the object that will fire an event
EventInfo SourceEvent = /**/;   // the event that will be fired
object Target = /**/;           // the object that will subscribe to the event
MethodInfo TargetMethod = /**/; // the method that will react to the event

// setting up objects involved
var sourceParam = Expression.Parameter(typeof(object), "source");
var targetParam = Expression.Parameter(typeof(object), "target");
var sourceParamCast = Expression.Convert(sourceParam, SourceEvent.DeclaringType);
var targetParamCast = Expression.Convert(targetParam, TargetMethod.DeclaringType);

// Get subscribing method group. This is where things fail
var targetMethodRef = Expression.MakeMemberAccess(targetParamCast, TargetMethod);
// Subscribe to the event
var addMethodCall = Expression.Call(sourceParamCast, SourceEvent.AddMethod, targetMethodRef);

var lambda = Expression.Lambda<Action<object, object>>(addMethodCall, sourceParam, targetParam);
var subscriptionAction = lambda.Compile();

// And then later, subscribe to the event
subscriptionAction(Source, Target);

在调用 MakeMemberAccess 时出现以下异常:

ArgumentException: Member 'void theMethodName()' not field or property

这里的目标是让 targetMethodRef 本质上表示在使用方法订阅事件时 += 右侧会出现的内容。

TLDR:如何创建表达式以将对象上的方法组作为参数传递给表达式树中的函数调用?

应该是这个。这里的复杂性在于您必须使用 CreateDelegate 在 lambda 方法中创建一个委托。遗憾的是,似乎不可能创建一个开放委托(没有 target 的委托)在 lambda 方法内编译,然后在执行 lambda 方法时在 lambda 方法内 "close" 它。或者至少我不知道该怎么做。 CreateDelegate可惜有点慢

static Action<object, object> MakeFunc(EventInfo sourceEvent, MethodInfo targetMethod)
{
    // setting up objects involved
    var sourceParam = Expression.Parameter(typeof(object), "source");
    var targetParam = Expression.Parameter(typeof(object), "target");
    var sourceParamCast = Expression.Convert(sourceParam, sourceEvent.DeclaringType);
    var targetParamCast = Expression.Convert(targetParam, targetMethod.DeclaringType);
    var createDelegate = typeof(Delegate).GetMethod(nameof(Delegate.CreateDelegate), BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(Type), typeof(object), typeof(MethodInfo) }, null);

    // Create a delegate of type sourceEvent.EventHandlerType
    var createDelegateCall = Expression.Call(createDelegate, Expression.Constant(sourceEvent.EventHandlerType), targetParam, Expression.Constant(targetMethod));

    // Cast the Delegate to its real type
    var delegateCast = Expression.Convert(createDelegateCall, sourceEvent.EventHandlerType);

    // Subscribe to the event
    var addMethodCall = Expression.Call(sourceParamCast, sourceEvent.AddMethod, delegateCast);

    var lambda = Expression.Lambda<Action<object, object>>(addMethodCall, sourceParam, targetParam);
    var subscriptionAction = lambda.Compile();

    return subscriptionAction;
}

嗯...可以通过调用委托构造函数来完成。通过试用构建(尚未找到太多关于此的文档):

static Action<object, object> MakeFunc(EventInfo sourceEvent, MethodInfo targetMethod)
{
    // setting up objects involved
    var sourceParam = Expression.Parameter(typeof(object), "source");
    var targetParam = Expression.Parameter(typeof(object), "target");
    var sourceParamCast = Expression.Convert(sourceParam, sourceEvent.DeclaringType);
    var targetParamCast = Expression.Convert(targetParam, targetMethod.DeclaringType);

    ConstructorInfo delegateContructror = sourceEvent.EventHandlerType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object), typeof(IntPtr) }, null);
    IntPtr fp = targetMethod.MethodHandle.GetFunctionPointer();

    // create the delegate
    var newDelegate = Expression.New(delegateContructror, targetParam, Expression.Constant(fp));

    // Subscribe to the event
    var addMethodCall = Expression.Call(sourceParamCast, sourceEvent.AddMethod, newDelegate);

    var lambda = Expression.Lambda<Action<object, object>>(addMethodCall, sourceParam, targetParam);
    var subscriptionAction = lambda.Compile();

    return subscriptionAction;
}

Delegates 有一个带有两个参数的构造函数,目标 objectIntPtr 是指向该方法的本机函数指针。它通常由 CIL 与 ldftn/ldvirtftn 一起使用,但 .MethodHandle.GetFunctionPointer() 与 "thing" 相同。所以我们在构建的 lambda 表达式中调用这个构造函数。