Lambda表达式可以转换成为代码(委托)或者数据(表达式树)。若将其赋值给委托,则Lambda表达式将转换为IL代码;如果赋值给Expression<TDelegate>,则构造出一颗表达式树。表达式树本质上来说就是一颗抽象语法树(AST),也就是一段代码经过解析后用树形来表达出这段代码的意思。解释器将在代码优化和代码生成的时候使用到AST。在.NET中,表达式树就是C#编译器解析lambda表达式的结果。简单来说,转换成表达式树以后,我们可以通过自己的解释器解析表达式树来按需求实现自己的逻辑。
比如想表达加法,用中文写就是 “二大于一” ,用数学来表达就是 "2>1",我们想表达的抽象概念就是大于,和具体的形式无关。因此表达式树中就有表示GreaterThan的一种Type,表达的就是这么一种大于的抽象概念。它可以由编译器把lambda表达式 ()=> 2>1 编译成我们所需的表达式树,然后我们再通过解析这个表达式树,把抽象概念翻译成我们所需的“二大于一”这种中文的具体形式。
C#中表达加法是……,因此不是每个类型都能相加的,编译器会报错。但是我们表达的意思是两种类型相加这一通用的概念,因此这时候就可以用表达式树来表达这一种概念,来“绕过”编译器限制。这是合理的。
我们可以自定义对于表达式中相加Expression.Add的理解,也可以由C#编译器按照它的理解来帮我们编译成可执行的匿名函数。C#编译器理解中并不是每种类型都可以相加的,因此如果Expression.compile成Func类型函数那么在执行的时候就有可能会报Exception,比如 "The binary operator Add is not defined for the types 'System.String' and 'System.String'."
表达式树的顺序与遍历……
编译器可以由lambda表达式帮我们生成一颗表达式树,我们接下来解决的就是要如何解析这个树的问题。对于树我们采用至顶向下的遍历方式,借助访问者模式去解析表达式树。
表达式树的生成
当编译器看到某个lambda表达式赋值给了类型为Expression的变量的时候,就会将其编译成对一系列工厂方法的调用,这些工厂方法将会在程序运行时动态的构造出表达式树。
表达式树将在程序运行时动态构造,不过一旦构造完成,则无法被再次修改。
public abstract class QueryProvider : IQueryProvider { protected QueryProvider() { } IQueryable<S> IQueryProvider.CreateQuery<S>(Expression expression) { return new Query<S>(this, expression); } IQueryable IQueryProvider.CreateQuery(Expression expression) { Type elementType = TypeSystem.GetElementType(expression.Type); try { return (IQueryable)Activator.CreateInstance(typeof(Query<>).MakeGenericType(elementType), new object[] { this, expression }); } catch (TargetInvocationException tie) { throw tie.InnerException; } } IQueryProvider.Execute<S>(Expression expression) { return (S)this.Execute(expression); } object IQueryProvider.Execute(Expression expression) { return this.Execute(expression); } public abstract string GetQueryText(Expression expression); public abstract object Execute(Expression expression); }
public class AmazonBookQueryProvider : QueryProvider { public override String GetQueryText(Expression expression) { AmazonBookQueryCriteria criteria; // Retrieve criteria criteria = new AmazonBookExpressionVisitor().ProcessExpression(expression); // Generate URL String url = AmazonHelper.BuildUrl(criteria); return url; } public override object Execute(Expression expression) { String url = GetQueryText(expression); IEnumerable<AmazonBook> results = AmazonHelper.PerformWebQuery(url); return results; } }
class AmazonBookExpressionVisitor { AmazonBookQueryCriteria _Criteria; public AmazonBookQueryCriteria ProcessExpression(Expression expression) { _Criteria = new AmazonBookQueryCriteria(); VisitExpression(expression); return _Criteria; } private void VisitExpression(Expression expression) { if (expression.NodeType == ExpressionType.AndAlso) { VisitAndAlso((BinaryExpression)expression); } else if (expression.NodeType == ExpressionType.Equal) { VisitEqual((BinaryExpression)expression); } else if (expression.NodeType == ExpressionType.LessThanOrEqual) { VisitLessThanOrEqual((BinaryExpression)expression); } else if (expression is MethodCallExpression) { VisitMethodCall((MethodCallExpression)expression); } else if (expression is LambdaExpression) { VisitExpression(((LambdaExpression)expression).Body); } } private void VisitAndAlso(BinaryExpression andAlso) { VisitExpression(andAlso.Left); VisitExpression(andAlso.Right); } private void VisitEqual(BinaryExpression expression) { // Handle book.Publisher == "xxx" if ((expression.Left.NodeType == ExpressionType.MemberAccess) && (((MemberExpression)expression.Left).Member.Name == "Publisher")) { if (expression.Right.NodeType == ExpressionType.Constant) _Criteria.Publisher = (String)((ConstantExpression)expression.Right).Value; else if (expression.Right.NodeType == ExpressionType.MemberAccess) _Criteria.Publisher = (String)GetMemberValue((MemberExpression)expression.Right); else throw new NotSupportedException("Expression type not supported for publisher: " + expression.Right.NodeType.ToString()); } // Handle book.Condition == BookCondition.* else if ((expression.Left is UnaryExpression) && (((UnaryExpression)expression.Left).Operand.Type == typeof(BookCondition))) { if (expression.Right.NodeType == ExpressionType.Constant) _Criteria.Condition = (BookCondition)((ConstantExpression)expression.Right).Value; else if (expression.Right.NodeType == ExpressionType.MemberAccess) _Criteria.Condition = (BookCondition)GetMemberValue((MemberExpression)expression.Right); else throw new NotSupportedException("Expression type not supported for book condition: " + expression.Right.NodeType.ToString()); } } private void VisitLessThanOrEqual(BinaryExpression expression) { // Handle book.Price <= xxx if ((expression.Left.NodeType == ExpressionType.MemberAccess) && (((MemberExpression)expression.Left).Member.Name == "Price")) { if (expression.Right.NodeType == ExpressionType.Constant) _Criteria.MaximumPrice = (Decimal)((ConstantExpression)expression.Right).Value; else if (expression.Right.NodeType == ExpressionType.MemberAccess) _Criteria.MaximumPrice = (Decimal)GetMemberValue((MemberExpression)expression.Right); else throw new NotSupportedException("Expression type not supported for price: " + expression.Right.NodeType.ToString()); } } private void VisitMethodCall(MethodCallExpression expression) { if ((expression.Method.DeclaringType == typeof(Queryable)) && (expression.Method.Name == "Where")) { //this.Visit(m.Arguments[0]); //LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]); //this.Visit(lambda.Body); VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand); } else if ((expression.Method.DeclaringType == typeof(String)) && (expression.Method.Name == "Contains")) { // Handle book.Title.Contains("xxx") if (expression.Object.NodeType == ExpressionType.MemberAccess) { MemberExpression memberExpr = (MemberExpression)expression.Object; if (memberExpr.Expression.Type == typeof(AmazonBook)) { if (memberExpr.Member.Name == "Title") { Expression argument; argument = expression.Arguments[0]; if (argument.NodeType == ExpressionType.Constant) { _Criteria.Title = (String)((ConstantExpression)argument).Value; } else if (argument.NodeType == ExpressionType.MemberAccess) { _Criteria.Title = (String)GetMemberValue((MemberExpression)argument); } else { throw new NotSupportedException("Expression type not supported: " + argument.NodeType.ToString()); } } } } } else { throw new NotSupportedException("Method not supported: " + expression.Method.Name); } } #region Helpers private Object GetMemberValue(MemberExpression memberExpression) { MemberInfo memberInfo; Object obj; if (memberExpression == null) throw new ArgumentNullException("memberExpression"); // Get object if (memberExpression.Expression is ConstantExpression) obj = ((ConstantExpression)memberExpression.Expression).Value; else if (memberExpression.Expression is MemberExpression) obj = GetMemberValue((MemberExpression)memberExpression.Expression); else throw new NotSupportedException("Expression type not supported: " + memberExpression.Expression.GetType().FullName); // Get value memberInfo = memberExpression.Member; if (memberInfo is PropertyInfo) { PropertyInfo property = (PropertyInfo)memberInfo; return property.GetValue(obj, null); } else if (memberInfo is FieldInfo) { FieldInfo field = (FieldInfo)memberInfo; return field.GetValue(obj); } else { throw new NotSupportedException("MemberInfo type not supported: " + memberInfo.GetType().FullName); } } #endregion Helpers }