C#无法动态生成这个简单的事件处理程序

时间:2011-03-04 12:31:03

标签: c# reflection.emit il

我正在尝试学习一些关于动态生成事件处理程序的知识,而我在尝试重新创建这种简单的情况时遇到了困难:

public delegate void SomethingHappenedEventHandler(object sender, object args);
public event SomethingHappenedEventHandler SomethingHappened;

// This is the event handler that I want to create dynamically
public void DoSomething(object a, object b)
{
    DoSomethingElse(a, b);
}

public void DoSomethingElse(object a, object b)
{
    Console.WriteLine("Yay! " + a + " " + b);
}

我用反射器为DoSomething方法生成IL,它给了我:

.method public hidebysig instance void DoSomething(object a, object b) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldarg.1 
    L_0002: ldarg.2 
    L_0003: call instance void MyNamespace::DoSomethingElse(object, object)
    L_0008: ret 
}

所以,我编写了以下代码来动态生成并执行一个等同于DoSomething(...)的方法:

public void CreateDynamicHandler()
{
    var eventInfo = GetType().GetEvent("SomethingHappened");
    var eventHandlerType = eventInfo.EventHandlerType;

    var dynamicMethod = new DynamicMethod("DynamicMethod", null, new[] { typeof(object), typeof(object) }, GetType());
    var ilgen = dynamicMethod.GetILGenerator();
    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Ldarg_1);
    ilgen.Emit(OpCodes.Ldarg_2);

    MethodInfo doSomethingElse = GetType().GetMethod("DoSomethingElse", new[] { typeof(object), typeof(object) });
    ilgen.Emit(OpCodes.Call, doSomethingElse);
    ilgen.Emit(OpCodes.Ret);

    Delegate emitted = dynamicMethod.CreateDelegate(eventHandlerType);
    emitted.DynamicInvoke("hello", "world");
}

然而,当我运行它时,我得到一个InvalidProgramException:JIT编译器遇到内部限制。

有谁可以指出我出错的地方?

[编辑]正如一些人评论的那样,如果我知道所涉及的所有类型,那么整个IL生成的东西是不必要的。我这样做的原因是,这是在运行时为我不知道所涉及的所有类型的事件动态生成事件处理程序的第一步。基本上我一直在http://msdn.microsoft.com/en-us/library/ms228976.aspx跟踪这个例子,卡住了,然后试图将事情放到一个简单的例子中,我可以开始工作。

2 个答案:

答案 0 :(得分:5)

目前还不清楚为什么要动态创建此方法。我无法想到你不能将lambda应用于事件的任何情况:

public delegate void SomethingHappenedEventHandler(object sender, object args);
public event SomethingHappenedEventHandler SomethingHappened;

public void DoSomethingElse(object a, object b)
{
    Console.WriteLine("Yay! " + a + " " + b);
}

// If the signature exactly matches the delegate, just use the method name
SomethingHappened += DoSomethingElse;

public void DoSomethingDifferent(object a)
{
    Console.WriteLine("Yay! " + a);
}

// Otherwise, just use a lambda expression
SomethingHappened += (a, b) => DoSomethingDifferent(a);

那就是说,你的代码不起作用的原因是因为DynamicMethod只生成静态方法。因此,IL代码无效,因为Ldarg_0Ldarg_1加载了两个参数,但Ldarg_2指的是不存在的参数。如果我按以下方式更改它,它可以正常工作 - 它现在是一个带有三个参数的静态方法,其中第一个参数基本上是this

public void CreateDynamicHandler()
{
    var dynamicMethod = new DynamicMethod("DynamicMethod", null,
        new[] { typeof(MyClass), typeof(object), typeof(object) }, typeof(MyClass));
    var ilgen = dynamicMethod.GetILGenerator();
    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Ldarg_1);
    ilgen.Emit(OpCodes.Ldarg_2);

    MethodInfo doSomethingElse = typeof(MyClass).GetMethod("DoSomethingElse",
        new[] { typeof(object), typeof(object) });
    ilgen.Emit(OpCodes.Call, doSomethingElse);
    ilgen.Emit(OpCodes.Ret);

    Delegate emitted = dynamicMethod.CreateDelegate(
        typeof(Action<MyClass, string, string>));
    emitted.DynamicInvoke(this, "Hello", "World");
}

将“MyClass”替换为您班级的名称。

关于问题的编辑,您不需要通过编写IL代码来生成动态方法,以便在运行时动态调用方法。只需使用Reflection,例如:

public void DoSomething(object a, object b)
{
    var method = GetType().GetMethod("DoSomethingElse", BindingFlags.Instance | BindingFlags.Public);
    method.Invoke(this, new object[] { a, b });
}

或:

// Note “static”
public static void DoSomething(dynamic instance, object a, object b)
{
    // This will call whatever “DoSomethingElse” method exists on the type
    // that “instance” has *at run-time*
    instance.DoSomethingElse(a, b);
}

答案 1 :(得分:0)

这种情况怎么样......

您有一对方法和事件,每个对都是MethodAsync和MethodCompleted。每个MethodCompleted都有一个不同的签名(eventargs的子类型不同,这是第二个参数)。

您希望创建一个包装器来调用给定的MethodAsync,该方法将一个通用事件处理程序挂钩到相应的MethodCompleted。

通常,您可以创建一个方法“void GlobalHandler(object,object)”,然后执行

MethodCompleted += GlobalHandler;  

但是,您无法传递事件对象,因此必须使用反射来获取对事件处理程序的引用,然后执行AddHandler。同样,障碍是AddHandler不喜欢多态(似乎),并抱怨MethodCompleted想要除GlobalHandler以外的东西。

在这种情况下,您似乎需要创建一个MethodCompleted期望的EXACT签名的DynamicMethod,然后从中调用GlobalHandler。我是对的,还是我在这里也遗漏了一些东西。