c#表示式樹入門,看這個就夠了

Best_Hong發表於2024-11-01
題記:
由於反射需要大量的效能開銷,所以推薦用表示式樹或者emit,但是emit 如果不熟悉指令程式設計的話,使用成本很大,所以優先推薦表示式樹,但是網上給出來的文件 都非常的複雜,只是帶你使用,剛好我團隊的小夥伴也不太理解,所以我來整理一篇簡單入門版本的.
問: 反射有3種方式,一個是獲取值,一個是賦值,一個是呼叫方法 (如構造器 靜態方法 普通方法等),哪個才是效能元兇
先總結: 表示式樹 就是程式碼的拼接, 所以有以下三個區域
- 入參
- 程式碼塊
- 返回值
  1. 入參:
    Expression.Parameter()
  2. 程式碼塊
  • 常見的程式碼塊 加/減/乘/除/等於/獲取欄位值/獲取屬性值/呼叫方法/new物件
  • Expression.Property()獲取某物件欄位的值 test.M
  • Expression.Assign(), 給物件屬性或欄位賦值 test.M = ""
  • Expression.Multiply(),加減乘除 的方法
  • Expression.Call(), 呼叫某物件的方法,可以是靜態
  • Expression.Constant() 常值 比如 1 其實應該是 Constant("This is t2 name", typeof(String));
  • Expression.New()建立物件
  • Expression.NotEqual ELSE For迴圈等等,這個入門後 自己查資料即可
  • Expression.Convert(),強制性轉換 如 Expression.Convert()配合Call

  3. 出參 或者 返回值

  • 返回值 其實就是2個:
  • 無返回 值即 可執行的程式碼塊{},
  • 返回一個值 如 new物件,入參的物件,常量值,或者 List陣列
  • Expression.Block()
  • Expression.MemberInit()
  • Expression.ListInit()
  其實理論上來說,表示式樹沒有固定的返回的值,因為 表示式樹中不允許出現return,只是呼叫Compile()的推測,所以其實 返回值 只有一個Block**,其他全部是 程式碼塊,只不過我覺得這時候應該區分出來,這個也是所有的表示式樹在呼叫Lamda 的時候標準寫法都是 Block包裝,可以省略。
以下是例子的由簡單到複雜的彙總,程式碼在後面的框裡面
  1. Expression<Func<int, int, int>> func = (m, n) => m \* n;
  2. Expression<Func<T01, String>> func = (T01 m) => m.Name;
  3. Expression<Action<T01,String>> func = (T01 m, String str) => m.Name = str;
  4. Expression<Func<String, T01>> expression1 = (String str)=>new T01() { Name = str };
  5. Expression<Action<T01,T02>> action = (T01 t1, T02 t2)=> t2.Name = t1.Name;
  6. Fun<T01, T02> func = T01 t1 => new T02(){ Name = t1.Name}
//1.Expression<Func<int, int, int>> func = (m, n) => m \* n;
//入參
ParameterExpression t1 = Expression.Parameter(typeof(Int32), "t1");
ParameterExpression t2 = Expression.Parameter(typeof(Int32), "t2");
//程式碼塊
BinaryExpression multiply = Expression.Multiply(t1, t2);
//返回值Block
BlockExpression block = Expression.Block(multiply);
//編譯
Expression<Func<int, int, int>> expression =  Expression.Lambda<Func<int, int, int>>(block, t1, t2);
Func<int, int, int> func = expression.Compile();

  

//上面說了 表示式樹不允許出現顯示的程式碼塊 不允許出現return,所以注意以下錯誤的寫法

```c#
Expression<Func<int, int,int>> expression = (m,n)=> m * n;
//以下是錯誤形式
Expression<Action<int, int>> expression = (m,n)=> { };
Expression<Action<int, int>> expression = (m,n)=> {m + n };
Expression<Func<int, int,int>> expression = (m,n)=> { return m + n; };

  

public class T01
{
    public String Name { get; set; }
}

public class T02
{
    public String Name { get; set; }
}

  

//操作 Expression<Func<T01, String>> func = (T01 m) => m.Name;

//入參
ParameterExpression m = Expression.Parameter(typeof(T01), "m");
//程式碼塊
MemberExpression left = Expression.Property(m, typeof(T01).GetProperty(nameof(T01.Name)));
//錯誤的寫法是 
//Expression.Property(null, typeof(T01).GetProperty(nameof(T01.Name))),
//因為有些以為 我都 傳了typeof(Test) 肯定獲取到對應屬性的值,
//其實表示式樹是 編譯程式碼的,所以每一步都得實現,少一個不可。
//這裡的 是 m.Name 其實分為3步  m  .   Name,都必須有

//返回值
BlockExpression block1 = Expression.Block(left);
Expression<Func<T01, String>> expression = Expression.Lambda<Func<T01, String>>(block1, m);
Func<T01, String> func = expression.Compile();

  

 //Expression<Action<T01>> func = (T01 m) => m.Name = "new name";

//入參
ParameterExpression m = Expression.Parameter(typeof(T01), "m");
//程式碼塊
//拼接 m.Name
MemberExpression left = Expression.Property(m, typeof(T01).GetProperty(nameof(T01.Name)));
// m.Name = "new name"
BinaryExpression assignExpression1 = Expression.Assign(left, Expression.Constant("new name", typeof(String)));
//返回值 此處無
BlockExpression block = Expression.Block(assignExpression1);
Expression<Action<T01>> expression = Expression.Lambda<Action<T01>>(block, m);
Action<T01> func = expression.Compile();
T01 t01 = new T01() { Name = "old name" };
func(t01);
Console.WriteLine(t01.Name);//new name

  

// Expression<Action<T01,String>> func = (T01 m, String str) => m.Name = str;

//入參
ParameterExpression m = Expression.Parameter(typeof(T01), "m");
ParameterExpression str = Expression.Parameter(typeof(String), "str");
//程式碼塊
//拼接 m.Name
MemberExpression left = Expression.Property(m, typeof(T01).GetProperty(nameof(T01.Name)));
// m.Name = str
//Assign 賦值的
BinaryExpression assignExpression1 = Expression.Assign(left, left);
//返回值
BlockExpression block = Expression.Block(assignExpression1);
Expression<Action<T01, String>> expression = Expression.Lambda<Action<T01, String>>(block, m, str);
Action<T01, String> func = expression.Compile();
T01 t01 = new T01() { Name = "old name" };
func(t01,"new name");
Console.WriteLine(t01.Name);//new name

  

//Expression<Func<String, T01>> expression1 = (String str)=>new T01() { Name = str };
//入參
ParameterExpression str = Expression.Parameter(typeof(String), "str");
//程式碼塊
MemberAssignment bind = Expression.Bind(typeof(T01).GetProperty(nameof(T01.Name)), str);
//返回值
MemberInitExpression newT01 = Expression.MemberInit(Expression.New(typeof(T01)), bind);
BlockExpression block = Expression.Block(newT01);
Expression<Func<String, T01>> expression = Expression.Lambda<Func<String, T01>>(block,str);
Func<String, T01> func = expression.Compile();
T01 t01 = new T01() { Name = "old name" };
t01 = func("new name");
Console.WriteLine(t01.Name);//new name

  

這裡有個問題是 new T01(){};
4.1. 這裡是物件初始化模組,而不是 var t01 = new T02();t02.Name="";
所以一定得區分,**而物件初始化模組 用得 Bind() 關係處理的,需要和Assign()區分開來
4.2. 注意Expression.MemberInit(Expression.New(typeof(T01))) == Expression.New(typeof(T01)), 如果沒有繫結關係 其實可以去掉 MemberInit()的寫法,就和 可以去掉所有的Block一樣,以及 Expression.Property()只傳遞一個 name 值一樣,其實裡面做了簡化
Expression<Action<T01,T02>> action = (T01 t1, T02 t2)=> t2.Name = t1.Name;

//入參
ParameterExpression t1 = Expression.Parameter(typeof(T01), "t1");
ParameterExpression t2 = Expression.Parameter(typeof(T02), "t2");
//程式碼塊
//第二步,t2.Name 拆解為 引數t2, 屬性符號 . 屬性名稱 Name
var member1 = Expression.Property(t2, nameof(T02.Name));
//拆解 t1.Name
var member2 = Expression.Property(t1, nameof(T01.Name));
//將member2賦值給member1
var member = Expression.Assign(member1, member2);

Expression block = Expression.Block(member);
Expression<Action<T01, T02>> expression = Expression.Lambda<Action<T01, T02>>(block, t1, t2);
var func = expression.Compile();
T01 t01 = new T01() { Name = "This is t1 name" };
T02 t02 = new T02() { Name = "This is t2 name" };

func(t01, t02);
Console.WriteLine(t02.Name); //輸出的是 This is t1 name; 

  

Fun<T01, T02> func = T01 t1 => new T02(){ Name = t1.Name}的轉換器

//定義入參
ParameterExpression t1 = Expression.Parameter(typeof(T01), "t1");
//程式碼塊
//拆解 t1.Name
var member2 = Expression.Property(t1, nameof(T01.Name));
//因為這裡是new,所以 這裡實際是 繫結 T02.Name與 T01.Name的關係
var member = Expression.Bind(typeof(T02).GetProperty(nameof(T02.Name)), member2);
//當建立物件的時候,依據繫結關係 new T02()
//當然也可以 var member = Expression.Bind(typeof(T02).GetProperty(nameof(T02.Name)), Expression.Constant("xxxxx"));
//如果沒有 member 那麼可以直接 New() 不需要MemberInit()
Expression block = Expression.Block(Expression.MemberInit(Expression.New(typeof(T02)), member));
Expression<Func<T01, T02>> expression = Expression.Lambda<Func<T01, T02>>(block, t1);
var func = expression.Compile();
T01 t01 = new T01() { Name = "This is t1 name" };
T02 t02 = new T02() { Name = "This is t2 name" };

t02 = func(t01);
Console.WriteLine(t02.Name);//這裡輸出的  "This is t1 name"

  

**注意 這裡是 Bind() 而不是 Assign(), 那這裡可以用 Assign()嗎?**

答案是不行.因為 Assign 的寫法就是 T02 t02 = new T02(); t02.Name = t01.Name;return t02; 這個原因上面解釋過, 這裡在強調下, 表示式樹 不允許程式碼塊,不允許return,只允許單操作.

最後 在強調下,表示式樹 其實 就是 入參 出參 返回的程式碼拼接,然後執行成 我們想要的程式碼,雖然看起來複雜,但是實際就是一步步來的.

留個作業,

1. 將上面的 Fun<T01, T02> func 改成通用的寫法. 下面直接貼了,我在本地沒除錯,大家可以自己除錯下
2. 如何實現 Expression.Call 的執行方式
3. 將註釋上面的功能都實現一遍
c#表示式樹入門,看這個就夠了
public class MapHelper
{
    private static Dictionary<String, Object> dicFunc = new Dictionary<string, Object>();
            /// 沒有判斷泛型 IsGenericType
        /// 沒操作 屬性 不同型別 轉不同型別的操作(重新呼叫Map類即可), ps => IEnumerable 轉換需要呼叫 Expression.Call( IEnumerable.Select(Map) )來處理轉換條件
        /// 沒操作 IsNullable 轉為值型別資料轉換  需要 Expression.PropertyType("value")來強制性賦值到 基礎型別
        /// 沒操作 特性轉換
        /// 可以理解成只做 型別相同欄位 的  值賦值轉換
    public static void Map<T1, T2>(T1 source, T2 destination) where T1 : class, new() where T2 : class, new()
    {
        Type tIn = typeof(T1);
        Type tResult = typeof(T2);
        String key = $"{tIn}_Convert_{tResult}";
        if (!dicFunc.TryGetValue(key, out var func))
        {
            lock (dicFunc)
            {
                if (!dicFunc.TryGetValue(key, out func))
                {
                    ParameterExpression t1 = Expression.Parameter(tIn);
                    ParameterExpression t2 = Expression.Parameter(tResult);
                    List<Expression> list = new List<Expression>();
                    Dictionary<PropertyInfo, PropertyInfo> map = new Dictionary<PropertyInfo, PropertyInfo>();
                    foreach (var item in tResult.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite && x.CanRead))
                    {
                        String name = item.Name;
                        var p1 = tIn.GetProperty(name);
                        if (p1 == null) continue;
                        if (!p1.PropertyType.IsPublic || !p1.CanWrite || !p1.CanRead || p1.PropertyType != item.PropertyType) continue;
                        MemberExpression mIn = Expression.Property(t1, p1);
                        MemberExpression mResult = Expression.Property(t2, item);
                        var memberBinding = Expression.Assign(mResult, mIn);
                        list.Add(memberBinding);
                    }
                    Expression<Action<T1, T2>> lambda = Expression.Lambda<Action<T1, T2>>(Expression.Block(list), t1, t2);
                    var action = lambda.Compile();
                    dicFunc[key] = action;
                    func = action;
                }
            }
        }
        ((Action<T1, T2>)func)(source, destination);
    }
    public static TResult Map<TIn, TResult>(TIn source) where TIn : class, new() where TResult : class, new()
    {
        Type tIn = typeof(TIn);
        Type tResult = typeof(TResult);
        String key = $"{tIn}_New_{tResult}";
        if (!dicFunc.TryGetValue(key, out var func))
        {
            lock (dicFunc)
            {
                if (!dicFunc.TryGetValue(key, out func))
                {
                    ParameterExpression t1 = Expression.Parameter(tIn);

                    List<MemberBinding> list = new List<MemberBinding>();
                    foreach (var item in tResult.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite && x.CanRead))
                    {
                        String name = item.Name;
                        var p1 = tIn.GetProperty(name);
                        if (p1 == null) continue;
                        if (!p1.PropertyType.IsPublic || !p1.CanWrite || !p1.CanRead || p1.PropertyType != item.PropertyType) continue;
                        MemberExpression property1 = Expression.Property(t1, p1);
                        var memberBinding = Expression.Bind(item, property1);
                        list.Add(memberBinding);
                    }
                    Expression<Func<TIn, TResult>> lambda;
                    if (list.Count == 0)
                    {
                        lambda = Expression.Lambda<Func<TIn, TResult>>(Expression.Block(Expression.Constant(null, tResult)), t1);
                    }
                    else
                    {
                        lambda = Expression.Lambda<Func<TIn, TResult>>(Expression.MemberInit(Expression.New(tResult), list), t1);
                    }
                    var action = lambda.Compile();
                    dicFunc[key] = action;
                    func = action;
                }
            }
        }
        return ((Func<TIn, TResult>)func)(source);
    }
}
View Code

相關文章