您在这里要做的是将一个表达式与另一个表达式组合在一起。用函数来做这件事很容易;用表达式来做这件事需要更多的工作。
你可以做的是将参数的所有实例替换为你的第二个表达式,谓词,用你的第一个函数的主体,选择器。
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
这依赖于以下方法将一个表达式的所有实例替换为另一个:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
最后一个缺失的难题是您将无法使用== 运算符在通用方法中比较您的值,因为您无法确定该类型是否覆盖了== 运算符;您需要手动构建该等式表达式:
public static Expression<Func<T, bool>> EqualsValue<T>(T value)
{
var param = Expression.Parameter(typeof(T));
var body = Expression.Equal(param, Expression.Constant(value));
return Expression.Lambda<Func<T, bool>>(body, param);
}
这允许你写:
return PrimaryKeySelector.Compose(EqualsValue(key));
至于您对性能的担忧,请不要担心。您构建这样一个表达式树所花费的时间将比您使用它们发出的网络请求慢几个数量级。在表达式操作上花费一点额外的时间甚至不会对应用程序的性能产生可衡量的影响;它将远低于网络请求的噪音。