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

后端 未结 2 1129
暖寄归人
暖寄归人 2021-01-24 02:09

I\'m building an event handler which will work similarly to how aggregates behave in event sourced systems.

What I\'m trying to achieve can be done in ways as documented

相关标签:
2条回答
  • 2021-01-24 02:38

    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);
    }
    
    0 讨论(0)
  • 2021-01-24 02:58

    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.

    0 讨论(0)
提交回复
热议问题