使用c#

时间:2018-10-02 15:38:02

标签: c# linq-expressions

我正在构建一个事件处理程序,其作用类似于事件源系统中聚合的行为。

我要实现的目标可以通过here中记载的方式来完成。我研究过的其他参考是Marten源代码和Greg Young的m-r。我想通过表达式树实现相同的目的。

本质上,我希望聚合实现动态执行传递给它的事件,前提是它具有Handle方法,该方法接受该事件作为参数。

首先我有我的活动

abstract class Event { }
class Event1 : Event { }
class Event2 : Event { }

我有从AggregateBase类继承的聚合实现。

class Aggregate : AggregateBase
{
    public int Counter { get; set; } = 10;
    public void Handle(Event1 @event)
    {
        Counter++;
        Console.WriteLine(Counter);
    }

    public void Handle(Event2 @event)
    {
        Counter = 100;
        Console.WriteLine(Counter);
    }
}

最后,AggregateBase在成员字典中执行处理程序的反射和注册。

abstract class AggregateBase
{
    // We're only interested in methods named Handle
    const string HandleMethodName = "Handle";
    private readonly IDictionary<Type, Action<Event>> _handlers = new Dictionary<Type, Action<Event>>();

    public AggregateBase()
    {
        var methods = this.GetType().GetMethods()
            .Where(p => p.Name == HandleMethodName
                && p.GetParameters().Length == 1);

        var runnerParameter = Expression.Parameter(this.GetType(), "r");

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

            // if parameter is not assignable from one event, then skip
            if (!eventType.IsClass || eventType.IsAbstract || !typeof(Event).IsAssignableFrom(eventType)) continue;

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

    public void Apply(Event @event)
    {
        var type = @event.GetType();
        if(_handlers.ContainsKey(type))
        {
            _handlers[type](@event);
        }
    }
}

使用上面的代码,出现错误

  从范围''引用的类型为'ConsoleApp_TestTypeBuilder.Aggregate'的变量'r',但未定义'。

我要实现的目标是:

  1. 获取封闭类中名为Handle的方法以及实现Event的参数
  2. 将事件参数的类型和方法调用作为Action委托存储在字典中
  3. 将事件应用于聚合时,执行与事件类型相对应的Action委托。否则,对事件不执行任何操作。

2 个答案:

答案 0 :(得分:2)

首先,使用块表达式将runnerParameter引入上下文。其次,将参数e设置为基本类型,这样就不必弄乱委托类型,然后使用转换表达式将其转换为派生类型。第三(可选),使用泛型Expression.Lambda重载,这样就可以得到所需的委托类型而无需强制转换。

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

这将一直有效,直到您去调用处理程序,然后您将获得NRE,因为runnerParameter没有值。将其更改为常数,以使您的代码块在this上结束。

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

另一个建议:将选择/排除条件移出循环,以免混淆问题,并将发现的事实保留在匿名对象中,以备后用。

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
              };

然后,当您在methods上循环时,您仅在进行委托创建。

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);
}

编辑:在仔细检查后,我意识到不需要块表达式。将runnerParameter设为常量表达式可以自行解决范围外的问题。

答案 1 :(得分:1)

您可以将lambda函数视为常规的静态方法。这意味着您应该向其传递其他参数(在您的情况下为Aggregate)。换句话说,您需要创建lambda,该类型类似于Action<AggregateBase, Event>

_handlers的声明更改为

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

现在您可以像这样编写AggregateBase构造函数:

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

编辑:此外,您还需要使用Apply方法更改调用:

public void Apply(Event @event)
{
    var type = @event.GetType();
    if (_handlers.ContainsKey(type))
        _handlers[type](this, @event);
}