表達式樹練習實踐:C#值類型、引用類型、泛型、集合、調用函數

  • 2019 年 10 月 3 日
  • 筆記

表達式樹練習實踐:C#值類型、引用類型、泛型、集合、調用函數

img

一,定義變量

C# 表達式樹中,定義一個變量,使用 ParameterExpression

創建變量結點的方法有兩種,

Expression.Parameter()  Expression.Variable()  // 另外,定義一個常量可以使用 Expression.Constant()。

兩種方式都是生成 ParameterExpression 類型 Parameter()Variable() 都具有兩個重載。他們創建一個 ParameterExpression節點,該節點可用於標識表達式樹中的參數或變量。

對於使用定義:

Expression.Variable 用於在塊內聲明局部變量。

Expression.Parameter用於聲明輸入值的參數。

先看第一種

        public static ParameterExpression Parameter(Type type)          {              return Parameter(type, name: null);          }                    public static ParameterExpression Variable(Type type)          {              return Variable(type, name: null);          }

從代碼來看,沒有區別。

再看看具有兩個參數的重載

        public static ParameterExpression Parameter(Type type, string name)          {              Validate(type, allowByRef: true);              bool byref = type.IsByRef;              if (byref)              {                  type = type.GetElementType();              }                return ParameterExpression.Make(type, name, byref);          }
        public static ParameterExpression Variable(Type type, string name)          {              Validate(type, allowByRef: false);              return ParameterExpression.Make(type, name, isByRef: false);          }

如你所見,兩者只有一個 allowByRef 出現了區別,Paramter 允許 Ref, Variable 不允許。

筆者在官方文檔和其他作者文章上,都沒有找到具體區別是啥,去 stackoverflow 搜索和查看源代碼後,確定他們的區別在於 Variable 不能使用 ref 類型。

從字面意思來看,聲明一個變量,應該用Expression.Variable, 函數的傳入參數應該使用Expression.Parameter

無論值類型還是引用類型,都是這樣子定義。

二,訪問變量/類型的屬性字段和方法

訪問變量或類型的屬性,使用

Expression.Property()

訪問變量/類型的屬性或字段,使用

Expression.PropertyOrField()

訪問變量或類型的方法,使用

Expression.Call()

訪問屬性字段和方法

Expression.MakeMemberAccess

他們都返回一個 MemberExpression類型。

使用上,根據實例化/不實例化,有個小區別,上面說了變量或類型。

意思是,已經定義的值類型或實例化的引用類型,是變量;

類型,就是指引用類型,不需要實例化的靜態類型或者靜態屬性字段/方法。

上面的解釋不太嚴謹,下面示例會慢慢解釋。

1. 訪問屬性

使用 Expression.Property()Expression.PropertyOrField()調用屬性。

調用靜態類型屬性

Console 是一個靜態類型,Console.Title 可以獲取編譯器程序的實際位置。

            Console.WriteLine(Console.Title);

使用表達式樹表達如下

            MemberExpression member = Expression.Property(null, typeof(Console).GetProperty("Title"));              Expression<Func<string>> lambda = Expression.Lambda<Func<string>>(member);                string result = lambda.Compile()();              Console.WriteLine(result);                Console.ReadKey();

因為調用的是靜態類型的屬性,所以第一個參數為空。

第二個參數是一個 PropertyInfo 類型。

調用實例屬性/字段

C#代碼如下

            List<int> a = new List<int>() { 1, 2, 3 };              int result = a.Count;              Console.WriteLine(result);              Console.ReadKey();

在表達式樹,調用實例的屬性

            ParameterExpression a = Expression.Parameter(typeof(List<int>), "a");              MemberExpression member = Expression.Property(a, "Count");                Expression<Func<List<int>, int>> lambda = Expression.Lambda<Func<List<int>, int>>(member, a);              int result = lambda.Compile()(new List<int> { 1, 2, 3 });              Console.WriteLine(result);                Console.ReadKey();

除了 Expression.Property() ,其他的方式請自行測試,這裡不再贅述。

2. 調用函數

使用 Expression.Call() 可以調用一個靜態類型的函數或者實例的函數。

調用靜態類型的函數

以 Console 為例,調用 WriteLine() 方法

            Console.WriteLine("調用WriteLine方法");                MethodCallExpression method = Expression.Call(                  null,                  typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),                  Expression.Constant("調用WriteLine方法"));                Expression<Action> lambda = Expression.Lambda<Action>(method);              lambda.Compile()();              Console.ReadKey();

Expression.Call() 的重載方法比較多,常用的重載方法是

public static MethodCallExpression Call(Expression instance, MethodInfo method, params Expression[] arguments)

因為要調用靜態類型的函數,所以第一個 instance 為空(instance英文意思是實例)。

第二個 method 是要調用的重載方法。

最後一個 arguments 是傳入的參數。

調用實例的函數

寫一個類

    public class Test      {          public void Print(string info)          {              Console.WriteLine(info);          }      }

調用實例的 Printf() 方法

            Test test = new Test();              test.Print("打印出來");              Console.ReadKey();

表達式表達如下

            ParameterExpression a = Expression.Variable(typeof(Test), "test");                MethodCallExpression method = Expression.Call(                  a,                  typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),                  Expression.Constant("打印出來")                  );                Expression<Action<Test>> lambda = Expression.Lambda<Action<Test>>(method,a);              lambda.Compile()(new Test());              Console.ReadKey();

注意的是,Expression.Variable(typeof(Test), "test"); 僅定義了一個變量,還沒有初始化/賦值。對於引用類型來說,需要實例化。

上面的方式,是通過外界實例化傳入裏面的,後面會說如何在表達式內實例化。

三,實例化引用類型

引用類型的實例化,使用 new ,然後選擇調用合適的構造函數、設置屬性的值。

那麼,根據上面的步驟,我們分開討論。

new

使用 Expression.New()來調用一個類型的構造函數。

他有五個重載,有兩種常用重載:

 public static NewExpression New(ConstructorInfo constructor);   public static NewExpression New(Type type);

依然使用上面的 Test 類型

            NewExpression newA = Expression.New(typeof(Test));

默認沒有參數的構造函數,或者只有一個構造函數,像上面這樣調用。

如果像指定一個構造函數,可以

            NewExpression newA = Expression.New(typeof(Test).GetConstructor(xxxxxx));

這裡就不詳細說了。

給屬性賦值

實例化一個構造函數的同時,可以給屬性賦值。

        public static MemberInitExpression MemberInit(NewExpression newExpression, IEnumerable<MemberBinding> bindings);            public static MemberInitExpression MemberInit(NewExpression newExpression, params MemberBinding[] bindings);

兩種重載是一樣的。

我們將 Test 類改成

    public class Test      {          public int sample { get; set; }          public void Print(string info)          {              Console.WriteLine(info);          }      }

然後

            var binding = Expression.Bind(                  typeof(Test).GetMember("sample")[0],                  Expression.Constant(10)              );  

創建引用類型

Expression.MemberInit()

表示調用構造函數並初始化新對象的一個或多個成員。

如果實例化一個類,可以使用

            NewExpression newA = Expression.New(typeof(Test));              MemberInitExpression test = Expression.MemberInit(newA,                  new List<MemberBinding>() { }                  );

如果要在實例化時給成員賦值

            NewExpression newA = Expression.New(typeof(Test));                // 給 Test 類型的一個成員賦值              var binding = Expression.Bind(                  typeof(Test).GetMember("sample")[0],Expression.Constant(10));                MemberInitExpression test = Expression.MemberInit(newA,                  new List<MemberBinding>() { binding}                  );

示例

實例化一個類型,調用構造函數、給成員賦值,示例代碼如下

            // 調用構造函數              NewExpression newA = Expression.New(typeof(Test));                // 給 Test 類型的一個成員賦值              var binding = Expression.Bind(                  typeof(Test).GetMember("sample")[0], Expression.Constant(10));                // 實例化一個類型              MemberInitExpression test = Expression.MemberInit(newA,                  new List<MemberBinding>() { binding }                  );                // 調用方法              MethodCallExpression method1 = Expression.Call(                  test,                  typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),                  Expression.Constant("打印出來")                  );                // 調用屬性              MemberExpression method2 = Expression.Property(test, "sample");                Expression<Action> lambda1 = Expression.Lambda<Action>(method1);              lambda1.Compile()();                Expression<Func<int>> lambda2 = Expression.Lambda<Func<int>>(method2);              int sample = lambda2.Compile()();              Console.WriteLine(sample);                Console.ReadKey();

四,實例化泛型類型於調用

將 Test 類,改成這樣

    public class Test<T>      {          public void Print<T>(T info)          {              Console.WriteLine(info);          }      }

Test 類已經是一個泛型類,表達式實例化示例

        static void Main(string[] args)          {              RunExpression<string>();              Console.ReadKey();          }          public static void RunExpression<T>()          {              // 調用構造函數              NewExpression newA = Expression.New(typeof(Test<T>));                // 實例化一個類型              MemberInitExpression test = Expression.MemberInit(newA,                  new List<MemberBinding>() { }                  );                // 調用方法              MethodCallExpression method = Expression.Call(                  test,                  typeof(Test<T>).GetMethod("Print").MakeGenericMethod(new Type[] { typeof(T) }),                  Expression.Constant("打印出來")                  );                Expression<Action> lambda1 = Expression.Lambda<Action>(method);              lambda1.Compile()();                Console.ReadKey();          }

五,定義集合變量、初始化、添加元素

集合類型使用 ListInitExpression表示。

創建集合類型,需要使用到

ElementInit 表示 IEnumerable集合的單個元素的初始值設定項。

ListInit 初始化一個集合。

C# 中,集合都實現了 IEnumerable,集合都具有 Add 扥方法或屬性。

使用 C# 初始化一個集合併且添加元素,可以這樣

            List<string> list = new List<string>()              {                  "a",                  "b"              };              list.Add("666");

而在表達式樹裏面,是通過 ElementInit 調用 Add 方法初始化/添加元素的。

示例

            MethodInfo listAdd = typeof(List<string>).GetMethod("Add");                /*               * new List<string>()               * {               *     "a",               *     "b"               * };               */              ElementInit add1 = Expression.ElementInit(                  listAdd,                  Expression.Constant("a"),                  Expression.Constant("b")                  );              // Add("666")              ElementInit add2 = Expression.ElementInit(listAdd, Expression.Constant("666"));

示例

            MethodInfo listAdd = typeof(List<string>).GetMethod("Add");                ElementInit add1 = Expression.ElementInit(listAdd, Expression.Constant("a"));              ElementInit add2 = Expression.ElementInit(listAdd, Expression.Constant("b"));              ElementInit add3 = Expression.ElementInit(listAdd, Expression.Constant("666"));                NewExpression list = Expression.New(typeof(List<string>));                // 初始化值              ListInitExpression setList = Expression.ListInit(                  list,                  add1,                  add2,                  add3                  );              // 沒啥執行的,就這樣看看輸出的信息              Console.WriteLine(setList.ToString());                MemberExpression member = Expression.Property(setList, "Count");                Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(member);              int result = lambda.Compile()();              Console.WriteLine(result);                Console.ReadKey();