在 C# 中为所有事件和委托创建一个包罗万象的处理程序
我想创建一个可用于处理任何事件或委托的处理程序。具体来说,我希望能够编写如下代码:
class Invoker
{
public object Invoke(object[] arg)
{
// generic handling code
}
}
static void Main()
{
var p = new Person();
p.AddHandler("Event1", new Invoker().Invoke);
}
AddHandler
是 object
的扩展方法,它接收事件名称和类型 Func
Event1
的签名并不重要,因为 AddHandler
应该适用于所有类型的事件(和委托)。
我怀疑这可能涉及一些 CIL 生成来构建与指定事件类型匹配的动态委托(例如 Event1
)并将调用转发给指定委托(例如 new Invoker().Invoke )。我能够构建这样的动态委托,但是它只能转发到静态方法,而不是实例方法,因为我找不到将要调用的方法的绑定实例推入 CLR 堆栈的方法(即示例中的
Invoker
实例)。请参阅下面提供的代码以清楚地看到此问题(请参阅标有 ISSUE 的行)。
如果有人能指出一种改进动态生成代码以捕获绑定对象或更好的方法,建议一个不需要 CIL 的更简单的解决方案,那么我们将非常感激。
public static void AddHandler(this object target, string fieldName,
Func<object[], object> func)
{
var eventInfo = target.GetType().GetEvent(fieldName);
if (eventInfo != null)
{
Type delegateType = eventInfo.EventHandlerType;
var dynamicHandler = BuildDynamicHandler(target.GetType(), delegateType, func);
eventInfo.GetAddMethod().Invoke(target, new Object[] { dynamicHandler });
}
}
public static Delegate BuildDynamicHandler(this Type delegateOwnerType, Type delegateType,
Func<object[], object> func)
{
MethodInfo invokeMethod = delegateType.GetMethod("Invoke");
Type returnType = invokeMethod.ReturnType;
bool hasReturnType = returnType != Constants.VoidType;
var paramTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();
var dynamicMethod = new DynamicMethod("add_handler",
hasReturnType ? returnType : null, paramTypes, delegateOwnerType);
var il = new EmitHelper(dynamicMethod.GetILGenerator());
if (paramTypes.Length == 0)
{
il.ldnull.end();
}
else
{
il.DeclareLocal(typeof(object[]));
il.ldc_i4(paramTypes.Length);
il.newarr(typeof(object));
il.stloc_0.end();
for (int i = 0; i < paramTypes.Length; i++)
{
il.ldloc_0
.ldc_i4(i)
.ldarg(i)
.boxIfValueType(paramTypes[i])
.stelem_ref.end();
}
il.ldloc_0.end();
}
/////// ****************** ISSUE: work for static method only
il.call(func.Method);
if (hasReturnType)
{
il.unbox_any(returnType).ret();
}
else
{
il.pop.ret();
}
return dynamicMethod.CreateDelegate(delegateType);
}
I want to create a handler that can be used to handle any event or delegate. Specifically, I want to be able to write code like below:
class Invoker
{
public object Invoke(object[] arg)
{
// generic handling code
}
}
static void Main()
{
var p = new Person();
p.AddHandler("Event1", new Invoker().Invoke);
}
AddHandler
is an extension method for object
which receive an event name and a delegate of type Func<object[], object>
. It should be able to do whatever magic to bind the event (e.g. Event1
in this case) to the provided delegate so that the delegate is invoked whenever the event is fired.
The signature of Event1
shouldn't matter because AddHandler
should work with all types of events (and delegates).
I suspect this might involve some CIL generation to build a dynamic delegate matching the type of the specified event (e.g. Event1
) and forwarding the call to the specified delegate (e.g. new Invoker().Invoke
). I was able to build such a dynamic delegate, however it could only forward to static methods, not instance methods because I couldn't find a way to push the bound instance of the to-be-invoked method into the CLR stack (i.e. the Invoker
instance in the example). See the code provided below to see this issue clearly (see the line marked with ISSUE).
If anyone could point out a way to improve the dynamic generation code to capture bound object or better yet, suggest a simpler solution which doesn't need CIL then it is much appreciated.
public static void AddHandler(this object target, string fieldName,
Func<object[], object> func)
{
var eventInfo = target.GetType().GetEvent(fieldName);
if (eventInfo != null)
{
Type delegateType = eventInfo.EventHandlerType;
var dynamicHandler = BuildDynamicHandler(target.GetType(), delegateType, func);
eventInfo.GetAddMethod().Invoke(target, new Object[] { dynamicHandler });
}
}
public static Delegate BuildDynamicHandler(this Type delegateOwnerType, Type delegateType,
Func<object[], object> func)
{
MethodInfo invokeMethod = delegateType.GetMethod("Invoke");
Type returnType = invokeMethod.ReturnType;
bool hasReturnType = returnType != Constants.VoidType;
var paramTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();
var dynamicMethod = new DynamicMethod("add_handler",
hasReturnType ? returnType : null, paramTypes, delegateOwnerType);
var il = new EmitHelper(dynamicMethod.GetILGenerator());
if (paramTypes.Length == 0)
{
il.ldnull.end();
}
else
{
il.DeclareLocal(typeof(object[]));
il.ldc_i4(paramTypes.Length);
il.newarr(typeof(object));
il.stloc_0.end();
for (int i = 0; i < paramTypes.Length; i++)
{
il.ldloc_0
.ldc_i4(i)
.ldarg(i)
.boxIfValueType(paramTypes[i])
.stelem_ref.end();
}
il.ldloc_0.end();
}
/////// ****************** ISSUE: work for static method only
il.call(func.Method);
if (hasReturnType)
{
il.unbox_any(returnType).ret();
}
else
{
il.pop.ret();
}
return dynamicMethod.CreateDelegate(delegateType);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是使用表达式树的实现:
Here's an implementation using expression trees:
您是否看过使用表达式树(http://msdn.microsoft.com/en -us/library/bb397951.aspx)?它们使生成 IL 变得更加容易。
Have you looked at using expression trees (http://msdn.microsoft.com/en-us/library/bb397951.aspx)? They make it much easier to generate IL.
我想出了一个解决方案。我在博客上写了完整的代码这里,以防有人对纯 CIL 生成方法感兴趣(这不像像 kvb 的方法那么优雅)。
I worked out a solution. I blogged about it with the full code here, in case anyone interested in the pure CIL generation approach (which is not as elegant as kvb's approach).