在c#中组合两个lambdaexpression式

给定一个像这样的类结构:

public class GrandParent { public Parent Parent { get; set;} } public class Parent { public Child Child { get; set;} } public class Child { public string Name { get; set;} } 

和下面的方法签名:

 Expression<Func<TOuter, TInner>> Combine (Expression<Func<TOuter, TMiddle>>> first, Expression<Func<TMiddle, TInner>> second); 

我怎样才能实现上述方法,以便我可以这样调用它:

 Expression<Func<GrandParent, Parent>>> myFirst = gp => gp.Parent; Expression<Func<Parent, string>> mySecond = p => p.Child.Name; Expression<Func<GrandParent, string>> output = Combine(myFirst, mySecond); 

这样输出结束为:

 gp => gp.Parent.Child.Name 

这可能吗?

每个Func的内容只会是一个MemberAccess 。 我宁愿不output是一个嵌套的函数调用。

谢谢

好; 相当长的片段,但是这里是expression式重写器的起始部分 ; 它不处理一些情况(我稍后会修复它),但它适用于给出的例子和许多其他的例子:

 using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; public class GrandParent { public Parent Parent { get; set; } } public class Parent { public Child Child { get; set; } public string Method(string s) { return s + "abc"; } } public class Child { public string Name { get; set; } } public static class ExpressionUtils { public static Expression<Func<T1, T3>> Combine<T1, T2, T3>( this Expression<Func<T1, T2>> outer, Expression<Func<T2, T3>> inner, bool inline) { var invoke = Expression.Invoke(inner, outer.Body); Expression body = inline ? new ExpressionRewriter().AutoInline(invoke) : invoke; return Expression.Lambda<Func<T1, T3>>(body, outer.Parameters); } } public class ExpressionRewriter { internal Expression AutoInline(InvocationExpression expression) { isLocked = true; if(expression == null) throw new ArgumentNullException("expression"); LambdaExpression lambda = (LambdaExpression)expression.Expression; ExpressionRewriter childScope = new ExpressionRewriter(this); var lambdaParams = lambda.Parameters; var invokeArgs = expression.Arguments; if (lambdaParams.Count != invokeArgs.Count) throw new InvalidOperationException("Lambda/invoke mismatch"); for(int i = 0 ; i < lambdaParams.Count; i++) { childScope.Subst(lambdaParams[i], invokeArgs[i]); } return childScope.Apply(lambda.Body); } public ExpressionRewriter() { subst = new Dictionary<Expression, Expression>(); } private ExpressionRewriter(ExpressionRewriter parent) { if (parent == null) throw new ArgumentNullException("parent"); subst = new Dictionary<Expression, Expression>(parent.subst); inline = parent.inline; } private bool isLocked, inline; private readonly Dictionary<Expression, Expression> subst; private void CheckLocked() { if(isLocked) throw new InvalidOperationException( "You cannot alter the rewriter after Apply has been called"); } public ExpressionRewriter Subst(Expression from, Expression to) { CheckLocked(); subst.Add(from, to); return this; } public ExpressionRewriter Inline() { CheckLocked(); inline = true; return this; } public Expression Apply(Expression expression) { isLocked = true; return Walk(expression) ?? expression; } private static IEnumerable<Expression> CoalesceTerms( IEnumerable<Expression> sourceWithNulls, IEnumerable<Expression> replacements) { if(sourceWithNulls != null && replacements != null) { using(var left = sourceWithNulls.GetEnumerator()) using (var right = replacements.GetEnumerator()) { while (left.MoveNext() && right.MoveNext()) { yield return left.Current ?? right.Current; } } } } private Expression[] Walk(IEnumerable<Expression> expressions) { if(expressions == null) return null; return expressions.Select(expr => Walk(expr)).ToArray(); } private static bool HasValue(Expression[] expressions) { return expressions != null && expressions.Any(expr => expr != null); } // returns null if no need to rewrite that branch, otherwise // returns a re-written branch private Expression Walk(Expression expression) { if (expression == null) return null; Expression tmp; if (subst.TryGetValue(expression, out tmp)) return tmp; switch(expression.NodeType) { case ExpressionType.Constant: case ExpressionType.Parameter: { return expression; // never a need to rewrite if not already matched } case ExpressionType.MemberAccess: { MemberExpression me = (MemberExpression)expression; Expression target = Walk(me.Expression); return target == null ? null : Expression.MakeMemberAccess(target, me.Member); } case ExpressionType.Add: case ExpressionType.Divide: case ExpressionType.Multiply: case ExpressionType.Subtract: case ExpressionType.AddChecked: case ExpressionType.MultiplyChecked: case ExpressionType.SubtractChecked: case ExpressionType.And: case ExpressionType.Or: case ExpressionType.ExclusiveOr: case ExpressionType.Equal: case ExpressionType.NotEqual: case ExpressionType.AndAlso: case ExpressionType.OrElse: case ExpressionType.Power: case ExpressionType.Modulo: case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: case ExpressionType.LeftShift: case ExpressionType.RightShift: case ExpressionType.Coalesce: case ExpressionType.ArrayIndex: { BinaryExpression binExp = (BinaryExpression)expression; Expression left = Walk(binExp.Left), right = Walk(binExp.Right); return (left == null && right == null) ? null : Expression.MakeBinary( binExp.NodeType, left ?? binExp.Left, right ?? binExp.Right, binExp.IsLiftedToNull, binExp.Method, binExp.Conversion); } case ExpressionType.Not: case ExpressionType.UnaryPlus: case ExpressionType.Negate: case ExpressionType.NegateChecked: case ExpressionType.Convert: case ExpressionType.ConvertChecked: case ExpressionType.TypeAs: case ExpressionType.ArrayLength: { UnaryExpression unExp = (UnaryExpression)expression; Expression operand = Walk(unExp.Operand); return operand == null ? null : Expression.MakeUnary(unExp.NodeType, operand, unExp.Type, unExp.Method); } case ExpressionType.Conditional: { ConditionalExpression ce = (ConditionalExpression)expression; Expression test = Walk(ce.Test), ifTrue = Walk(ce.IfTrue), ifFalse = Walk(ce.IfFalse); if (test == null && ifTrue == null && ifFalse == null) return null; return Expression.Condition(test ?? ce.Test, ifTrue ?? ce.IfTrue, ifFalse ?? ce.IfFalse); } case ExpressionType.Call: { MethodCallExpression mce = (MethodCallExpression)expression; Expression instance = Walk(mce.Object); Expression[] args = Walk(mce.Arguments); if (instance == null && !HasValue(args)) return null; return Expression.Call(instance, mce.Method, CoalesceTerms(args, mce.Arguments)); } case ExpressionType.TypeIs: { TypeBinaryExpression tbe = (TypeBinaryExpression)expression; tmp = Walk(tbe.Expression); return tmp == null ? null : Expression.TypeIs(tmp, tbe.TypeOperand); } case ExpressionType.New: { NewExpression ne = (NewExpression)expression; Expression[] args = Walk(ne.Arguments); if (HasValue(args)) return null; return ne.Members == null ? Expression.New(ne.Constructor, CoalesceTerms(args, ne.Arguments)) : Expression.New(ne.Constructor, CoalesceTerms(args, ne.Arguments), ne.Members); } case ExpressionType.ListInit: { ListInitExpression lie = (ListInitExpression)expression; NewExpression ctor = (NewExpression)Walk(lie.NewExpression); var inits = lie.Initializers.Select(init => new { Original = init, NewArgs = Walk(init.Arguments) }).ToArray(); if (ctor == null && !inits.Any(init => HasValue(init.NewArgs))) return null; ElementInit[] initArr = inits.Select(init => Expression.ElementInit( init.Original.AddMethod, CoalesceTerms(init.NewArgs, init.Original.Arguments))).ToArray(); return Expression.ListInit(ctor ?? lie.NewExpression, initArr); } case ExpressionType.NewArrayBounds: case ExpressionType.NewArrayInit: /* not quite right... leave as not-implemented for now { NewArrayExpression nae = (NewArrayExpression)expression; Expression[] expr = Walk(nae.Expressions); if (!HasValue(expr)) return null; return expression.NodeType == ExpressionType.NewArrayBounds ? Expression.NewArrayBounds(nae.Type, CoalesceTerms(expr, nae.Expressions)) : Expression.NewArrayInit(nae.Type, CoalesceTerms(expr, nae.Expressions)); }*/ case ExpressionType.Invoke: case ExpressionType.Lambda: case ExpressionType.MemberInit: case ExpressionType.Quote: throw new NotImplementedException("Not implemented: " + expression.NodeType); default: throw new NotSupportedException("Not supported: " + expression.NodeType); } } } static class Program { static void Main() { Expression<Func<GrandParent, Parent>> myFirst = gp => gp.Parent; Expression<Func<Parent, string>> mySecond = p => p.Child.Name; Expression<Func<GrandParent, string>> outputWithInline = myFirst.Combine(mySecond, false); Expression<Func<GrandParent, string>> outputWithoutInline = myFirst.Combine(mySecond, true); Expression<Func<GrandParent, string>> call = ExpressionUtils.Combine<GrandParent, Parent, string>( gp => gp.Parent, p => p.Method(p.Child.Name), true); unchecked { Expression<Func<double, double>> mathUnchecked = ExpressionUtils.Combine<double, double, double>(x => (x * x) + x, x => x - (x / x), true); } checked { Expression<Func<double, double>> mathChecked = ExpressionUtils.Combine<double, double, double>(x => x - (x * x) , x => (x / x) + x, true); } Expression<Func<int,int>> bitwise = ExpressionUtils.Combine<int, int, int>(x => (x & 0x01) | 0x03, x => x ^ 0xFF, true); Expression<Func<int, bool>> logical = ExpressionUtils.Combine<int, bool, bool>(x => x == 123, x => x != false, true); Expression<Func<int[][], int>> arrayAccess = ExpressionUtils.Combine<int[][], int[], int>(x => x[0], x => x[0], true); Expression<Func<string, bool>> isTest = ExpressionUtils.Combine<string,object,bool>(s=>s, s=> s is Regex, true); Expression<Func<List<int>>> f = () => new List<int>(new int[] { 1, 1, 1 }.Length); Expression<Func<string, Regex>> asTest = ExpressionUtils.Combine<string, object, Regex>(s => s, s => s as Regex, true); var initTest = ExpressionUtils.Combine<int, int[], List<int>>(i => new[] {i,i,i}, arr => new List<int>(arr.Length), true); var anonAndListTest = ExpressionUtils.Combine<int, int, List<int>>( i => new { age = i }.age, i => new List<int> {i, i}, true); /* var arrBoundsInit = ExpressionUtils.Combine<int, int[], int[]>( i => new int[i], arr => new int[arr[0]] , true); var arrInit = ExpressionUtils.Combine<int, int, int[]>( i => i, i => new int[1] { i }, true);*/ } } 

我假设你的目标是获得你会得到的expression式树,如果你真的编译了“合并”的lambdaexpression式。 构造一个新的expression式树可以简单地调用给定的expression式树,但是我认为这不是你想要的。

  • 先提取身体,将其投射到MemberExpression。 把它叫做第一个体。
  • 提取第二个身体,称这第二个身体
  • 提取第一个参数。 调用这个第一个参数。
  • 提取第二个参数。 调用这个第二个参数。
  • 现在,困难的部分。 通过secondBody来查找secondParam的单一用法来编写一个访客模式的实现。 (如果知道它只是成员访问expression式,这样会容易得多,但是通常可以解决这个问题。)当find它时,构造一个与其父types相同的新expression式,用firstBodyreplace参数。 继续在返回的路上重build转换的树; 记住,所有你需要重build的是包含参数引用的树的“脊骨”。
  • 访客传递的结果将是一个重写的secondBody,不会出现secondParam,只会出现涉及firstParam的expression式。
  • 构造一个新的lambdaexpression式与身体作为其正文,firstParam作为其参数。
  • 你完成了!

马特·沃伦的博客可能是一件好事,你可以阅读。 他devise并实现了所有这些东西,并且写了很多关于如何有效地重写expression式树的方法。 (我只是做了编译器的事情结束。)

更新:

正如这个相关的答案指出的那样 ,在.NET 4中,现在有一个expression式重写器的基类,使得这种事情变得更容易。

我不知道你的意思是不是一个嵌套的函数调用,但是这将做的伎俩 – 一个例子:

 using System; using System.IO; using System.Linq.Expressions; class Test { static Expression<Func<TOuter, TInner>> Combine<TOuter, TMiddle, TInner> (Expression<Func<TOuter, TMiddle>> first, Expression<Func<TMiddle, TInner>> second) { var parameter = Expression.Parameter(typeof(TOuter), "x"); var firstInvoke = Expression.Invoke(first, new[] { parameter }); var secondInvoke = Expression.Invoke(second, new[] { firstInvoke} ); return Expression.Lambda<Func<TOuter, TInner>>(secondInvoke, parameter); } static void Main() { Expression<Func<int, string>> first = x => (x + 1).ToString(); Expression<Func<string, StringReader>> second = y => new StringReader(y); Expression<Func<int, StringReader>> output = Combine(first, second); Func<int, StringReader> compiled = output.Compile(); var reader = compiled(10); Console.WriteLine(reader.ReadToEnd()); } } 

我不知道生成的代码如何与单个lambdaexpression式进行比较,但我怀疑它不会太糟糕。

有关完整的解决scheme, 请看LINQKit :

 Expression<Func<GrandParent, string>> output = gp => mySecond.Invoke(myFirst.Invoke(gp)); output = output.Expand().Expand(); 

output.ToString()打印出来

 gp => gp.Parent.Child.Name 

而Jon Skeet的解决scheme产量

 x => Invoke(p => p.Child.Name,Invoke(gp => gp.Parent,x)) 

我想这就是你所说的“嵌套函数调用”。

尝试这个:

 public static Expression<Func<TOuter, TInner>> Combine<TOuter, TMiddle, TInner>( Expression<Func<TOuter, TMiddle>> first, Expression<Func<TMiddle, TInner>> second) { return x => second.Compile()(first.Compile()(x)); } 

和用法:

 Expression<Func<GrandParent, Parent>> myFirst = gp => gp.Parent; Expression<Func<Parent, string>> mySecond = p => p.Child.Name; Expression<Func<GrandParent, string>> output = Combine(myFirst, mySecond); var grandParent = new GrandParent { Parent = new Parent { Child = new Child { Name = "child name" } } }; var childName = output.Compile()(grandParent); Console.WriteLine(childName); // prints "child name" 

有一个名为Layer Over LINQ的工具包,有一个扩展方法可以完成这个工作,它将两个expression式结合起来创build一个适用于LINQ to Entities的新expression式。

 Expression<Func<GrandParent, Parent>>> myFirst = gp => gp.Parent; Expression<Func<Parent, string>> mySecond = p => p.Child.Name; Expression<Func<GrandParent, string>> output = myFirst.Chain(mySecond); 
  public static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, TResult>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); } public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); }