我正在嘗試編寫一個表達式樹,它可以使用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:Member'void theMethodName()'not field或property
這裡的目標是targetMethodRef
基本上表示在使用方法訂閱事件時在+=
的右側顯示的內容。
TLDR:如何創建表達式以將對像上的方法組作為參數傳遞給表達式樹中的函數調用?
它應該是這個。這裡的複雜性是你必須使用CreateDelegate
在lambda方法中創建一個委託。遺憾的是,似乎不可能在lambda方法中創建一個開放的委託(沒有target
的委託),然後在執行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;
}
Delegate
有一個帶有兩個參數的構造函數,即目標object
和IntPtr
,它是該方法的本機函數指針。它通常由CIL用於ldftn
/ ldvirtftn
,但.MethodHandle.GetFunctionPointer()
是相同的“東西”。所以我們在我們構建的lambda表達式中調用這個構造函數。