Доступ к группе методов в дереве выражений - PullRequest
0 голосов
/ 03 июня 2018

Я пытаюсь написать дерево выражений, которое может подписаться на событие, заданное EventInfo с помощью метода, заданного MethodInfo.Дерево выражений должно компилироваться в 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: Как создать выражение для передачи группы методов объекта в качестве параметра для вызова функции в дереве выражений?

1 Ответ

0 голосов
/ 03 июня 2018

Так и должно быть.Сложность здесь заключается в том, что вам нужно создать делегат внутри лямбда-метода с CreateDelegate.К сожалению, кажется невозможным создать открытый делегат (делегат без target) для компиляции внутри лямбда-метода и затем «закрыть» его внутри лямбда-метода при выполнении лямбда-метода.Или, по крайней мере, я не знаю, как это сделать.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;
}

Delegate s имеет конструктор с двумя параметрами: target object и IntPtr, который является указателем на встроенную функциюк методу.Обычно он используется CIL с ldftn / ldvirtftn, но .MethodHandle.GetFunctionPointer() - это то же самое.Поэтому мы вызываем этот конструктор внутри лямбда-выражения, которое мы строим.

...