Dynamically calling methods corresponding a parameter's type using expression trees in c#

試著忘記壹切 提交于 2019-12-02 04:44:39

First, use a block expression to introduce runnerParameter to the context. Second, make parameter e the base type so you don't have to mess with the delegate type, and then convert it to the derived type with a conversion expression. Third (optional), use a generic Expression.Lambda overload so you get the desired delegate type without casting.

var eventParameter = Expression.Parameter(typeof(Event), "e");
var body = Expression.Call(runnerParameter, method, Expression.Convert(eventParameter, eventType));
var block = Expression.Block(runnerParameter, body);
var lambda = Expression.Lambda<Action<Event>>(block, eventParameter);
var compiled = lambda.Compile();
_handlers.Add(eventType, compiled);

That will work until you go to call the hander and then you'll get NREs because runnerParameter doesn't have a value. Change it to a constant so that your block closes on this.

var runnerParameter = Expression.Constant(this, this.GetType());

One other suggestion: Move your selection/exclusion criteria out of the loop so you're not mixing concerns, and keep facts you've discovered in an anonymous object for use later.

var methods = from m in this.GetType().GetMethods()
              where m.Name == HandleMethodName
              let parameters = m.GetParameters()
              where parameters.Length == 1
              let p = parameters[0]
              let pt = p.ParameterType
              where pt.IsClass
              where !pt.IsAbstract
              where typeof(Event).IsAssignableFrom(pt)
              select new
              {
                  MethodInfo = m,
                  ParameterType = pt
              };

Then when you loop on methods, you're only doing delegate creation.

foreach (var method in methods)
{
    var eventType = method.ParameterType;
    var eventParameter = Expression.Parameter(typeof(Event), "e");
    var body = Expression.Call(runnerParameter, method.MethodInfo, Expression.Convert(eventParameter, eventType));
    var block = Expression.Block(runnerParameter, body);
    var lambda = Expression.Lambda<Action<Event>>(block, eventParameter);
    var compiled = lambda.Compile();
    _handlers.Add(eventType, compiled);
}

EDIT: Upon closer examination, I realized that the block expression is unnecessary. Making runnerParameter a constant expression solves the out-of-scope problem on its own.

You can treat lambda functions like a regular static methods. This mean you should pass additional parameter to it (Aggregate in your case). In other words you need to create lambda, that type looks like Action<AggregateBase, Event>.

Change declaration of your _handlers to

private readonly IDictionary<Type, Action<AggregateBase, Event>> _handlers
  = new Dictionary<Type, Action<AggregateBase, Event>>();

Now you can write AggregateBase constructor like this:

var methods = this.GetType().GetMethods()
    .Where(p => p.Name == handleMethodName
                && p.GetParameters().Length == 1);

var runnerParameter = Expression.Parameter(typeof(AggregateBase), "r");
var commonEventParameter = Expression.Parameter(typeof(Event), "e");

foreach (var method in methods)
{
    var eventType = method.GetParameters().Single().ParameterType;

    var body = Expression.Call(
        Expression.Convert(runnerParameter, GetType()),
        method,
        Expression.Convert(commonEventParameter, eventType)
      );

    var lambda = Expression.Lambda<Action<AggregateBase, Event>>(
      body, runnerParameter, commonEventParameter);

    _handlers.Add(eventType, lambda.Compile());
}

EDIT: Also you need to change invocation in Apply method:

public void Apply(Event @event)
{
    var type = @event.GetType();
    if (_handlers.ContainsKey(type))
        _handlers[type](this, @event);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!