1. 程式人生 > >表示式樹練習實踐:C#值型別、引用型別、泛型、集合、呼叫函式

表示式樹練習實踐:C#值型別、引用型別、泛型、集合、呼叫函式

目錄

  • 表示式樹練習實踐:C#值型別、引用型別、泛型、集合、呼叫函式
    • 一,定義變數
    • 二,訪問變數/型別的屬性欄位和方法
      • 1. 訪問屬性
      • 2. 呼叫函式
    • 三,例項化引用型別
    • 四,例項化泛型型別於呼叫
    • 五,定義集合變數、初始化、新增元素

表示式樹練習實踐:C#值型別、引用型別、泛型、集合、呼叫函式

一,定義變數

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();