I am having to deal with maintaining a library which allows users to register a generic handler (Action<T>) against it and later on once it receives an event it goes through each of the registered handlers and passes them the event. To keep the question short, let us skip the reasons why it has been done this way.
Because of this design we have had to call DynamicInvoke when passing each event; This is proving quite slow and hence the need to either turn the delegates into a CompiledExpression or a DynamicMethod using IL generation. I have seen various examples of implementing this for PropertyGetters (Matt Warren's excellent article) but I cannot get it to work with Action<T> where T can be both a ValueType or a ReferenceType.
Here's a current (slow) working example to play with (simplified for brevity):
void Main()
{
var producer = new FancyEventProduder();
var fancy = new FancyHandler(producer);
fancy.Register<Base>(x => Console.WriteLine(x));
producer.Publish(new Child());
}
public sealed class FancyHandler
{
private readonly List<Delegate> _handlers;
public FancyHandler(FancyEventProduder produer)
{
_handlers = new List<Delegate>();
produer.OnMessge += OnMessage;
}
public void Register<T>(Action<T> handler) => _handlers.Add(handler);
private void OnMessage(object sender, object payload)
{
Type payloadType = payload.GetType();
foreach (Delegate handler in _handlers)
{
// this could be cached at the time of registration but has negligable impact
Type delegParamType = handler.Method.GetParameters()[0].ParameterType;
if(delegParamType.IsAssignableFrom(payloadType))
{
handler.DynamicInvoke(payload);
}
}
}
}
public sealed class FancyEventProduder
{
public event EventHandler<object> OnMessge;
public void Publish(object payload) => OnMessge?.Invoke(this, payload);
}
public class Base { }
public sealed class Child : Base { }
Not sure if it is a good idea:
Note that some smaller ideas can be reused even without using expression trees: saving the
typeof(T)instead of calling many timeshandler.Method.GetParameters()[0].ParameterTypefor example.Some test cases:
Full expression tree (the type check is moved INSIDE the expression tree, where the
asis done instead ofIsAssignableFrom)This version supports even
null:A third version, based on the idea of @BorisB, this version eskews the use of
Expressionand uses directly delegates. It should have a shorter warmup time (noExpressiontrees to be compiled). There is still has a minor reflection problem, but fortunately this problem is only present during the adding of new handlers (there is a comment that explains it).