问题
Let us say I have the following two expressions:
Expression<Func<T, IEnumerable<TNested>>> collectionSelector;
Expression<Func<IEnumerable<TNested>, TNested>> elementSelector;
Is there a way to "combine" these in order to form the below: (?)
Expression<Func<T, TNested>> selector;
EDIT:
Performance is very critical, so I would appreciate an optimal solution with very little overhead, if possible.
Many Thanks!
回答1:
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);
}
Unfortunately, I cannot access to computer ( it is bad solution in performance terms). Actually, I think that you can optimize code via call Expression.
Another way seems like that(usage auxiliary function):
Func<A, C> Foo<A,B,C>(Func<B, C> f, Func<A, B> g)
{
return (A x) => f(g(x));
}
Then you should create pipeline Expression via call Expression with Foo function. Like that pseudo code:
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);
回答2:
Another solution is to use ExpressionVisitor
to replace the parameter in right expression with the whole left expression, in other words, embed the left one in the right one.
Expression visitor will be quite simple, add needed data to constructor, override one method and that's all.
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);
}
}
It can be quite easily extended to handle collections of expressions in the constructor, but I kept it short.
Now, you just need to use it on the expression bodies and construct new 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;
}
I tested it on this code:
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");
and the result is as expected: 9 something
.
EDIT:
If performance is your concern, why are you using expressions instead of plain Func
s? Then you could just invoke one after another. Are the expression trees later analyzed?
回答3:
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;
}
来源:https://stackoverflow.com/questions/40696361/combining-two-expressions-into-a-pipeline