1. 程式人生 > >Expression表示式目錄樹

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));
            }
View Code

 

   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就是通過這個進行封裝的,授人以魚不如授人以漁。在最後的一個例項中我們使用到了很多細節的知識