本人之前從未接觸過表達式樹的概念,所以特意從網上找到兩篇這方面的資料學習了下。本文為閱讀筆記性質博客!
表達式樹是.NET 3.5之后引入的,它是一個強大靈活的工具(比如用在LINQ中構造動態查詢)。
先來看看Expression類的API接口:
using System.Collections.ObjectModel; namespace System.Linq.Expressions { // Summary: // Represents a strongly typed lambda expression as a data structure in the // form of an expression tree. This class cannot be inherited. // // Type parameters: // TDelegate: // The type of the delegate that the System.Linq.Expressions.Expression<tdelegate> // represents. public sealed class Expression<tdelegate> : LambdaExpression { // Summary: // Compiles the lambda expression described by the expression tree into executable // code. // // Returns: // A delegate of type TDelegate that represents the lambda expression described // by the System.Linq.Expressions.Expression<tdelegate>. public TDelegate Compile(); } }
表達式樹的語法如下:
Expression<Func<type,returnType>> = (param) => lamdaexpresion;
我們先來看一個簡單例子:
Expression<Func<int, int, int>> expr = (x, y) => x+y;
這就是一個表達式樹了。使用Expression Tree Visualizer工具(直接調試模式下看也可以,只不過沒這個直觀)在調試模式下查看這個表達式樹(就是一個對象),如下:
可以看到表達式樹主要由下面四部分組成:
1、Body 主體部分
2、Parameters 參數部分
3、NodeType 節點類型
4、Lambda表達式類型
對於前面舉的例子,主體部分即x+y,參數部分即(x,y)。Lambda表達式類型是Func<Int32, Int32, Int32>。注意主體部分可以是表達式,但是不能包含語句,如下這樣:
Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };
用前面的方法雖然可以創建表達式樹,但是不夠靈活,如果要靈活構建表達式樹,可以像下面這樣:
ParameterExpression exp1 = Expression.Parameter(typeof(int), "a"); ParameterExpression exp2 = Expression.Parameter(typeof(int), "b"); BinaryExpression exp = Expression.Multiply(exp1,exp2); var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });
exp1、exp2即表達式樹的參數,exp是表達式樹的主體。如果我利用Reflector反編譯Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };得到下面的C#代碼:
ParameterExpression CS$0$0000; ParameterExpression CS$0$0001; Expression<Func<int, int, int>> expr = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(CS$0$0000 = Expression.Parameter(typeof(int), "x"), CS$0$0001 = Expression.Parameter(typeof(int), "y")), new ParameterExpression[] { CS$0$0000, CS$0$0001 });
可以看到它基本和上面的手動構建代碼一致。再來看一個簡單的例子:
Expression<Func<Customer, bool>> filter = cust => Equal(Property(cust,"Region"),"North");
可以用下面的代碼手動構建效果等同於上面的表達式樹:
// declare a parameter of type Customer named cust ParameterExpression custParam = Expression.Parameter( typeof(Customer), "custParam"); // compare (equality) the Region property of the // parameter against the string constant "North" BinaryExpression body = Expression.Equal( Expression.Property(custParam, "Region"), Expression.Constant("North", typeof(string))); // formalise this as a lambda Expression<Func<Customer, bool>> filter = Expression.Lambda<Func<Customer, bool>>(body, cust);
然后我們可以通過表達式樹的Compile方法將表達式樹編譯成Lambda表達式,如下:
Func<Customer, bool> filterFunc = filter.Compile();
但是Compile調用過程涉及動態代碼生成,所以出於性能考慮最好只調用一次,然后緩存起來。或者像下面這樣在靜態構造塊中使用(也只會調用一次):
public static class Operator<T> { private static readonly Func<T, T, T> add; public static T Add(T x, T y) { return add(x, y); } static Operator() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); var body = Expression.Add(x, y); add = Expression.Lambda<Func<T, T, T>>( body, x, y).Compile(); } }
Expression類包含下面幾類靜態方法(.NET 3.5中):
Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, Subtract, SubtractChecked, UnaryPlus Creation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit Bitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>) Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, NotEqual, OrElse (||), TypeIs Member Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField Other: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote
下面我們類似前面重載一個淺拷貝的例子(比使用反射開銷小):
using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var p = new Person() { Name = "jxq", Age = 23 }; var shallowCopy = Operator<Person>.ShallowCopy(p); shallowCopy.Name = "feichexia"; Console.WriteLine(shallowCopy.Name); Console.WriteLine(p.Name); Console.ReadKey(); } public class Person { public string Name { get; set; } public int Age { get; set; } } public static class Operator<T> { private static readonly Func<T, T> ShallowClone; public static T ShallowCopy(T sourcObj) { return ShallowClone.Invoke(sourcObj); } static Operator() { var origParam = Expression.Parameter(typeof(T), "orig"); // for each read/write property on T, create a new binding // (for the object initializer) that copies the original's value into the new object var setProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) where prop.CanRead && prop.CanWrite select (MemberBinding)Expression.Bind(prop, Expression.Property(origParam, prop)); var body = Expression.MemberInit( // object initializer Expression.New(typeof(T)), // ctor setProps // property assignments ); ShallowClone = Expression.Lambda<Func<T, T>>(body, origParam).Compile(); } } } }
繼續看Expression.AndAlso的使用,它可以用來替代類似下面這種多條件與的情況:
Func<Person, Person, bool> personEqual = (person1, person2) => person1.Name == person2.Name && person1.Age == person2.Age; if(personEqual(p1, p2)) { Console.WriteLine("兩個對象所有屬性值都相等!"); }
代碼如下:
using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var p1 = new Person() { Name = "jxq", Age = 23 }; var p2 = new Person() { Name = "jxq", Age = 23 }; if (Operator<Person>.ObjectPropertyEqual(p1, p2)) { Console.WriteLine("兩個對象所有屬性值都相等!"); } Console.ReadKey(); } public class Person { public string Name { get; set; } public int Age { get; set; } } public static class Operator<T> { private static readonly Func<T, T, bool> PropsEqual; public static bool ObjectPropertyEqual(T obj1, T obj2) { return PropsEqual.Invoke(obj1, obj2); } static Operator() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); // 獲取類型T上的可讀Property var readableProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) where prop.CanRead select prop; Expression combination = null; foreach (var readableProp in readableProps) { var thisPropEqual = Expression.Equal(Expression.Property(x, readableProp), Expression.Property(y, readableProp)); if(combination == null) { combination = thisPropEqual; } else { combination = Expression.AndAlso(combination, thisPropEqual); } } if(combination == null) // 如果沒有需要比較的東西,直接返回false { PropsEqual = (p1, p2) => false; } else { PropsEqual = Expression.Lambda<Func<T, T, bool>>(combination, x, y).Compile(); } } } } }
在.NET 4.0中擴展了一些Expression的靜態方法,使得編寫動態代碼更容易:
Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked
Arithmetic: Decrement, Default, Increment, OnesComplement
Member Access: ArrayAccess, Dynamic
Logical: ReferenceEqual, ReferenceNotEqual, TypeEqual
Flow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable
Exceptions: Catch, Rethrow, Throw
Debug: ClearDebugInfo, DebugInfo
下面是一個利用表達式樹編寫動態代碼的例子(循環打印0到9):
using System; using System.Linq.Expressions; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var exitFor = Expression.Label("exitFor"); // jump label var x = Expression.Variable(typeof(int), "x"); var body = Expression.Block( new[] { x }, // declare scope variables Expression.Assign(x, Expression.Constant(0, typeof(int))), // init Expression.Loop( Expression.IfThenElse( Expression.GreaterThanOrEqual( // test for exit x, Expression.Constant(10, typeof(int)) ), Expression.Break(exitFor), // perform exit Expression.Block( // perform code Expression.Call( typeof(Console), "WriteLine", null, x), Expression.PostIncrementAssign(x) ) ), exitFor ) // Loop ends ); var runtimeLoop = Expression.Lambda<Action>(body).Compile(); runtimeLoop(); Console.Read(); } } }
另外WhereIn擴展實現如下,如果前面的例子都熟悉了的話,這個自然也很容易看懂了:
/// <summary> /// 使之支持Sql in語法 /// </summary> /// <typeparam name = "T"></typeparam> /// <typeparam name = "TValue"></typeparam> /// <param name = "query"></param> /// <param name = "obj"></param> /// <param name = "values"></param> /// <returns></returns> public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values) { return query.Where(BuildContainsExpression(obj, values)); } private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>( Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values) { if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); } if (null == values) { throw new ArgumentNullException("values"); } var p = valueSelector.Parameters.Single(); if (!values.Any()) return e => false; var equals = values.Select(value => (Expression) Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof (TValue)))); var body = equals.Aggregate(Expression.Or); return Expression.Lambda<Func<TElement, bool>>(body, p); }
調用方式如下:
db.Users.WhereIIn(u => u.Id, new int[] { 1, 2, 3 });
關於使用表達式樹構建LINQ動態查詢,請參考Dynamic Linq Queries with Expression Trees
參考資料: