How to Raise an Event via Reflection in .Net/C#

How do I raise an event via reflection in .NET/C#?

Here's a demo using generics (error checks omitted):

using System;
using System.Reflection;
static class Program {
private class Sub {
public event EventHandler<EventArgs> SomethingHappening;
}
internal static void Raise<TEventArgs>(this object source, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs
{
var eventDelegate = (MulticastDelegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source);
if (eventDelegate != null)
{
foreach (var handler in eventDelegate.GetInvocationList())
{
handler.Method.Invoke(handler.Target, new object[] { source, eventArgs });
}
}
}
public static void Main()
{
var p = new Sub();
p.Raise("SomethingHappening", EventArgs.Empty);
p.SomethingHappening += (o, e) => Console.WriteLine("Foo!");
p.Raise("SomethingHappening", EventArgs.Empty);
p.SomethingHappening += (o, e) => Console.WriteLine("Bar!");
p.Raise("SomethingHappening", EventArgs.Empty);
Console.ReadLine();
}
}

Raise an event via reflection in c#

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()

Raising event via reflection using webforms with codebehind

The problem I was having was that iterating through the field collection of Me.GetType() wasn't returning the "TestEvent" field. I did some further digging and realised that that is because the event is declared in the "code behind" class, e.g. myPageName.aspx.vb, however, during runtime, this code was being called from the inheriting "design" class, e.g. myPageName.aspx.

This blog post pointed out that even with BindingFlags.FlattenHierarchy, .GetType.GetField() will not return private static fields from inherited classes: https://web.archive.org/web/20131213074318/http://bobpowell.net/eventsubscribers.aspx

As a result, the solution was to use Me.GetType.BaseType.GetField("TestEvent"), and from there use the technique as described by Tejas Sharma. A VB.NET example of this technique is offered in this answer: How to Attach the Events of an Original Object to a Deep Copied Clone

How would you invoke an event with reflection?

Exactly what you want - taken from this fantastic page....

    public void TriggerEvent(string handler, EventArgs e)
{
MulticastDelegate eventDelegate =
(MulticastDelegate)this.GetType().GetField(handler,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic).GetValue(this);

Delegate[] delegates = eventDelegate.GetInvocationList();

foreach (Delegate dlg in delegates)
{
dlg.Method.Invoke(dlg.Target, new object[] { this, e });
}
}

C# Reflection: How to invoke a EventInfo?

"Invoking" event only makes sense if event is implemented without overriding add\remove:

class Test {
public event Action TestEvent;

void Invoke() {
// fine
TestEvent();
}
}

However, concept of invoking for event with custom add\remove does not make sense:

class Test {
public event Action TestEvent
{
add { }
remove { }
}

void Invoke() {
// does not compile, invoke what?
TestEvent();
}
}

So event invocation is just syntax sugar of calling underlying compiler-generated delegate field, for events with "default" implementation.

Knowing that, you can search for that field and invoke it. This is a private field with the same name as event:

class Program {
static void Main(string[] args) {
var test = new Test();
test.TestEvent += OnTest;
var backingField = typeof(Test).GetField("TestEvent", BindingFlags.Instance | BindingFlags.NonPublic);
var delegateInstance = (Action)backingField.GetValue(test);
delegateInstance();
}

private static void OnTest() {
Console.WriteLine("Event invoked");
}
}

class Test {
public event Action TestEvent;
}

That said, compilers of some .NET languages might generate "Raise" method for autoimplemented event. If that is the case - GetRaiseMethod will return such method. C# compiler doesn't do that though. So if you want to be safe - you might first call GetRaiseEvent and if it returns null - fallback to field approach. Of course you should expect field to be null also (since not all events are invocable as described above - there is not requirement for such field to exist).

AddEventHandler using reflection

Here's a sample showing how to attach an event using reflection:

class Program
{
static void Main(string[] args)
{
var p = new Program();
var eventInfo = p.GetType().GetEvent("TestEvent");
var methodInfo = p.GetType().GetMethod("TestMethod");
Delegate handler =
Delegate.CreateDelegate(eventInfo.EventHandlerType,
p,
methodInfo);
eventInfo.AddEventHandler(p, handler);

p.Test();
}

public event Func<string> TestEvent;

public string TestMethod()
{
return "Hello World";
}

public void Test()
{
if (TestEvent != null)
{
Console.WriteLine(TestEvent());
}
}
}

Subscribing an Action to any event type via reflection

static void AddEventHandler(EventInfo eventInfo, object item,  Action action)
{
var parameters = eventInfo.EventHandlerType
.GetMethod("Invoke")
.GetParameters()
.Select(parameter => Expression.Parameter(parameter.ParameterType))
.ToArray();

var handler = Expression.Lambda(
eventInfo.EventHandlerType,
Expression.Call(Expression.Constant(action), "Invoke", Type.EmptyTypes),
parameters
)
.Compile();

eventInfo.AddEventHandler(item, handler);
}
static void AddEventHandler(EventInfo eventInfo, object item, Action<object, EventArgs> action)
{
var parameters = eventInfo.EventHandlerType
.GetMethod("Invoke")
.GetParameters()
.Select(parameter => Expression.Parameter(parameter.ParameterType))
.ToArray();

var invoke = action.GetType().GetMethod("Invoke");

var handler = Expression.Lambda(
eventInfo.EventHandlerType,
Expression.Call(Expression.Constant(action), invoke, parameters[0], parameters[1]),
parameters
)
.Compile();

eventInfo.AddEventHandler(item, handler);
}

Usage:

  Action action = () => BM_21_Grad.LaunchMissle();

foreach (var eventInfo in form.GetType().GetEvents())
{
AddEventHandler(eventInfo, form, action);
}

Subscribe to an event with Reflection

With a few changes, I was able to execute your sample.

Firstly, the method on Trace must have a different signature to correspond with the EventHandler type:

public class Trace
{
public void WriteTrace(object sender, EventArgs e)
{
Console.WriteLine("Trace !");
}
}

Next, a few changes were made to the SubscribeEvent:

public void SubscribeEvent(Control control)
{
if (typeof(Control).IsAssignableFrom(control.GetType()))
{
Trace test = this;
MethodInfo method = typeof(Trace).GetMethod("WriteTrace");

EventInfo eventInfo = control.GetType().GetEvent("Load");

// Create the delegate on the test class because that's where the
// method is. This corresponds with `new EventHandler(test.WriteTrace)`.
Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, test, method);
// Assign the eventhandler. This corresponds with `control.Load += ...`.
eventInfo.AddEventHandler(control, handler);
}
}

I hope this helps.



Related Topics



Leave a reply



Submit