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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM