第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)
一. 基本介紹
回憶:?最早接觸到表達式目錄樹(Expression)可能要追溯到幾年前使用EF早期的時候,發現where方法里的參數是Expression<Func<T,bool>>這么一個類型,當初不是很理解,只是知道傳入lambda表達式使用即可,對于Expression和里面的Func<T,bool>到底是怎么一種關系,都不清楚。
今天,帶著回憶開發初期的心情,詳細的介紹一下這一段時間對Expression的理解。
1. Expression與Func委托的區別
①:委托是一種類型,是方法的抽象,通過委托可以將方法以參數的形式傳遞給另一個方法,同時調用委托的時候,它縮包含的方法都會被實現。委托的關鍵字是delegate,可以自定義委托,也可以使用內置委托,通過簡化,可以將Lambda表達式或Lambda語句賦值給委托,委托的調用包括同步調用和異步調用。
②:表達式目錄樹(Expression),是一種數據結構,可以利用Lambda表達式進行聲明,Lambda表達式的規則要符合Expression中Func委托的參數規則。
可以利用Lambda表達式進行聲明,但Lambda語句是不能聲明的。
Expression調用Compile方法可以轉換成<TDelegate>中的委托。
回顧委托的代碼:
1 {2 //1. Func委托,必須要有返回值,最后一個參數為返回值,前面為輸入參數3 Func<int, int, int> func1 = new Func<int, int, int>((int m, int n) =>4 {5 return m * n + 2;6 });7 //對其進行最簡化(Lambda語句)8 Func<int, int, int> func2 = (m, n) =>9 { 10 return m * n + 2; 11 }; 12 //對其進行最簡化(Lambda表達式) 13 Func<int, int, int> func3 = (m, n) => m * n + 2; 14 //調用委托 15 int result1 = func1.Invoke(2, 3); 16 int result2 = func2.Invoke(2, 3); 17 int result3 = func3.Invoke(2, 3); 18 Console.WriteLine("委托三種形式結果分別為:{0},{1},{2}", result1, result2, result3); 19 }初識Expression表達式目錄樹的代碼
1 {2 //報錯 (Lambda語句無法轉換成表達式目錄樹)3 //Expression<Func<int, int, int>> exp1 = (m, n) =>4 //{5 // return m * n + 2;6 //};7 8 //利用Lambda表達式 來聲明 表達式目錄樹9 Expression<Func<int, int, int>> exp2 = (m, n) => m * n + 2; 10 11 //利用Compile編譯,可以將表達式目錄樹轉換成委托 12 Func<int, int, int> func = exp2.Compile(); 13 int result1 = func.Invoke(2, 3); 14 Console.WriteLine("表達式目錄樹轉換成委托后結果為:{0}", result1); 15 }執行結果
2. 自己拼接表達式目錄樹
①. 核心:先把Lambda表達式寫出來,然后用ILSpy工具進行編譯,就能把表達式目錄樹的拼接過程給顯示出來,當然有些代碼不是我們想要的,需要轉換成C#代碼。
②. 了解拼接要用到的類型和方法:
類型:常量值表達式(ConstantExpression)、命名參數表達式(ParameterExpression)、含二元運算符的表達式(BinaryExpression)
方法:設置常量表達式的值(Constant方法)、設置參數表達式的變量(Parameter方法)、相加(Add方法)、相乘(Multiply方法)、Lambda方法:將拼接的二元表達式轉換成表達式目錄樹
③. 步驟:聲明變量和常量→進行二元計算→將最終二元運算符的表達式轉換成表達式目錄樹
?下面分享拼接?Expression<Func<int, int, int>> express1 = (m, n) => m * n + 2; 的代碼:
{Func<int, int, int> func1 = (m, n) => m * n + 2;//利用反編譯工具翻譯下面這個ExpressionExpression<Func<int, int, int>> express1 = (m, n) => m * n + 2;//下面是反編譯以后的代碼(自己稍加改進了一下)ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");BinaryExpression binaryExpression = Expression.Multiply(parameterExpression, parameterExpression2);ConstantExpression constantExpression = Expression.Constant(2, typeof(int));BinaryExpression binaryFinalBody = Expression.Add(binaryExpression, constantExpression);Expression<Func<int, int, int>> express = Expression.Lambda<Func<int, int, int>>(binaryFinalBody, new ParameterExpression[]{parameterExpression, parameterExpression2});int result = express.Compile().Invoke(2, 3);Console.WriteLine("自己拼接的表達式目錄樹的結果為:{0}", result);}運行結果:
?
二.?實體間Copy賦值的幾類處理方案
背景:?在實際開發中,我們可能經常會遇到這種場景,兩個實體的名稱不同,屬性完全相同,需要將一個實體的值賦值給另一個對應實體上的屬性。
解決這類問題通常有以下幾種方案:
1. 直接硬編碼的形式:速度最快(0.126s)
2. 通過反射遍歷屬性的形式 (6.328s)
3. 利用序列化和反序列化的形式:將復制實體序列化字符串,在把該字符串反序列化被賦值實體(7.768s)
4. 字典緩存+表達式目錄樹(Lambda的拼接代碼了解即可) (0.663s)
5. 泛型緩存+表達式目錄樹(Lambda的拼接代碼了解即可) (2.134s)
?代碼分享:
1 public static class CopyUtils2 {3 //字典緩存4 private static Dictionary<string, object> _Dic = new Dictionary<string, object>();5 6 public static void Show()7 {8 //0. 準備實體 9 User user = new User()10 {11 id = 1,12 userName = "ypf",13 userAge = 314 };15 long time1 = 0;16 long time2 = 0;17 long time3 = 0;18 long time4 = 0;19 long time5 = 0;20 21 #region 1-直接硬編碼的形式22 {23 Task.Run(() =>24 {25 Stopwatch watch = new Stopwatch();26 watch.Start();27 for (int i = 0; i < 1000000; i++)28 {29 UserCopy userCopy = new UserCopy()30 {31 id = user.id,32 userName = user.userName,33 userAge = user.userAge,34 };35 }36 watch.Stop();37 time1 = watch.ElapsedMilliseconds;38 Console.WriteLine("方案1所需要的時間為:{0}", time1);39 });40 41 }42 #endregion43 44 #region 2-反射遍歷屬性45 {46 Task.Run(() =>47 {48 Stopwatch watch = new Stopwatch();49 watch.Start();50 for (int i = 0; i < 1000000; i++)51 {52 CopyUtils.ReflectionMapper<User, UserCopy>(user);53 }54 watch.Stop();55 time2 = watch.ElapsedMilliseconds;56 Console.WriteLine("方案2所需要的時間為:{0}", time2);57 });58 }59 #endregion60 61 #region 3-序列化和反序列化62 {63 Task.Run(() =>64 {65 Stopwatch watch = new Stopwatch();66 watch.Start();67 for (int i = 0; i < 1000000; i++)68 {69 CopyUtils.SerialzerMapper<User, UserCopy>(user);70 }71 watch.Stop();72 time3 = watch.ElapsedMilliseconds;73 Console.WriteLine("方案3所需要的時間為:{0}", time3);74 });75 76 }77 #endregion78 79 #region 04-字典緩存+表達式目錄樹80 {81 Task.Run(() =>82 {83 Stopwatch watch = new Stopwatch();84 watch.Start();85 for (int i = 0; i < 1000000; i++)86 {87 CopyUtils.DicExpressionMapper<User, UserCopy>(user);88 }89 watch.Stop();90 time4 = watch.ElapsedMilliseconds;91 Console.WriteLine("方案4所需要的時間為:{0}", time4);92 });93 }94 #endregion95 96 #region 05-泛型緩存+表達式目錄樹97 {98 Task.Run(() =>99 { 100 Stopwatch watch = new Stopwatch(); 101 watch.Start(); 102 for (int i = 0; i < 1000000; i++) 103 { 104 GenericExpressionMapper<User, UserCopy>.Trans(user); 105 } 106 watch.Stop(); 107 time5 = watch.ElapsedMilliseconds; 108 Console.WriteLine("方案5所需要的時間為:{0}", time5); 109 }); 110 } 111 #endregion 112 113 }上述代碼涉及到的幾個封裝
?View Code
?泛型緩存
最終運行結果:
三.?剝離表達式目錄樹
? 這里補充幾個常用的表達式目錄樹的拼接代碼:And、Or、Not 。
?ExpressionExtend擴展類代碼:
1 public static class ExpressionExtend2 {3 /// <summary>4 /// 合并表達式 expr1 AND expr25 /// </summary>6 /// <typeparam name="T"></typeparam>7 /// <param name="expr1"></param>8 /// <param name="expr2"></param>9 /// <returns></returns> 10 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) 11 { 12 ParameterExpression newParameter = Expression.Parameter(typeof(T), "c"); 13 NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter); 14 15 var left = visitor.Replace(expr1.Body); 16 var right = visitor.Replace(expr2.Body); 17 var body = Expression.And(left, right); 18 return Expression.Lambda<Func<T, bool>>(body, newParameter); 19 20 } 21 /// <summary> 22 /// 合并表達式 expr1 or expr2 23 /// </summary> 24 /// <typeparam name="T"></typeparam> 25 /// <param name="expr1"></param> 26 /// <param name="expr2"></param> 27 /// <returns></returns> 28 public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) 29 { 30 31 ParameterExpression newParameter = Expression.Parameter(typeof(T), "c"); 32 NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter); 33 34 var left = visitor.Replace(expr1.Body); 35 var right = visitor.Replace(expr2.Body); 36 var body = Expression.Or(left, right); 37 return Expression.Lambda<Func<T, bool>>(body, newParameter); 38 } 39 public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr) 40 { 41 var candidateExpr = expr.Parameters[0]; 42 var body = Expression.Not(expr.Body); 43 44 return Expression.Lambda<Func<T, bool>>(body, candidateExpr); 45 } 46 }NewExpressionVisitor建立新表達式輔助類代碼:
1 /// <summary>2 /// 建立新表達式3 /// </summary>4 internal class NewExpressionVisitor : ExpressionVisitor5 {6 public ParameterExpression _NewParameter { get; private set; }7 public NewExpressionVisitor(ParameterExpression param)8 {9 this._NewParameter = param; 10 } 11 public Expression Replace(Expression exp) 12 { 13 return this.Visit(exp); 14 } 15 protected override Expression VisitParameter(ParameterExpression node) 16 { 17 return this._NewParameter; 18 } 19 }如何使用的代碼:
1 public class VisitorUtils2 {3 public static void Show()4 {5 Expression<Func<User, bool>> lambda1 = x => x.userAge > 5;6 Expression<Func<User, bool>> lambda2 = x => x.id > 5;7 Expression<Func<User, bool>> lambda3 = lambda1.And(lambda2);8 Expression<Func<User, bool>> lambda4 = lambda1.Or(lambda2);9 Expression<Func<User, bool>> lambda5 = lambda1.Not(); 10 11 Do1(lambda1); 12 Do1(lambda2); 13 Do1(lambda3); 14 Do1(lambda4); 15 Do1(lambda5); 16 } 17 18 private static void Do1(Expression<Func<User, bool>> func) 19 { 20 List<User> user = new List<User>() 21 { 22 new User(){id=4,userName="123",userAge=4}, 23 new User(){id=5,userName="234",userAge=5}, 24 new User(){id=6,userName="345",userAge=6}, 25 }; 26 27 List<User> peopleList = user.Where(func.Compile()).ToList(); 28 } 29 }總結
以上是生活随笔為你收集整理的第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 央行数字货币发行!从以下4个方面入手比较
- 下一篇: RDLC报表下载的权限问题