通过反射在C#中触发事件

6
我想写一个可重用函数,通过反射来触发一个事件。
在搜索后,我找到了一个类似的问题:如何通过反射在.NET/C#中触发事件? 这个方法可以工作,但当我注册一个事件处理程序到WinForm控件并尝试调用它时,私有字段“<EventName>”会被删除。
下面是我简化后能够重现问题的代码:
Program.cs:
public static void Main()
{
    Control control = new Control();
    control.Click += new EventHandler(control_Click);

    MethodInfo eventInvoker = ReflectionHelper.GetEventInvoker(control, "Click");
    eventInvoker.Invoke(control, new object[] {null, null});
}

static void control_Click(object sender, EventArgs e)
{
    Console.WriteLine("Clicked !!!!!!!!!!!");
}

这是我的ReflectionHelper类:

public static class ReflectionHelper
{
    /// <summary>
    /// Gets method that will be invoked the event is raised.
    /// </summary>
    /// <param name="obj">Object that contains the event.</param>
    /// <param name="eventName">Event Name.</param>
    /// <returns></returns>
    public static MethodInfo GetEventInvoker(object obj, string eventName)
    {
        // --- Begin parameters checking code -----------------------------
        Debug.Assert(obj != null);
        Debug.Assert(!string.IsNullOrEmpty(eventName));
        // --- End parameters checking code -------------------------------

        // prepare current processing type
        Type currentType = obj.GetType();

        // try to get special event decleration
        while (true)
        {
            FieldInfo fieldInfo = currentType.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetField);

            if (fieldInfo == null)
            {
                if (currentType.BaseType != null)
                {
                    // move deeper
                    currentType = currentType.BaseType;
                    continue;
                }

                Debug.Fail(string.Format("Not found event named {0} in object type {1}", eventName, obj));
                return null;
            }

            // found
            return ((MulticastDelegate)fieldInfo.GetValue(obj)).Method;
        }
    }

附加信息:

  • 在同一类中的事件:正常工作。
  • 在不同类、同一程序集的子类中的事件:正常工作。
  • 我的不同程序集中,调试和发布模式下:正常工作。
  • 在WinForm、DevExpress等中的事件:无法正常工作。

非常感谢您的帮助。


我很好奇你开发这个助手的背景是什么 - 是为了尝试进行UI自动化吗? - Aaron Anodide
1
在您喜爱的C#语言书籍中查找事件的addremove访问器。这些访问器被Winforms广泛使用,只有自动生成的实现才具备您正在寻找的后备字段。顺便提一下,该字段的名称也取决于所使用的编译器。这将对您的计划造成相当大的影响。 - Hans Passant
也许目标应该是创建等效的条件来触发事件,例如对于按钮点击,向窗口的消息泵/队列发送消息。 - Aaron Anodide
@Gabriel:我想编写一个可重用的反射函数库,帮助我在代码中进行一些自动化操作,而不是 UI 相关的操作,例如在 DevExpress 控件中自动实现事件、自动验证并使一些不可能的函数变得可能。 - Thang Tran
2个回答

1
WinForms中的事件通常被覆盖,没有一对一的委托支持。相反,该类(基本上)具有事件->委托映射的字典,并且只有在添加事件时才创建委托。因此,您不能假设访问反射字段后会有一个委托支持事件。
编辑:这也存在同样的问题,但比将其作为字段获取并进行强制转换要好。
  var eventInfo = currentType.GetEvent(eventName); 
  var eventRaiseMethod = eventInfo.GetRaiseMethod()
  eventRaiseMethod.Invoke()

有没有办法获取您提到的WinForm的字典?其他第三方组件是否以与WinForm相同的方式实现? - Thang Tran
1
我尝试了你的代码:var eventInfo = currentType.GetEvent(eventName); var eventRaiseMethod = eventInfo.GetRaiseMethod();但是 eventInfo.GetRaiseMethod() 返回的是 nulleventInfo.GetRaiseMethod(true)eventInfo.GetRaiseMethod(false) 都不能解决问题。 - Thang Tran
嗯,是的,我想它会遇到同样的问题,因为没有可调用的方法。所以你必须深入字典并手动调用事件(顺便知道字典中的键是什么)。 - Mark Sowul
1
原来它是一个属性,而不是字段,因此您需要调用GetPropertyInfo。它恰好被称为“Events”,类型为System.ComponentModel.EventHandlerList。但是通常情况下,如果添加/删除方法被重写,则没有通用的方法来调用事件。 - Mark Sowul
我已经获取了Events属性并可以获取该控件中注册事件的列表,但现在卡在这里,因为我没有一个键来获取我想要的事件处理程序(稍微反射一下给我私有静态对象Control.EventClick)。更糟糕的是,在使用DevExpress进行测试时,Events属性返回null。 - Thang Tran
1
根据MSDN文档(http://msdn.microsoft.com/en-us/library/1a4k4e35.aspx),GetRaiseMethod方法通常对于使用C#事件关键字或Visual Basic事件关键字声明的事件返回null。这是因为C#和Visual Basic编译器默认情况下不生成此类方法。 - poy

0

这是我找到的一些代码。'obj' 是你想要调用方法的对象,而 'methodName' 则是你想要调用的方法:

public void Invoke(Object obj, String methodName) {
    MethodInfo m = obj.GetType().GetMethod(methodName);

    if (m != null) {
        m.Invoke(obj, null);
    }
}

使用示例:

String s = " test string ";
Invoke(s, "Trim");

我还没有在不同的程序集中测试过这个,但是我从中获取的项目已经经过测试并且运行良好。


这对于“Control”事件无效,正如Mark所说,它们存储在字典中而不是单独的委托字段中。 - Ben Voigt

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