从通用事件处理程序重定向到动态方法
本文关键字:动态 方法 重定向 程序 事件处理 | 更新日期: 2023-09-27 18:12:24
我正试图编写一个用于触发从任意事件调用方法的类,但我被卡住了,因为我根本无法找出从发出的MSIL代码引用'this'的方法。
这个例子应该描述我正在寻找的内容:
class MyEventTriggeringClass
{
private object _parameter;
public void Attach(object source, string eventName, object parameter)
{
_parameter = parameter;
var e = source.GetType().GetEvent(eventName);
if (e == null) return;
hookupDelegate(source, e);
}
private void hookupDelegate(object source, EventInfo e)
{
var handlerType = e.EventHandlerType;
// (omitted some validation here)
var dynamicMethod = new DynamicMethod("invoker",
null,
getDelegateParameterTypes(handlerType), // (omitted this method in this exmaple)
GetType());
var ilgen = dynamicMethod.GetILGenerator();
var toBeInvoked = GetType().GetMethod(
"invokedMethod",
BindingFlags.NonPublic | BindingFlags.Instance);
ilgen.Emit(OpCodes.Ldarg_0); // <-- here's where I thought I could push 'this' (failed)
ilgen.Emit(OpCodes.Call, toBeInvoked);
ilgen.Emit(OpCodes.Ret);
var sink = dynamicMethod.CreateDelegate(handlerType);
e.AddEventHandler(source, sink);
}
private void invokedMethod()
{
Console.WriteLine("Value of _parameter = " + _parameter ?? "(null)");
// output is always "(null)"
}
}
下面是我设想的使用该类的示例:
var handleEvent = new MyEventTriggeringClass();
handleEvent.Attach(someObject, "SomeEvent", someValueToBePassedArround);
请注意,上面的例子是毫无意义的。我只是试着描述我在寻找什么。这里我的最终目标是能够在触发任意事件时触发对任意方法的调用。我将在一个WPF项目中使用它,我试图使用100%的MVVM,但我偶然发现了一个[看似]经典的断点。
无论如何,代码"工作"到目前为止,它成功地调用了"invokedMethod"当任意事件触发,但"this"似乎是一个空对象(_parameter总是null)。我已经做了一些研究,但根本找不到任何好的例子,其中'this'被正确地传递给一个方法被调用从一个动态方法中像这样。
我发现最接近的例子是THIS ARTICLE,但在这个例子中,' THIS '可以强制为动态方法,因为它是从代码中调用的,而不是任意的事件处理程序。
由于。net中委托的变化方式,您可以在c#中编写代码而不使用codegen:
private void InvokedMethod(object sender, EventArgs e)
{
// whatever
}
private MethodInfo _invokedMethodInfo =
typeof(MyEventTriggeringClass).GetMethod(
"InvokedMethod", BindingFlags.Instance | BindingFlags.NonPublic);
private void hookupDelegate(object source, EventInfo e)
{
Delegate invokedMethodDelegate =
Delegate.CreateDelegate(e.EventHandlerType, this, _invokedMethodInfo);
e.AddEventHandler(source, invokedMethodDelegate);
}
为了解释,假设您有一些遵循标准事件模式的事件,即返回类型为void
,第一个参数为object
,第二个参数为EventArgs
或从EventArgs
派生的某种类型。如果您已经定义了InvokeMethod
,那么您可以编写someObject.theEvent += InvokedMethod
。这是允许的,因为它是安全的:你知道第二个参数是某种类型,可以作为EventArgs
。
上面的代码基本上是相同的,除了在给定事件为EventInfo
时使用反射。只需创建一个引用我们的方法并订阅事件的正确类型的委托。
如果你确定你想用编码方式,可能是因为你也想支持非标准事件,你可以这样做:
当你想要附加到一个事件时,创建一个类,它的方法与事件的委托类型相匹配。该类型还将有一个保存传入参数的字段。(更接近您的设计将是一个字段,保存对MyEventTriggeringClass
的this
实例的引用,但我认为这样更有意义。)该字段在构造函数中设置。
该方法将调用invokedMethod
,传递parameter
作为参数。(这意味着invokedMethod
必须是公共的,可以设置为静态,如果你没有其他理由保持非静态。)
当我们完成类的创建时,创建它的一个实例,创建一个方法的委托,并将其附加到事件。
public class MyEventTriggeringClass
{
private static readonly ConstructorInfo ObjectCtor =
typeof(object).GetConstructor(Type.EmptyTypes);
private static readonly MethodInfo ToBeInvoked =
typeof(MyEventTriggeringClass)
.GetMethod("InvokedMethod",
BindingFlags.Public | BindingFlags.Static);
private readonly ModuleBuilder m_module;
public MyEventTriggeringClass()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("dynamicAssembly"),
AssemblyBuilderAccess.RunAndCollect);
m_module = assembly.DefineDynamicModule("dynamicModule");
}
public void Attach(object source, string @event, object parameter)
{
var e = source.GetType().GetEvent(@event);
if (e == null)
return;
var handlerType = e.EventHandlerType;
var dynamicType = m_module.DefineType("DynamicType" + Guid.NewGuid());
var thisField = dynamicType.DefineField(
"parameter", typeof(object),
FieldAttributes.Private | FieldAttributes.InitOnly);
var ctor = dynamicType.DefineConstructor(
MethodAttributes.Public, CallingConventions.HasThis,
new[] { typeof(object) });
var ctorIL = ctor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, ObjectCtor);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, thisField);
ctorIL.Emit(OpCodes.Ret);
var dynamicMethod = dynamicType.DefineMethod(
"Invoke", MethodAttributes.Public, typeof(void),
GetDelegateParameterTypes(handlerType));
var methodIL = dynamicMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, thisField);
methodIL.Emit(OpCodes.Call, ToBeInvoked);
methodIL.Emit(OpCodes.Ret);
var constructedType = dynamicType.CreateType();
var constructedMethod = constructedType.GetMethod("Invoke");
var instance = Activator.CreateInstance(
constructedType, new[] { parameter });
var sink = Delegate.CreateDelegate(
handlerType, instance, constructedMethod);
e.AddEventHandler(source, sink);
}
private static Type[] GetDelegateParameterTypes(Type handlerType)
{
return handlerType.GetMethod("Invoke")
.GetParameters()
.Select(p => p.ParameterType)
.ToArray();
}
public static void InvokedMethod(object parameter)
{
Console.WriteLine("Value of parameter = " + parameter ?? "(null)");
}
}
这仍然没有考虑到所有可能的事件。这是因为事件的委托可以有返回类型。这意味着为生成的方法提供一个返回类型,并从中返回一些值(可能是default(T)
)。
有(至少)一个可能的优化:不要每次都创建新类型,而是缓存它们。当您尝试附加到具有与前一个事件相同签名的事件时,请使用use其类。
我要回答我自己的问题了。当我意识到真正的问题是什么时,解决方案非常简单:指定事件处理程序的实例/目标。这是通过向MethodInfo.CreateDelegate()添加一个参数来实现的。
如果你感兴趣,这里有一个简单的例子,你可以剪切并粘贴到控制台应用程序中并尝试一下:
class Program
{
static void Main(string[] args)
{
var test = new MyEventTriggeringClass();
var eventSource = new EventSource();
test.Attach(eventSource, "SomeEvent", "Hello World!");
eventSource.RaiseSomeEvent();
Console.ReadLine();
}
}
class MyEventTriggeringClass
{
private object _parameter;
public void Attach(object eventSource, string eventName, object parameter)
{
_parameter = parameter;
var sink = new DynamicMethod(
"sink",
null,
new[] { typeof(object), typeof(object), typeof(EventArgs) },
typeof(Program).Module);
var eventInfo = typeof(EventSource).GetEvent("SomeEvent");
var ilGenerator = sink.GetILGenerator();
var targetMethod = GetType().GetMethod("TargetMethod", BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
ilGenerator.Emit(OpCodes.Ldarg_0); // <-- loads 'this' (when sink is not static)
ilGenerator.Emit(OpCodes.Call, targetMethod);
ilGenerator.Emit(OpCodes.Ret);
// SOLUTION: pass 'this' as the delegate target...
var handler = (EventHandler)sink.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(eventSource, handler);
}
public void TargetMethod()
{
Console.WriteLine("Value of _parameter = " + _parameter);
}
}
class EventSource
{
public event EventHandler SomeEvent;
public void RaiseSomeEvent()
{
if (SomeEvent != null)
SomeEvent(this, new EventArgs());
}
}
所以,谢谢你的评论和帮助。希望有人学到了一些东西。我知道我做过。
欢呼
这是我自己的版本/根据我自己的需要:
/// <summary>
/// Corresponds to
/// control.Click += new EventHandler(method);
/// Only done dynamically, and event arguments are omitted.
/// </summary>
/// <param name="objWithEvent">Where event resides</param>
/// <param name="objWhereToRoute">To which object to perform execution to</param>
/// <param name="methodName">Method name which to call.
/// methodName must not take any parameter in and must not return any parameter. (.net 4.6 is strictly checking this)</param>
private static void ConnectClickEvent( object objWithEvent, object objWhereToRoute, string methodName )
{
EventInfo eventInfo = null;
foreach (var eventName in new String[] { "Click" /*WinForms notation*/, "ItemClick" /*DevExpress notation*/ })
{
eventInfo = objWithEvent.GetType().GetEvent(eventName);
if( eventInfo != null )
break;
}
Type objWhereToRouteObjType = objWhereToRoute.GetType();
var method = eventInfo.EventHandlerType.GetMethod("Invoke");
List<Type> types = method.GetParameters().Select(param => param.ParameterType).ToList();
types.Insert(0, objWhereToRouteObjType);
var methodInfo = objWhereToRouteObjType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
if( methodInfo.ReturnType != typeof(void) )
throw new Exception("Internal error: methodName must not take any parameter in and must not return any parameter");
var dynamicMethod = new DynamicMethod(eventInfo.EventHandlerType.Name, null, types.ToArray(), objWhereToRouteObjType);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator(256);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
ilGenerator.Emit(OpCodes.Ret);
var methodDelegate = dynamicMethod.CreateDelegate(eventInfo.EventHandlerType, objWhereToRoute);
eventInfo.AddEventHandler(objWithEvent, methodDelegate);
} //ConnectClickEvent