【问题标题】:Access method group within expression tree表达式树中的访问方法组
【发布时间】:2018-06-03 04:09:18
【问题描述】:

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

这是我目前所拥有的:

// 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:成员 'void theMethodName()' 不是字段或属性

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

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

【问题讨论】:

    标签: c# expression-trees linq-expressions


    【解决方案1】:

    应该是这样的。这里的复杂性在于您必须在 lambda 方法中使用CreateDelegate 创建一个委托。遗憾的是,似乎不可能创建一个开放委托(没有target 的委托)在 lambda 方法内编译,然后在 lambda 方法执行时在 lambda 方法内“关闭”它。或者至少我不知道该怎么做。 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 有一个带有两个参数的构造函数,目标object 和一个IntPtr,它是指向该方法的本机函数指针。 CIL 通常将它与ldftn/ldvirtftn 一起使用,但.MethodHandle.GetFunctionPointer() 是同一个“东西”。所以我们在我们构建的 lambda 表达式中调用这个构造函数。

    【讨论】:

    • 感谢您的回答!不幸的是,使用表达式树编写此代码的部分目的是尽量避免对每个目标实例调用CreateDelegate。我不介意表达式编译或 CreateDelegate 调用每个 (EventInfo, MethodInfo) 对,但对每个实例都这样做太昂贵了。
    • @DeanJohnson 添加了第二个没有 CreateDelegate 的变体
    • @DeanJohnson 虽然没有记录,但在 Microsoft 代码 here 中至少有一个案例他们做同样的事情:使用 GetFunctionPointer() 的结果调用委托构造函数
    • 感谢更新版本 - 这正是我所需要的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多