【學習筆記】Expression表達式目錄樹

Expression表達式目錄樹:一個能拼裝能解析的數據結構,語法樹。前端

1、手動拼裝表達式目錄樹

示例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表達式聲明表達式目錄樹(快捷方式)這是硬編碼,硬編碼就沒法作到動態,可是咱們能夠藉助手動拼裝表達式目錄樹來動態生成硬編碼。

  二、表達式目錄樹能夠轉換成委託,手動拼裝表達式目錄樹就至關於能夠動態生成委託。

  三、表達式目錄樹是一個能拼裝能解析的數據結構,語法樹。手動拼裝成功後,後面就能夠根據實際須要去解析表達式目錄樹完成相應的操做。

2、表達式目錄樹應用場景

下面咱們來看個簡單的例子:

需求:一個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
相關文章
相關標籤/搜索