C#无法动态生成此简单事件处理程序。

3

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

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的示例进行操作,卡住了,然后试图将事情解开成一个简单的示例,以便我可以使其工作。

你可以使用表达式来编译函数。这通常比使用Reflection.Emit更容易。 - CodesInChaos
为什么要将“emitted”变量设置为Delegate?将其设置为与eventHandlerType相同的类型并进行强制转换。然后,您可以像正常调用一样调用它,而不是使用DynamicInvoke()。如果这不是您的问题,请告诉我哪一行抛出了异常?这些信息会很有帮助。 - jonathanpeppers
即使您进行了编辑,仍然很不可能需要通过在运行时生成IL来动态生成方法。 您仍然可以使用反射在运行时使用类型和方法,并使用MethodBase.Invoke()调用它们,或者您可以使用dynamic类型。 - Timwi
@Timwi,你的更新示例真的帮助我找到了正确的方法。我提到的Microsoft示例以及我正在尝试处理的一些事件不符合标准事件签名的事实,意味着我分心了,认为我需要动态创建具有正确签名的处理程序。然而,事实证明,这些事件可以由具有1、2或3个参数的处理程序处理,因此我只需要硬编码3个处理程序,它们采用正确数量的参数(类型为对象),然后使用反射将它们连接起来。谢谢! - Akash
实际上,您可以只使用一个带有类型为 object[] 的参数的单个处理程序。 - Timwi
2个回答

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代码来生成动态方法以便在运行时动态调用方法。只需使用反射即可,例如:

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);
}

谢谢,我之前没有意识到DynamicMethod只生成静态方法(虽然事后看起来很合理)。这也解释了为什么反射器会显示额外的参数,因为我示例代码中的方法不是静态的。 - Akash
请看原问题中的编辑,了解我为什么要这样做 - 你说得对,在我发布的示例上下文中没有意义。 - Akash
1
@Akash:我编辑了答案来解决这个问题。请注意 :) - Timwi
感谢您的更新 - 很有帮助。我现在不需要它,但我已经忘记了动态选项 - 这是要记住的! - Akash

0
这种情况怎么样?
您有一些方法和事件的配对,每个配对都是MethodAsync和MethodCompleted。每个MethodCompleted具有不同的签名(在eventargs的子类型上不同,这是第二个参数)。
您想为调用给定的MethodAsync创建一个包装器,将一个通用的事件处理程序连接到相应的MethodCompleted。
通常,您可以创建一个方法“void GlobalHandler(object,object)”,然后进行
MethodCompleted += GlobalHandler;  

然而,您无法传递事件对象,因此必须使用反射来获取对事件处理程序的引用,然后执行AddHandler。再次遇到的问题是AddHandler不喜欢多态性(似乎是这样),并且抱怨MethodCompleted想要的不是GlobalHandler。

在这种情况下,似乎您需要创建一个与MethodCompleted期望的完全签名相同的DynamicMethod,然后从其中调用GlobalHandler。我是对的,还是我也错过了什么。


你能将GlobalHandler转换为所需的委托类型吗?http://code.logos.com/blog/2008/07/casting_delegates.html提供了一些必要的转换代码。 - Akash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接