C#中的表示式樹
本人之前從未接觸過表示式樹的概念,所以特意從網上找到兩篇這方面的資料學習了下。本文為閱讀筆記性質部落格!
表示式樹是.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; };
會報編譯錯誤“Lambada expression with state body cannot be converted to expression tree
”:即帶有語句的Lambda
表示式不能轉換成表示式樹。
用前面的方法雖然可以建立表示式樹,但是不夠靈活,如果要靈活構建表示式樹,可以像下面這樣:
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 });
表示式和表示式樹
表示式是匿名委託,本不是委託,是通過顯示或隱式的轉成委託例項
表示式樹是一種資料結構,是不可執行程式碼,它需要在
c#
程式碼中編譯成sql
語句,然後再sql
庫中執行查詢操作表示式樹種的每個節點本身表示一個表示式表示式轉換成表示式樹
Expression<表示式>
對於
IEnumerable
(即在記憶體中查詢)要用func<>(表示式(委託))
,對於委託而言直接執行即可
對於Queryable
(在庫裡查詢),傳入引數是表示式樹,得先解析翻譯成sql
語句,然後在外部庫中執行- 將表示式樹編譯成可執行的委託 表示式樹名
.Compile()(引數)
- 表示式的屬性:
(1).body
表示式的主體
(2)parameters
表示式的引數
(3)NodeType
表示式的返回引數
(4)Type
表示式型別 如Func<int,int>
相關文章
- c# 表示式樹(一)C#
- 【c#表示式樹】最完善的表示式樹Expression.Dynamic的玩法C#Express
- C# Lambda表示式詳解,及Lambda表示式樹的建立C#
- .NET 中的表示式樹
- 表示式樹
- 使用c#強大的表示式樹實現物件的深克隆C#物件
- c#表示式樹入門,看這個就夠了C#
- 表示式目錄樹
- C#高階程式設計六十五天----表示式樹C#程式設計
- C#高階程式設計六十六天----表示式樹總結C#程式設計
- C#-表示式目錄樹C#
- C# 正規表示式提取字串中括號裡的值C#字串
- SQLite中的表示式SQLite
- 瞭解下C# 正規表示式C#
- 二叉樹中序和後序遍歷表示式二叉樹
- 05.表示式目錄樹ExpressionExpress
- C# Lambda表示式和linq表示式 之 匿名物件查詢接收C#物件
- 中綴表示式轉字尾表示式
- MShadow中的表示式模板
- Java 中的 Lambda 表示式Java
- 學習正規表示式(js、C#)JSC#
- [C# Expression] 之動態建立表示式C#Express
- 使用c#強大的表示式樹實現物件的深克隆之解決迴圈引用的問題C#物件
- 中綴表示式
- C#進階之全面解析Lambda表示式C#
- Python中lambda表示式的用法Python
- Python中的邏輯表示式Python
- JS中的正規表示式JS
- Java中Lambda表示式的使用Java
- java中的正規表示式Java
- 理解DAX表示式中的VAR
- 關於利用STL棧求解四則中綴表示式以及中綴表示式轉逆波蘭表示式和逆波蘭表示式的求解
- 中綴表示式轉為逆波蘭表示式
- 羨慕 C# 的 switch 表示式不,JS 也可以有C#JS
- await會阻塞其所在表示式中後續表示式的執行AI
- C# 中的本地函式C#函式
- Python中eval函式的表示式如何使用Python函式
- C#字尾表示式解析計算字串公式C#字串公式