将两个表达式组合成一个管道
Combining two expressions into a pipeline
假设我有以下两个表达式:
Expression<Func<T, IEnumerable<TNested>>> collectionSelector;
Expression<Func<IEnumerable<TNested>, TNested>> elementSelector;
有没有办法 "combine" 这些以形成以下内容:(?)
Expression<Func<T, TNested>> selector;
编辑:
性能非常关键,所以如果可能的话,我希望有一个开销很小的最佳解决方案。
非常感谢!
static Expression<Func<A, C>> Foo<A, B, C>(
Expression<Func<B, C>> f,
Expression<Func<A, B>> g)
{
var x = Expression.Parameter(typeof(A));
return Expression.Lambda<Func<A, C>>(
Expression.Invoke(f, Expression.Invoke(g, x)), x);
}
不幸的是,我无法访问计算机(就性能而言,这是一个糟糕的解决方案)。实际上,我认为您可以通过调用 Expression 来优化代码。
另一种方式好像是这样(使用辅助功能):
Func<A, C> Foo<A,B,C>(Func<B, C> f, Func<A, B> g)
{
return (A x) => f(g(x));
}
然后您应该通过调用带有 Foo 函数的表达式来创建管道表达式。像那个伪代码:
var expr1 = get expresstion<B,C>
var expr2 = get Expression<A, B>
var foo = get method info of Foo method
specialize method with generic types A, B, C (via make generic method)
return Expression.Call(foo, expr1, expr2);
另一种解决方案是使用ExpressionVisitor
将右侧表达式中的参数替换为整个左侧表达式,换句话说,将左侧嵌入右侧。
表达式访问者将非常简单,将需要的数据添加到构造函数中,重写一个方法即可。
internal sealed class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression _searched;
private readonly Expression _replaced;
public ParameterReplaceVisitor(ParameterExpression searched, Expression replaced)
{
if (searched == null)
throw new ArgumentNullException(nameof(searched));
if (replaced == null)
throw new ArgumentNullException(nameof(replaced));
_searched = searched;
_replaced = replaced;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == _searched)
return _replaced;
return base.VisitParameter(node);
}
}
它可以很容易地扩展以处理构造函数中的表达式集合,但我保持简短。
现在,您只需要在表达式主体上使用它并构造新的 lambda。
private static Expression<Func<TIn, TOut>> Merge<TIn, TInter, TOut>(Expression<Func<TIn, TInter>> left, Expression<Func<TInter, TOut>> right)
{
var merged = new ParameterReplaceVisitor(right.Parameters[0], left.Body).Visit(right.Body);
var lambda = Expression.Lambda<Func<TIn, TOut>>(merged, left.Parameters[0]);
return lambda;
}
我用这段代码测试了它:
Expression<Func<string, int>> l = s => s.Length + 5;
Expression<Func<int, string>> r = i => i.ToString() + " something";
var merged = Merge(l, r);
var res = merged.Compile()("test");
结果如预期:9 something
。
编辑:
如果您关心性能,为什么要使用表达式而不是普通的 Func
s?然后你可以一个接一个地调用。后面分析表达式树了吗?
Expression<Func<TSourceType, TFinalType>> ChainExpressions<TSourceType, TIntermediaryType, TFinalType>(
Expression<Func<TSourceType, TIntermediaryType>> firstExpression,
Expression<Func<TIntermediaryType, TFinalType>> secondExpression
)
{
var sourceInput = Expression.Parameter(typeof(TSourceType));
var expressionForIntermediaryValue = Expression.Invoke(firstExpression, sourceInput);
var expressionToGetTypedIntermediaryValue = Expression.Convert(expressionForIntermediaryValue, typeof(TIntermediaryType));
var expressionForFinalValue = Expression.Invoke(secondExpression, expressionToGetTypedIntermediaryValue);
var expressionToGetTypedFinalValue = Expression.Convert(expressionForFinalValue, typeof(TFinalType));
var finalOutputExpression = Expression.Lambda(expressionToGetTypedFinalValue, sourceInput);
return (Expression<Func<TSourceType, TFinalType>>)finalOutputExpression;
}
假设我有以下两个表达式:
Expression<Func<T, IEnumerable<TNested>>> collectionSelector;
Expression<Func<IEnumerable<TNested>, TNested>> elementSelector;
有没有办法 "combine" 这些以形成以下内容:(?)
Expression<Func<T, TNested>> selector;
编辑:
性能非常关键,所以如果可能的话,我希望有一个开销很小的最佳解决方案。
非常感谢!
static Expression<Func<A, C>> Foo<A, B, C>(
Expression<Func<B, C>> f,
Expression<Func<A, B>> g)
{
var x = Expression.Parameter(typeof(A));
return Expression.Lambda<Func<A, C>>(
Expression.Invoke(f, Expression.Invoke(g, x)), x);
}
不幸的是,我无法访问计算机(就性能而言,这是一个糟糕的解决方案)。实际上,我认为您可以通过调用 Expression 来优化代码。
另一种方式好像是这样(使用辅助功能):
Func<A, C> Foo<A,B,C>(Func<B, C> f, Func<A, B> g)
{
return (A x) => f(g(x));
}
然后您应该通过调用带有 Foo 函数的表达式来创建管道表达式。像那个伪代码:
var expr1 = get expresstion<B,C>
var expr2 = get Expression<A, B>
var foo = get method info of Foo method
specialize method with generic types A, B, C (via make generic method)
return Expression.Call(foo, expr1, expr2);
另一种解决方案是使用ExpressionVisitor
将右侧表达式中的参数替换为整个左侧表达式,换句话说,将左侧嵌入右侧。
表达式访问者将非常简单,将需要的数据添加到构造函数中,重写一个方法即可。
internal sealed class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression _searched;
private readonly Expression _replaced;
public ParameterReplaceVisitor(ParameterExpression searched, Expression replaced)
{
if (searched == null)
throw new ArgumentNullException(nameof(searched));
if (replaced == null)
throw new ArgumentNullException(nameof(replaced));
_searched = searched;
_replaced = replaced;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == _searched)
return _replaced;
return base.VisitParameter(node);
}
}
它可以很容易地扩展以处理构造函数中的表达式集合,但我保持简短。
现在,您只需要在表达式主体上使用它并构造新的 lambda。
private static Expression<Func<TIn, TOut>> Merge<TIn, TInter, TOut>(Expression<Func<TIn, TInter>> left, Expression<Func<TInter, TOut>> right)
{
var merged = new ParameterReplaceVisitor(right.Parameters[0], left.Body).Visit(right.Body);
var lambda = Expression.Lambda<Func<TIn, TOut>>(merged, left.Parameters[0]);
return lambda;
}
我用这段代码测试了它:
Expression<Func<string, int>> l = s => s.Length + 5;
Expression<Func<int, string>> r = i => i.ToString() + " something";
var merged = Merge(l, r);
var res = merged.Compile()("test");
结果如预期:9 something
。
编辑:
如果您关心性能,为什么要使用表达式而不是普通的 Func
s?然后你可以一个接一个地调用。后面分析表达式树了吗?
Expression<Func<TSourceType, TFinalType>> ChainExpressions<TSourceType, TIntermediaryType, TFinalType>(
Expression<Func<TSourceType, TIntermediaryType>> firstExpression,
Expression<Func<TIntermediaryType, TFinalType>> secondExpression
)
{
var sourceInput = Expression.Parameter(typeof(TSourceType));
var expressionForIntermediaryValue = Expression.Invoke(firstExpression, sourceInput);
var expressionToGetTypedIntermediaryValue = Expression.Convert(expressionForIntermediaryValue, typeof(TIntermediaryType));
var expressionForFinalValue = Expression.Invoke(secondExpression, expressionToGetTypedIntermediaryValue);
var expressionToGetTypedFinalValue = Expression.Convert(expressionForFinalValue, typeof(TFinalType));
var finalOutputExpression = Expression.Lambda(expressionToGetTypedFinalValue, sourceInput);
return (Expression<Func<TSourceType, TFinalType>>)finalOutputExpression;
}