Expression表達式目錄樹:一個能拼裝能解析的數據結構,語法樹。前端
示例1:數據庫
/// <summary> /// 展現表達式樹,協助用的 /// 編譯lambda--反編譯C#--獲得原始聲明方式 /// </summary> public class ExpressionTreeVisualizer { public static void Show() { //lambda表達式聲明表達式目錄樹(快捷方式),是一個數據結構 //不能有語句體,只能是一行,不能有大括號 Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2; //int iResult = expression.Compile().Invoke(23, 34); } }
上面的這種方式是使用lambda表達式快捷的聲明表達式目錄樹,那咱們怎麼手動拼裝這樣子的一個表達式目錄樹呢?express
此處咱們藉助反編譯工具ILSpy,編譯lambda--反編譯C#--獲得原始聲明方式緩存
反編譯示例1後獲得的結果以下:數據結構
public static void Show() { ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n"); Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(Expression.Add(Expression.Add( Expression.Add(Expression.Multiply(parameterExpression, parameterExpression2), parameterExpression), parameterExpression2), Expression.Constant(2, typeof(int))), new ParameterExpression[] { parameterExpression, parameterExpression2 }); }
而後根據反編譯後獲得的結果做爲參考,再手動拼裝表達式目錄樹,以下所示:app
{ //Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2; //int iResult = expression.Compile().Invoke(23, 34); ParameterExpression m = Expression.Parameter(typeof(int), "m"); ParameterExpression n = Expression.Parameter(typeof(int), "n"); var constant = Expression.Constant(2); var mutiply = Expression.Multiply(m, n); var plus1 = Expression.Add(mutiply, m); var plus2 = Expression.Add(plus1, n); var plus3 = Expression.Add(plus2, constant); Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(plus3, new ParameterExpression[] { m, n }); int iResult = expression.Compile().Invoke(23, 34); }
運行後就能夠發現咱們動態的拼裝出來了:函數
示例2:工具
/// <summary> /// 展現表達式樹,協助用的 /// 編譯lambda--反編譯C#--獲得原始聲明方式 /// </summary> public class ExpressionTreeVisualizer { public static void Show() { //lambda表達式聲明表達式目錄樹(快捷方式),是一個數據結構 //不能有語句體,只能是一行,不能有大括號 //Expression<Func<int, int, int>> expression = (m, n) => m * n + m + n + 2; //int iResult = expression.Compile().Invoke(23, 34); Expression<Func<People, bool>> lambda = x => x.Id.ToString().Equals("5"); } }
反編譯示例2後獲得的結果以下:性能
public static void Show() { ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "x"); Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(Expression.Call(Expression.Call(Expression.Field(parameterExpression, FieldInfo.GetFieldFromHandle(ldtoken(Id))), (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(ToString())), new Expression[0]), (MethodInfo)MethodBase.GetMethodFromHandle(ldtoken(Equals())), new Expression[] { Expression.Constant("5", typeof(string)) }), new ParameterExpression[] { parameterExpression }); }
同理咱們根據反編譯後獲得的結果做爲參考,再手動拼裝表達式目錄樹,以下所示:測試
{ //Expression<Func<People, bool>> lambda = x => x.Id.ToString().Equals("5"); ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "x"); var constantExp = Expression.Constant("5"); FieldInfo field = typeof(People).GetField("Id"); var fieldExp = Expression.Field(parameterExpression, field); var toString = typeof(int).GetMethod("ToString", new Type[] { }); var toStringExp = Expression.Call(fieldExp, toString, new Expression[0]); var equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) }); var equalsExp = Expression.Call(toStringExp, equals, new Expression[] { constantExp }); Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(equalsExp, new ParameterExpression[] { parameterExpression }); bool bResult = expression.Compile()(new People() { Id = 5, Name = "浪子天涯", Age = 20 }); }
運行結果以下:
手動拼裝表達式目錄樹的目的:
一、使用lambda表達式聲明表達式目錄樹(快捷方式)這是硬編碼,硬編碼就沒法作到動態,可是咱們能夠藉助手動拼裝表達式目錄樹來動態生成硬編碼。
二、表達式目錄樹能夠轉換成委託,手動拼裝表達式目錄樹就至關於能夠動態生成委託。
三、表達式目錄樹是一個能拼裝能解析的數據結構,語法樹。手動拼裝成功後,後面就能夠根據實際須要去解析表達式目錄樹完成相應的操做。
下面咱們來看個簡單的例子:
需求:一個Web應用經過前端收集用戶的輸入成爲Dto,而後將Dto轉換成領域模型並持久化到數據庫中。也就是說在實際的軟件開發項目中,咱們的「業務邏輯」經常須要咱們對一樣的數據進行各類變換。
解決這個問題咱們的常規作法是:使用AutoMapper來完成Dto與Model之間的實體映射。
此處引起一個思考:若是讓咱們本身來完成實體映射,那麼咱們有哪些解決思路呢?
準備測試用的實體和Dto:
/// <summary> /// 實體 /// </summary> public class People { public int Age { get; set; } public string Name { get; set; } public int Id; } /// <summary> /// Dto /// </summary> public class DtoPeople { public int Age { get; set; } public string Name { get; set; } public int Id; }
第1種解決思路:硬編碼
People people = new People() { Id = 1, Name = "測試", Age = 22 }; for (int i = 0; i < 1_000_000; i++) { DtoPeople dtoPeople = new DtoPeople() { Id = people.Id, Name = people.Name, Age = people.Age }; }
第2種解決思路:反射
/// <summary> /// 反射映射 /// </summary> public class ReflectionMapper { /// <summary> /// 實體轉換 /// </summary> /// <typeparam name="T">傳入類型</typeparam> /// <typeparam name="TResult">返回值類型</typeparam> /// <param name="tIn">傳入參數</param> /// <returns>轉換好的實體</returns> public static TResult Trans<T, TResult>(T tIn) { TResult tOut = Activator.CreateInstance<TResult>(); foreach (var itemOut in tOut.GetType().GetProperties()) { var propIn = tIn.GetType().GetProperty(itemOut.Name); itemOut.SetValue(tOut, propIn.GetValue(tIn)); } foreach (var itemOut in tOut.GetType().GetFields()) { var fieldIn = tIn.GetType().GetField(itemOut.Name); itemOut.SetValue(tOut, fieldIn.GetValue(tIn)); } return tOut; } }
第3種解決思路:序列化反序列化
/// <summary> /// 使用第三方序列化反序列化工具 /// </summary> public class SerializeMapper { /// <summary> /// 實體轉換 /// </summary> public static TResult Trans<T, TResult>(T tIn) { return JsonConvert.DeserializeObject<TResult>(JsonConvert.SerializeObject(tIn)); } }
第4種解決思路:表達式目錄樹 + 字典緩存
/// <summary> /// 生成表達式目錄樹 字典緩存 /// </summary> public class ExpressionMapper { /// <summary> /// 字典緩存--hash分佈 /// </summary> private static Dictionary<string, object> _dic = new Dictionary<string, object>(); /// <summary> /// 實體轉換 /// </summary> public static TResult Trans<T, TResult>(T tIn) { string key = string.Format("funckey_{0}_{1}", typeof(T).FullName, typeof(TResult).FullName); if (!_dic.ContainsKey(key)) { ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TResult).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } foreach (var item in typeof(TResult).GetFields()) { MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray()); Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); Func<T, TResult> func = lambda.Compile(); //調用Compile方法將表達式轉換成委託 _dic[key] = func; //拼裝是一次性的 } return ((Func<T, TResult>)_dic[key]).Invoke(tIn); } }
第5種解決思路:表達式目錄樹 + 泛型緩存(泛型緩存特色:爲不一樣類型的組合去緩存一個結果。)
/// <summary> /// 生成表達式目錄樹 泛型緩存 /// </summary> /// <typeparam name="T">傳入參數類型</typeparam> /// <typeparam name="TResult">返回值類型</typeparam> public class ExpressionGenericMapper<T, TResult> { /// <summary> /// 泛型緩存 /// </summary> private static Func<T, TResult> _func = null; /// <summary> /// 靜態構造函數(只會被調用一次) /// </summary> static ExpressionGenericMapper() { ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TResult).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } foreach (var item in typeof(TResult).GetFields()) { MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray()); Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); _func = lambda.Compile();//拼裝是一次性的 } /// <summary> /// 實體轉換 /// </summary> public static TResult Trans(T t) { return _func(t); } }
下面咱們來測試下這5種方案的性能:
/// <summary> /// 性能測試 /// </summary> public static void MapperTest() { People people = new People() { Id = 1, Name = "測試", Age = 22 }; long common = 0; long generic = 0; long cache = 0; long reflection = 0; long serialize = 0; //硬編碼 { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { DtoPeople dtoPeople = new DtoPeople() { Id = people.Id, Name = people.Name, Age = people.Age }; } watch.Stop(); common = watch.ElapsedMilliseconds; } //反射 { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { DtoPeople dtoPeople = ReflectionMapper.Trans<People, DtoPeople>(people); } watch.Stop(); reflection = watch.ElapsedMilliseconds; } //序列化反序列化 { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { DtoPeople dtoPeople = SerializeMapper.Trans<People, DtoPeople>(people); } watch.Stop(); serialize = watch.ElapsedMilliseconds; } //表達式目錄樹 + 字典緩存 { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { DtoPeople dtoPeople = ExpressionMapper.Trans<People, DtoPeople>(people); } watch.Stop(); cache = watch.ElapsedMilliseconds; } //表達式目錄樹 + 泛型緩存 { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { DtoPeople dtoPeople = ExpressionGenericMapper<People, DtoPeople>.Trans(people); } watch.Stop(); generic = watch.ElapsedMilliseconds; } Console.WriteLine($"common = { common} ms"); Console.WriteLine($"reflection = { reflection} ms"); Console.WriteLine($"serialize = { serialize} ms"); Console.WriteLine($"cache = { cache} ms"); Console.WriteLine($"generic = { generic} ms"); //性能比AutoMapper還要高 }
來看下運行結果:
從運行結果能夠看出硬編碼的性能是最高的,其次就是咱們的表達式目錄樹 + 泛型緩存,這2個的性能幾乎是同一個數量級的。
小結:
一、既須要考慮動態(通用),又要保證性能(硬編碼)---動態生成硬編碼---表達式目錄樹拼裝(動態生成委託)(獲得的就是硬編碼)。
二、若是使用反射來實現某個功能時性能不高,能夠考慮使用表達式目錄樹拼裝來實現這個功能(動態生成委託)。
三、不能將具備語句體的Lambda表達式轉換爲表達式目錄樹。(即只能是一行且不能有大括號)
反編譯工具ILSpy:
連接:https://pan.baidu.com/s/1ngh2AK9HrKLVLi8ng8vcrQ 提取碼:c9x5