Crispried Crispried - 2 months ago 35
C# Question

Add and remove event handler via reflection c#

Good day!
My purpose is to implement class which will allow us subscribe and unsubscribe objects to(from) events. Here is the code of my class.

public static class EventSubscriber
{
public static void AddEventHandler(EventInfo eventInfo, object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.AddEventHandler(item, handler);
}

public static void RemoveEventHandler(EventInfo eventInfo,
object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.RemoveEventHandler(item, handler);
}

private static ParameterExpression[] GetParameters(EventInfo eventInfo)
{
return eventInfo.EventHandlerType
.GetMethod("Invoke")
.GetParameters()
.Select(parameter => Expression.Parameter(parameter.ParameterType))
.ToArray();
}

private static Delegate GetHandler(EventInfo eventInfo,
Action action, ParameterExpression[] parameters)
{
return Expression.Lambda(
eventInfo.EventHandlerType,
Expression.Call(Expression.Constant(action),
"Invoke", Type.EmptyTypes), parameters)
.Compile();
}
}


As you can see here are 2 public methods which actually subscribe and unsubscribe objects to(from) event. And here is the sample how I test it

class Program
{
static void Main()
{
Test test = new Test();
test.SubscribeTimer();
while (true)
{
if(test.a == 10)
{
break;
}
}
test.UnsubscribeTimer();
while (true)
{

}
}
}

class Test
{
System.Timers.Timer timer;
public int a = 0;

public Test()
{
timer = new System.Timers.Timer(1000);
timer.Start();
}

public void SubscribeTimer()
{
var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed");
EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerNotElapsed);
}

public void UnsubscribeTimer()
{
var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed");
EventSubscriber.AddEventHandler(eventInfo, timer, TimerNotElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerElapsed);
}

public void TimerElapsed()
{
Console.WriteLine("timer elapsed");
a++;
}

public void TimerNotElapsed()
{
Console.WriteLine("timer not elapsed");
a++;
}
}


The expected behaviour of sample is that on the begining we will see the message "timer elapsed" every second, after 10-th second we should see only "timer not elapsed" and we do, but we still see "timer elapsed" too. This means that AddEventHandler method works, but RemoveEventHandler method doesn't.

I would be very happy if you will help me. Thanks in advance.

Answer

I think it's because you are creating a new handler each time: (which doesn't match the previous handler, so can't be removed from the invocation list)

public static void RemoveEventHandler(EventInfo eventInfo,
                    object item, Action action)
{
    var parameters = GetParameters(eventInfo);
    var handler = GetHandler(eventInfo, action, parameters); // <--
    eventInfo.RemoveEventHandler(item, handler);
}

Why are you wrapping the Action? To lose the what parameters? It is not possible to add/remove the eventInfo.RemoveEventHandler(item, action); because of the parameters. If you want to remove a newly generated handler, you should return that handler when you want to remove it.

public static Delegate AddEventHandler(EventInfo eventInfo, object item, Action action)
{
    var parameters = GetParameters(eventInfo);
    var handler = GetHandler(eventInfo, action, parameters);
    eventInfo.AddEventHandler(item, handler);
    return handler;
}

public static void RemoveEventHandler(EventInfo eventInfo,
                    object item, Delegate handler)
{
    eventInfo.RemoveEventHandler(item, handler);
}

var handler = EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed);

EventSubscriber.RemoveEventHandler(eventInfo, timer, handler);