最近要重寫公司自己開發的ORM框架;其中有一部分就是查詢的動態表達式;於是對這方面的東西做了一個簡單的梳理
官網的解釋:
表達式樹以樹形數據結構表示代碼,其中每一個節點都是一種表達式,比如方法調用和 x < y
這樣的二元運算等。
你可以對表達式樹中的代碼進行編輯和運算。 這樣能夠動態修改可執行代碼、在不同數據庫中執行 LINQ 查詢以及創建動態查詢。
表達式樹還能用於動態語言運行時 (DLR) 以提供動態語言和 .NET 之間的互操作性,同時保證編譯器編寫員能夠發射表達式樹而非 Microsoft 中間語言 (MSIL)
總結:構造可執行的代碼,以樹形結構的方式構造;
表達式與委托:委托可以直接執行,而表達式不可以,表達式可以編譯成委托
備注:實際上.NET還有以文本的方式構造可執行代碼塊的方式,但和表達式樹還是有區別
我個人工作中主要在以下場景中使用表達式樹:
1,代理類方法的構造(如AOP,gRpc,WebService中),通過表達式樹,構造委托執行要代理的方法
2,EF,MongoDB,自定義ORM等數據訪問層過濾使用的Expression,最典型的就是針對IQueryable類型分頁,排序的動態擴展
3,常用的Linq中AND,OR等Predicate加強的一些擴展
4,數學表達式,偽SQL代碼的執行解析器的構造(這個比較底層)
Expression調試工具:2019下載地址ExpressionTreeVisualizer
Expression調試工具源碼:https://github.com/zspitz/ExpressionTreeVisualizer/blob/master/README.md
Expression類有以下屬性:
Body:表達式的主體(類似於方法體)
Parameters:Lambda表達示的參數(參數列表)
NodeType:得到樹中某些節點的表達式類型(ExpressionType),這是一個有45種不同值的枚舉類型,代表表達式節點的所有可能類型,如返回常數、
也可能返回參數、或者返回一個值是否小於另外一個(<),或者返回一個值是否大於另外一個(>),或者返回兩個值的和( )等等。
Type:得到表達式的靜態類型
表達式樹的幾個比較常用的類型/方法,.NET FrameWork與.NET Core在表達式樹的使用上做了一些調整
Expression:表達式樹核心類,基類;提供了Call,Add等構建Expression的靜態方法
Expression.Call():非常核心的一個方法,用於創建調用類的方法的方法類型表達式樹;在.NET Core(易用)和.NET FrameWork(易懂)做了些變化
Expression.Parameter():用於創建參數類型表達式樹
Expression.Constant():用於創建常量類型表達式樹
Expression<TDelegate>:強類型的表達式樹類
Expression.Lambda<TDelegate>:將Expression轉換成Lambda表達式(設置Lambda的方法體和參數列表)
LambdaExpression.Parameters:做表達式樹拼接時常用到
LambdaExpression.tailCall:屬性,標記是否【尾調優化,參考后面】
創建表達式樹的方式(以下示例為.Net Core3.1)
1,以單行Lambda表達式創建表達式樹( 包含方法體的Lambda表達式是不能創建表達式樹 );
這種表達式樹的構造方式,Lambda表達式實際上是給LambdaExpression的Body(Body為Expression類型)賦值
//使用單行Lambda創建表達式 static void Expression1() { //表達式樹的Body Expression<Func<int, int>> expression = a => a + 100; Expression<Func<int, int, int>> expression2 = (a, b) => a + b; Console.WriteLine(expression.ToString()); Console.WriteLine(expression.Compile()(120)); }
如果你使用具有方法體的Lambda表達式創建表達式樹,是不成功的;表達式樹是Lambda表達式的內存中表現形式
//錯誤示例:使用具有方法體的Lambda創建表達式 static void Expression2() { //編譯不通過,不支持有方法體的Lambda創建表達式 //Expression<Action<int>> expression1 = a => { }; //編譯不通過,有方法體的Lambda創建表達式 //Expression<Func<int, int>> expression2 = a => { return a + 100; }; //Console.WriteLine(expression1.ToString()); }
2,通過API創建表達式樹
創建沒有入參和返回參數的表達式樹
//無入參,返參的表達式 static void Expression3() { var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); //表達式樹的Body var method = Expression.Block(Expression.Call(null, WriteLine, Expression.Constant("來自Expression的輸出"))); var action = Expression.Lambda<Action>(method).Compile(); action(); }
創建有入參,沒有返回參數的表達式樹
//有入參,無返參的表達式 static void Expression4() { var stringParam = Expression.Parameter(typeof(string), "stringParam"); var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); var method = Expression.Block(Expression.Call(null, WriteLine, new[] { stringParam })); var action = Expression.Lambda<Action<string>>(method, new[] { stringParam }).Compile(); action("來自Expression4的輸出yyyyyy"); }
創建有入參,有返回參數的表達式樹
//有入參,有返參的表達式1 static void Expression5() { var stringParam = Expression.Parameter(typeof(string), "stringParam"); var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); var method = Expression.Block( Expression.Call(null, WriteLine, new[] { stringParam }), Expression.Assign(stringParam, Expression.Constant("你好啊")) ); var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile(); var ms = action("Expression5ss"); } //有入參,有返參的表達式2 static void Expression6() { var stringParam = Expression.Parameter(typeof(string), "stringParam"); var method = Expression.Block(Expression.Call(null,typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, Expression.Constant("哈哈哈哈") })); var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile(); var ms = action("test單個參數"); Console.WriteLine(ms); } //有入參,有返參的表達式3 static void Expression7() { var stringParam = Expression.Parameter(typeof(string), "stringParam"); var stringParam2 = Expression.Parameter(typeof(string), "stringParam2"); var method = Expression.Block( new[] { stringParam2 }, Expression.Assign(stringParam2, Expression.Constant("參數2")), Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 }) ); var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile(); var ms = action("test單個參數,且Call聲明參數"); Console.WriteLine(ms); } //有入參,有返參的表達式4 static void Expression8() { var stringParam = Expression.Parameter(typeof(string), "stringParam"); var stringParam2 = Expression.Parameter(typeof(string), "stringParam2"); var method = Expression.Block( Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 }) ); var action = Expression.Lambda<Func<string, string,string>>(method, new[] { stringParam, stringParam2 }).Compile(); var ms = action("多個參數:參數1","啦啦啦啦"); Console.WriteLine(ms); }
函數尾部調用參數優化:簡稱尾調優化
1,當函數的最后一步時調用其他函數時,即為尾調;尾調不只是說函數的最尾部;示例,Update,Add方法都是尾部調用
public int AddUser(string id,string name) { if (id==null) { return Update(name); } return Add(name); }
2,LambdaExpression.tailCall屬性和Expresssion.Lambda方法中的tailCall參數都是用來標記是否需要尾調優化的,
如果標記尾調優化,則表示當執行進入尾調的函數時,當前函數已經不再依賴父函數上的數據,父函數上的所占用的內存空間將被清空,用於優化節省內存
表達式樹動態復制對象:
/// <summary> /// 當前類 /// </summary> public class Student { public int Id { get; set; } public string UserName { get; set; } public string Name { get; set; } } /// <summary> /// 目標類 /// </summary> public class StudentCopy { public int Id { get; set; } public string UserName { get; set; } public string Name { get; set; } }
class Program { static void Main(string[] args) { Student student = new Student() { Id = 1002, Name = "哈哈哈", UserName = "cc" }; //使用new Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); var entity1 = CopyToByNew(student); stopwatch.Stop(); Console.WriteLine($"new:{stopwatch.ElapsedTicks}"); //使用反射 stopwatch = new Stopwatch(); stopwatch.Start(); var entity2 = CopyToByReflect1<Student, StudentCopy>(student); stopwatch.Stop(); Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}"); stopwatch = new Stopwatch(); stopwatch.Start(); var entity3 = CopyToByReflect2<Student, StudentCopy>(student); stopwatch.Stop(); Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}"); //使用json stopwatch = new Stopwatch(); stopwatch.Start(); var entity4 = CopyToByJson<Student, StudentCopy>(student); stopwatch.Stop(); Console.WriteLine($"json:{stopwatch.ElapsedTicks}"); //使用表達式構造委托 stopwatch = new Stopwatch(); stopwatch.Start(); var entity5 = CopyToByExpression<Student, StudentCopy>(student); stopwatch.Stop(); Console.WriteLine($"expression:{stopwatch.ElapsedTicks}"); Console.WriteLine("Hello World!"); Console.ReadKey(); } static StudentCopy CopyToByNew(Student student) { return new StudentCopy { Id = student.Id, Name = student.Name, UserName = student.UserName }; } static Tout CopyToByReflect1<Tin, Tout>(Tin dto) { Tout entity = Activator.CreateInstance<Tout>(); foreach (var itemOut in entity.GetType().GetProperties()) { var propIn = dto.GetType().GetProperty(itemOut.Name); if (propIn != null) { itemOut.SetValue(entity, propIn.GetValue(dto)); } } return entity; } static Tout CopyToByReflect2<Tin, Tout>(Tin dto) { Tout entity = Activator.CreateInstance<Tout>(); foreach (var itemOut in entity.GetType().GetFields()) { var propIn = dto.GetType().GetField(itemOut.Name); itemOut.SetValue(entity, propIn.GetValue(dto)); } return entity; } static Tout CopyToByJson<TIn, Tout>(TIn dto) { return JsonConvert.DeserializeObject<Tout>(JsonConvert.SerializeObject(dto)); } static Tout CopyToByExpression<Tin, Tout>(Tin dto) { ParameterExpression parameterExpression = Expression.Parameter(typeof(Tin), "a"); List<MemberBinding> memberBindings = new List<MemberBinding>(); foreach (var item in typeof(Tout).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(Tin).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindings.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(Tout)), memberBindings.ToArray()); Expression<Func<Tin, Tout>> lambda = Expression.Lambda<Func<Tin, Tout>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); Func<Tin, Tout> func = lambda.Compile(); return func(dto); } }
使用ExpressionTreeVisualizer工具
下載地址:參考前面的地址
ExpressionTreeVisualizer工具主要有三個功能
- 表達式樹結構的可視化
- 表達式樹源碼顯示
- 表達式樹節點結構: parameters, closure variables, constants and default values