从通用事件处理程序重定向到动态方法
我正在尝试编写一个类,用于从任意事件触发对方法的调用,但我陷入困境,因为我根本无法找出从发出的 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 ' 可以强制使用动态方法,因为它是从代码调用的,而不是任意事件处理程序。
任何建议或提示将不胜感激。
I'm trying to write a class that's to be used to trigger a call to a method from an arbitrary event but I'm stuck as I simply cannot figure out a way to reference 'this' from emitted MSIL code.
This example should describe what I'm looking for:
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)"
}
}
Here's an xample how I envision the class being used:
var handleEvent = new MyEventTriggeringClass();
handleEvent.Attach(someObject, "SomeEvent", someValueToBePassedArround);
(Please note that the above example is quite pointless. I just try to describe what I'm looking for. My final goal here is to be able to trigger a call to an arbitrary method whenever an arbitrary event fires. I'll use that in a WPF projekt where I try to use 100% MVVM but I've stumbled upon one of the [seemingly] classic breaking points.)
Anyway, the code "works" so far as it successfully invoked the "invokedMethod" when the arbitrary event fires but 'this' seems to be an empty object (_parameter is always null). I have done some research but simply cannot find any good examples where 'this' is properly passed to a method being called from within a dynamic method like this.
The closest example I've found is THIS ARTICLE but in that example 'this' can be forced to the dynamic method since it's called from the code, not an arbitrary event handler.
Any suggestions or hints would be very appreciated.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
由于 .Net 中委托差异的工作方式,您可以在不使用 codegen 的情况下用 C# 编写代码:
为了解释一下,假设您有一些遵循标准事件模式的事件,即 return类型是
void
,第一个参数是object
,第二个参数是EventArgs
或从EventArgs
派生的某种类型。如果您已按上述方式定义了该内容和InvokeMethod
,则可以编写someObject.theEvent += InvokedMethod
。这是允许的,因为它是安全的:您知道第二个参数是可以充当EventArgs
的某种类型。上面的代码基本相同,除了在将事件作为
EventInfo
给出时使用反射。只需创建一个引用我们的方法的正确类型的委托并订阅该事件即可。Because of the way variance on delegates works in .Net, you can write the code in C# without using codegen:
To explain, let's say you have some event that follows the standard event pattern, that is, return type is
void
, first parameter isobject
and second parameter isEventArgs
or some type derived fromEventArgs
. If you have that andInvokeMethod
defined as above, you can writesomeObject.theEvent += InvokedMethod
. This is allowed because it is safe: you know the second parameter is some type that can act asEventArgs
.And the code above is basically the same, except using reflection when given the event as
EventInfo
. Just create a delegate of the correct type that references our method and subscribe to the event.如果您确定要采用 codegen 方式,可能是因为您也想支持非标准事件,您可以这样做:
每当您想要附加到事件时,创建一个具有以下方法的类:匹配事件的委托类型。该类型还将有一个字段来保存传入的参数。 (更接近您的设计的是一个包含对
MyEventTriggeringClass
的this
实例的引用的字段,但我认为这种方式更有意义。)此字段设置在构造函数。该方法将调用
invokedMethod
,并将parameter
作为参数传递。 (这意味着invokedMethod
必须是公共的,并且可以设为静态,如果您没有其他理由保持非静态。)当我们创建完类后,创建一个实例它,创建该方法的委托并将其附加到事件。
不过,这仍然无法考虑所有可能的事件。这是因为事件的委托可以有返回类型。这意味着为生成的方法提供一个返回类型并从中返回一些值(可能是
default(T)
)。(至少)有一种可能的优化:不要每次都创建新类型,而是缓存它们。当您尝试附加到与前一个事件具有相同签名的事件时,请使用其类。
If you're sure you want to go with the codegen way, possibly because you want to support non-standard events too, you could do it like this:
Whenever you want to attach to an event, create a class that has a method that matches the event's delegate type. The type will also have a field that holds the passed-in parameter. (Closer to your design would be a field that holds a reference to the
this
instance ofMyEventTriggeringClass
, but I think it makes more sense this way.) This field is set in the constructor.The method will call
invokedMethod
, passingparameter
as a parameter. (This meansinvokedMethod
has to be public and can be made static, if you don't have another reason to keep in non-static.)When we're done creating the class, create an instance of it, create a delegate to the method and attach that to the event.
This is still doesn't take care of all possible events, though. That's because the delegate of an event can have a return type. That would mean giving a return type to the generated method and returning some value (probably
default(T)
) from it.There's (at least) one possible optimization: don't create a new type every time, but cache them. When you try to attach to an event with the same signature as a previous one, use use its class.
我将继续在这里回答我自己的问题。一旦我意识到真正的问题是什么,解决方案就非常简单:指定事件处理程序的实例/目标。这是通过向 MethodInfo.CreateDelegate() 添加参数来完成的。
如果您感兴趣,这里有一个简单的示例,您可以将其剪切并粘贴到控制台应用程序中并尝试一下:
所以,感谢您的评论和帮助。希望有人能学到一些东西。我知道我做到了。
干杯
I'm gonna go ahead and answer my own question here. The solution was very simple once I realized what the real problem was: Specifying the event handler's instance/target. This is done by adding an argument to MethodInfo.CreateDelegate().
If you're interested, here's a simple example you can cut'n'paste into a console app and try it out:
So, thanks for your comments and help. Hopefully someone learned something. I know I did.
Cheers
这是我自己的版本/满足我自己的需要:
Here is my own version / for my own needs: