6

I want to write a reusable function to raise an event via reflection.

After searching, I found this similar question: How do I raise an event via reflection in .NET/C#?

It works until I register an event handler to WinForm control and try to invoke it. The private field '<EventName>' simply disappears.

Below is my simplified code which reproduces the problem:

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

Here is my ReflectionHelper class:

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

Additional information:

  • Event in same class: worked.
  • Event in different class, sub-class in same assembly: worked.
  • Event in MY different assembly, debug & release mode: worked.
  • Event in WinForm, DevExpress, ...: did not work

Any help is appreciated.

Community
  • 1
  • 1
Thang Tran
  • 137
  • 3
  • 6
  • i'm curious what is the context of you developing this helper - is it some sort of attempt at ui automation? – Aaron Anodide Feb 25 '11 at 04:29
  • 1
    Look in your favorite C# language book for the *add* and *remove* accessors of an event. Used extensively by Winforms, only auto-generated implementations of these accessor have the backing field you are looking for. The name of which also depends on the compiler used btw. Blows a pretty big hole in your plans. – Hans Passant Feb 25 '11 at 05:55
  • maybe the goal should be to create equivalent conditions that would cause the event to fire, i.e. for a button click, sending a message to the window's message pump/queue – Aaron Anodide Feb 25 '11 at 20:08
  • @Gabriel: I want to write a library reusable reflection functions that helps me to do some automation in my code, not UI, such as auto implemented events in DevExpress control, auto validation and makes some impossible functions to be possible – Thang Tran Feb 26 '11 at 12:23

2 Answers2

1

Events in WinForms are generally overridden and not don't have a one-to-one delegate backing. Instead the class (basically) has a dictionary of the event->delegate mappings and the delegates are only created when the events are added. So you can't assume there's a delegate backing the event once you access the field with reflection.

Edit: this falls prey to the same problem, but is better than getting it as a field and casting it.

  var eventInfo = currentType.GetEvent(eventName); 
  var eventRaiseMethod = eventInfo.GetRaiseMethod()
  eventRaiseMethod.Invoke()
Mark Sowul
  • 10,244
  • 1
  • 45
  • 51
  • Is there anyway to get the WinForm's **dictionary** you mentioned? Are other 3rd party components implemented in the same way as WinForm? – Thang Tran Feb 25 '11 at 03:51
  • 1
    i tried your code: `var eventInfo = currentType.GetEvent(eventName); var eventRaiseMethod = eventInfo.GetRaiseMethod()` but `eventInfo.GetRaiseMethod()` returns **null**, `eventInfo.GetRaiseMethod(true)` or `eventInfo.GetRaiseMethod(false)` does not work either. – Thang Tran Feb 25 '11 at 04:00
  • Hm. Yeah I guess it falls prey to the same problem, which makes sense because there is no method to call. So you'd have to dig in to the dictionary and manually call the even that way (knowing what the key in the dictionary is too, incidentally). – Mark Sowul Feb 25 '11 at 04:11
  • 1
    It turns out to be a property, not a field, so you need to call GetPropertyInfo. It happens to be called "Events" and is of type System.ComponentModel.EventHandlerList. But in general if the add/remove methods are overridden there's no universal way to invoke the event. – Mark Sowul Feb 25 '11 at 04:13
  • I got the Events property and can get the list of **registered events in that control**, it's stuck here because I don't have a key to get the handler of my wanted event (a little bit of reflection gives me private static object Control.EventClick). To make it worse, while testing with DevExpress, the Events property returns null. – Thang Tran Feb 25 '11 at 07:24
  • 1
    @MarkSowul - Per MSDN Docs (http://msdn.microsoft.com/en-us/library/1a4k4e35.aspx) - This method (GetRaiseMethod) usually returns `null` for events declared with the C# event keyword or the Visual Basic Event keyword. This is because the C# and Visual Basic compilers do not generate such a method by default. – poy Feb 14 '13 at 22:06
0

Here's some code I had laying around. 'obj' is the object for which you want to invoke a method, and 'methodName' is the method that you want to invoke:

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

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

Example usage:

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

I haven't tested this across assemblies, but the project I took it from was tested and it worked great.

FreeAsInBeer
  • 12,937
  • 5
  • 50
  • 82
  • This doesn't work for `Control` events which, as Mark said, are stored in a Dictionary and not separate delegate fields. – Ben Voigt Feb 25 '11 at 03:31