我正在构建一个事件处理程序,其作用类似于事件源系统中聚合的行为。
我要实现的目标可以通过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',但未定义'。
我要实现的目标是:
Handle
的方法以及实现Event
的参数答案 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);
}