Expression表示式目錄樹
一、初識Expression
原始碼
1、在上一篇我們講到了委託(忘記了可以在看看,點贊在看養成習慣),今天要講的Expression也和委託有一點點關係吧(沒有直接關係,只是想要大家看看我其他的文章),Expression是.NET準備為Linq to Sql準備的,它的名稱空間是System.Linq.Expressions
2、不知道大家有沒有使用者ORM(物件對映實體)的資料訪問層框架,使用過的小夥伴我相信對下面的虛擬碼不會陌生,我們在Where中傳入的就是Expression<Func<TSource, bool>> predicate
3、我們進入Expression一看究竟,我們可以看到Expression<Func<TSource, bool>>裡面有一些方法(後面會慢慢道來),最終繼承LambdaExpression
4、我們繼續進入LambdaExpression,我們看到了一些屬性(這些就是我們lambda的組成的方法和屬性),但是最終還是看到繼承了Expression
5、繼續一鼓作氣進入Expression,到這裡我們看到了最終的基類它裡面也有很多方法,要說的話這兩天都說不完,我們就簡單的介紹一些常用的
二、循序漸進
1、大家可能看了上面還有一點點蒙,不急我們繼續,我們看下面的實際操作,我們可以看到我們建立一個Expression和一個委託,我們使用Compile方法可以將Expression轉換成委託,最後我們執行的結果是一樣的。(大家是不是覺得,Expression和一個委託差不多呢?哈哈答案肯定不是)
{ //這裡我們看這著和委託差不多,但是它還真不是委託 Expression<Func<int, int>> expression = x => x + 10; //Compile方法可以將Expression轉換成委託 Func<int, int> func = expression.Compile(); //直接宣告委託 Func<int, int> func1 = x => x + 10; Console.WriteLine("轉換之後的委託--" + func.Invoke(5)); Console.WriteLine("委託--" + func1.Invoke(5)); }
2、接下來我們進一步的解析我們直接使用lambda表示式建立Expression<Func<int, int, int>> expression = (m, n) => m * n + 3; 然後我們在使用底層程式碼實現這句程式碼,我們也可以很清楚的看到這裡我們一步一步的拆解,裡面使用了Expression中一些物件建立的
//下面我們使用原始的方式建立一個Expression<Func<int, int, int>> //建立一個m引數 這裡的引數是值的(m,n)的,如果說你有幾個引數就建立幾個 ParameterExpression parameter = Expression.Parameter(typeof(int), "m"); //建立一個n引數 ParameterExpression parameter1 = Expression.Parameter(typeof(int), "n"); //建立一個常量3 ConstantExpression constant = Expression.Constant(3, typeof(int)); //首先算出最左邊的m*n的結果 BinaryExpression binaryExpression = Expression.Multiply(parameter, parameter1); //然後算出(m*n)+3的結果 binaryExpression = Expression.Add(binaryExpression, constant); //將上面分解的步驟拼接成lambda Expression<Func<int, int, int>> expression1 = Expression.Lambda<Func<int, int, int>>(binaryExpression, new ParameterExpression[] { parameter, parameter1 }); Console.WriteLine("lambda表示式方式--" + expression.Compile()(5, 6)); Console.WriteLine("自己寫的組裝" + expression1.Compile()(5, 6));View Code
3、如果你覺得,還不夠我們就寫幾個例項Expression<Func<Student, bool>> expression = x => x.ID.Equals(15);
//首先還是定義一個x引數對於上面的x的引數 ParameterExpression parameter = Expression.Parameter(typeof(Student), "x"); //首先我們還是從左邊進行拆分 獲取到屬性 MemberExpression property = Expression.Property(parameter, typeof(Student).GetProperty("ID")); //獲取我們的方法 MethodInfo equals = typeof(Student).GetMethod("Equals"); //定義我們的常量 ConstantExpression constant = Expression.Constant("15", typeof(string)); //定義一個方法拼接、第一個引數是我們的屬性,第二個引數是使用的方法,第三個引數是傳入方法的引數 MethodCallExpression coll = Expression.Call(property, equals, new Expression[] { constant }); //所有的資料解析完了之後,我們就需要將引數、方法進行拼裝了 Expression<Func<Student, bool>> expression1 = Expression.Lambda<Func<Student, bool>>(coll, new ParameterExpression[] { parameter }); Student student = new Student { ID = 15 }; Console.WriteLine("lambda表示式方式--" + expression.Compile()(student)); Console.WriteLine("自己組裝方式--" + expression1.Compile()(student));View Code
4、我們可以看出Expression就是進行圖下的不斷拆解,然後在進行組裝lambda執行
三、漸入佳境
1、我記得我之前在寫AutoMapper的時候說要給大家寫一次,這次我就滿足大家,我們在寫Mode和Entity轉換的時候,量少的時候我們會直接寫硬編碼
Student student = new Student { ID = 15, Name = "產品粑粑", Age = 18 }; //硬編碼 { //硬編碼轉換 StudentModel studentModel = new StudentModel { ID = student.ID, Name = student.Name, Age = student.Age }; }View Code
2、但是我們專案中使用的次數過於頻繁後,我們就會使用AutoMapper自動映射了,今天我們就不使用它了我們決定自己造輪子,我們分別使用(1,反射的方式、2,表示式目錄樹+字典、3,表示式目錄樹+泛型委託)
StudentModel studentModel = new StudentModel(); Type type1 = student.GetType(); Type type2 = studentModel.GetType(); foreach (var item in type2.GetProperties()) { //判斷是不是存在 if (type1.GetProperty(item.Name) != null) { item.SetValue(studentModel, type1.GetProperty(item.Name).GetValue(student)); } }View Code
/// <summary> /// 詞典方法推展 /// </summary> public class DictionariesExpand<T, TOut> { /// <summary> /// 建立一個靜態的容器存放委託 /// </summary> private static Dictionary<string, Func<T, TOut>> pairs = new Dictionary<string, Func<T, TOut>>(); /// <summary> /// 轉換物件 /// </summary> /// <typeparam name="T">輸入物件</typeparam> /// <param name="obj">輸入引數</param> /// <returns></returns> public static TOut ToObj(T obj) { //生成 string key = typeof(T).FullName + typeof(TOut).FullName; if (!pairs.ContainsKey(key)) { //首先我們還是建立一個引數 ParameterExpression parameter = Expression.Parameter(typeof(T)); //獲取要轉化後的型別 Type type = typeof(TOut); //建立一個容器存放解析的成員 List<MemberBinding> list = new List<MemberBinding>(); //遍歷屬性 foreach (var item in type.GetProperties()) { //獲取引數中item.Name對應的名稱 MemberExpression memberExpression = Expression.Property(parameter, typeof(T).GetProperty(item.Name)); //判斷是否存在 if (memberExpression != null) { MemberBinding member = Expression.Bind(item, memberExpression); list.Add(member); } } //遍歷欄位 foreach (var item in type.GetFields()) { //獲取引數中item.Name對應的名稱 MemberExpression memberExpression = Expression.Field(parameter, typeof(T).GetField(item.Name)); //判斷是否存在 if (memberExpression != null) { MemberBinding member = Expression.Bind(item, memberExpression); list.Add(member); } } //初始化轉換後的型別,並且進行初始化賦值 MemberInitExpression memberInit = Expression.MemberInit(Expression.New(typeof(TOut)), list); //所有的準備工作已經完成準備生成lambda Expression<Func<T, TOut>> expression = Expression.Lambda<Func<T, TOut>>(memberInit, new ParameterExpression[] { parameter }); Func<T, TOut> entrust = expression.Compile(); //生成委託存放到我們的字典 pairs.Add(key, entrust); return entrust.Invoke(obj); } return pairs[key].Invoke(obj); } }View Code
/// <summary> /// 泛型方法推展 /// 當我們使用靜態方法,會執行靜態的無參建構函式 ,不會呼叫無參建構函式 /// 我們使用泛型的時候會儲存不同泛型的副本,一直儲存在記憶體裡面不會釋放,所以可以 /// 實現偽硬編碼 /// </summary> public class GenericityExpand<T, TOut> { private static Func<T, TOut> _Func = null; static GenericityExpand() { //首先我們還是建立一個引數 ParameterExpression parameter = Expression.Parameter(typeof(T)); //獲取要轉化後的型別 Type type = typeof(TOut); //建立一個容器存放解析的成員 List<MemberBinding> list = new List<MemberBinding>(); //遍歷屬性 foreach (var item in type.GetProperties()) { //獲取引數中item.Name對應的名稱 MemberExpression memberExpression = Expression.Property(parameter, typeof(T).GetProperty(item.Name)); //判斷是否存在 if (memberExpression != null) { MemberBinding member = Expression.Bind(item, memberExpression); list.Add(member); } } //遍歷欄位 foreach (var item in type.GetFields()) { //獲取引數中item.Name對應的名稱 MemberExpression memberExpression = Expression.Field(parameter, typeof(T).GetField(item.Name)); //判斷是否存在 if (memberExpression != null) { MemberBinding member = Expression.Bind(item, memberExpression); list.Add(member); } } //初始化轉換後的型別,並且進行初始化賦值 MemberInitExpression memberInit = Expression.MemberInit(Expression.New(typeof(TOut)), list); //所有的準備工作已經完成準備生成lambda Expression<Func<T, TOut>> expression = Expression.Lambda<Func<T, TOut>>(memberInit, new ParameterExpression[] { parameter }); //生成委託存放到我們的泛型委託中 _Func = expression.Compile(); } public static TOut ToObj(T obj) { return _Func(obj); } }View Code
3、我針對上面的程式碼,進行了迴圈百萬次的測試
1. 直接硬編碼的形式:速度最快(0.126s)
2. 通過反射遍歷屬性的形式 (6.328s)
3. 利用序列化和反序列化的形式:將複製實體序列化字串,在把該字串反序列化被賦值實體(7.768s)
4. 字典快取+表示式目錄樹(Lambda的拼接程式碼瞭解即可) (2.134s)
5. 泛型快取+表示式目錄樹(Lambda的拼接程式碼瞭解即可) (0.663s)
四、總結
1、還有一些其他的用法我還沒有完全介紹,比如可以封裝一個自己的ORM,我們使用的ORM就是通過這個進行封裝的,授人以魚不如授人以漁。在最後的一個例項中我們使用到了很多細節的知識