C#使用表達式樹動態調用方法並實現99乘法表

咱們在使用C#編程的時候,常常使用反射來動態調用方法,但有時候須要動態的生成方法,下面介紹使用表達式樹的方式來自動生成方法,並調用。編程

首先須要說明什麼是表達式,熟悉Linq的程序猿都用過相似於下面的代碼:t=>t.Length<=25;數組

在C#中=>表明這是一個Lambda表達式,它用來對數組進行查詢,統計,排序,去重的功能很是有用。而表達式樹就是經過動態的建立一個Lambda的方式來實現相關的功能。app

下面是一個相似於JS中apply函數的示例。函數

使用表達式樹,必定要引用System.Linq.Expressions;其中的Expression類有不少的方法能夠定義一個方法所須要的全部東西。oop

public class CommonTest調試

    {blog

        public object TestMethodCall(int age, string name)排序

        {索引

            Console.WriteLine($"{name}'s Age is {age}");ip

            return true;

        }

        public object TestExpression(MethodInfo method, object[] parameters, CommonTest instance)

        {

            //最終生成的表達式樣式(m,p)=>{return (object)m.method(p);}

            //定義兩個參數表達式

            ParameterExpression mParameter = Expression.Parameter(typeof(CommonTest), "m");//定義一個名稱爲m的參數

            ParameterExpression pParameter = Expression.Parameter(typeof(object[]), "p");//定義一個名稱爲p的參數

 

            ParameterInfo[] tParameter = method.GetParameters();//獲取到方法的全部參數

            Expression[] rParameter = new Expression[tParameter.Length];//定義一個與方法參數長度相同的表達式容器,由於在調用方法的時候須要使用的是表達式,不是直接使用方法的參數列表

            for (int i = 0; i < rParameter.Length; i++)

            {

                BinaryExpression pExpression = Expression.ArrayIndex(pParameter, Expression.Constant(i));//從方法中獲取到對應索引的參數

                UnaryExpression uExpression = Expression.Convert(pExpression, tParameter[i].ParameterType);//將此參數的類型轉化成實際參數的類型

                rParameter[i] = uExpression;//將對應的參數表達式添加到參數表達式容器中

            }

 

            MethodCallExpression mcExpression = Expression.Call(mParameter,method, rParameter);//調用方法,由於是實例方法因此第一個參數必須是m,若是是靜態方法,那麼第一個參數就應該是null

            UnaryExpression reExpression = Expression.Convert(mcExpression, typeof(object));//將結果轉換成object,由於要動態的調用全部的方法,因此返回值必須是object,若是是無返回值的方法,則不須要這一步

            return Expression.Lambda<Func<CommonTest, object[], object>>(reExpression, mParameter, pParameter).Compile()(instance, parameters);//將方法編譯成一個Func委託,並執行他

        }

}

以上的代碼的調用方式以下:

  CommonTest ct = new CommonTest();

            MethodInfo mi = typeof(CommonTest).GetMethod("TestMethodCall");

            var r = ct.TestExpression(mi, new object[] { 25, "SC" }, ct);

此方法也是C#MVC中調用控制器中的Action的原理代碼,其最大的做用是無論目標Action擁有多少個參數,最後調用都只須要一個object[]的參數,避免了直接使用反射調用,可是不肯定參數個數的困難。

使用Expression不只能夠實習以上的相似於MVC原理的代碼,也能夠對錶達式樹進行解析,能夠實現ORM底層的Sql構成,但此出再也不進行詳解,有興趣能夠百度查詢表達式樹的解析。

表達式樹實現的缺點是功能實現複雜,調試困難,建議在實現以前先將須要實現的功能使用C#語法編寫出來,再按照對應的格式經過表達式樹來實現,這樣相對簡單一些。

下面是使用表達式輸出一個99乘法表。

如下是實現的結果

 

 

 

首先是經過正常的方式來實現,代碼以下:

for (int i = 1; i <= 9; i++)

            {

                for (int j = 1; j <= i; j++)

                {

                    int total = i * j;

                    Console.Write($"{i} * {j} = {total}\t");

                }

                Console.WriteLine();

            }

            Console.ReadKey();

下面是使用表達式樹實現相同功能的代碼:

/// <summary>

        /// 使用表達式樹實現99乘法表

        /// </summary>

        public void TestMultiple()

        {

            LabelTarget labOut = Expression.Label();//用於跳出外部循環的標誌

            LabelTarget labIn = Expression.Label();//用於跳出內部循環的標誌

 

            ParameterExpression iParameter = Expression.Parameter(typeof(int), "i");//定義外部循環的變量,相似於int i;

            ParameterExpression jParameter = Expression.Parameter(typeof(int), "j");//定義內部循環的變量,相似於int j;

            ParameterExpression rParameter = Expression.Parameter(typeof(int), "result");//定義用於保存i*j的結果的變量

 

            MethodInfo writeString = typeof(Console).GetMethod("Write", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(string) }, null);//獲取Write方法

            MethodInfo writeInt = typeof(Console).GetMethod("Write", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(int) }, null);//獲取Write方法

 

            Expression expResult = Expression.Block(

                new[] { iParameter, jParameter, rParameter },

                Expression.Assign(iParameter, Expression.Constant(1)),//爲i賦初始值,相似於i=1;

                Expression.Loop(Expression.Block(//此處開始外部循環,表達式只能實現while循環,不能實現for循環

                Expression.IfThenElse(Expression.LessThanOrEqual(iParameter, Expression.Constant(9)),//定義執行的條件,相似於if(i<=9){

                                                                                                     //外部if爲真的時候執行如下代碼

                  Expression.Block(

                    Expression.Assign(jParameter, Expression.Constant(1)),//爲j賦初始值,相似於j=1;

                    Expression.Loop(Expression.Block(//此處開始內部循環

                        Expression.IfThenElse(Expression.LessThanOrEqual(jParameter, iParameter),//定義執行的條件,相似於if(j<=i){

                                                                                                 //內部if爲真的時候執行如下代碼

                        Expression.Block(

                            Expression.Assign(rParameter, Expression.Multiply(iParameter, jParameter)),//此處用於計算i*j的結果,並進行賦值,相似於result=i*j

                                                                                                       //打印出結果,相似於Console.Write("i * j = " + result + "\t")

                            Expression.Call(null, writeInt, jParameter),

                            Expression.Call(null, writeString, Expression.Constant(" * ")),

                            Expression.Call(null, writeInt, iParameter),

                            Expression.Call(null, writeString, Expression.Constant(" = ")),

                            Expression.Call(null, writeInt, rParameter),

                            Expression.Call(null, writeString, Expression.Constant("\t")),

                            Expression.PostIncrementAssign(jParameter)//j自增加,相似於j++

                            ),

                            //內部if爲假的時候執行如下代碼

                            Expression.Break(labIn))//此處跳出內部循環)

                        ), labIn),

                    Expression.Block(

                         Expression.Call(null, writeString, Expression.Constant("\n")),//此處打印換行符,相似於Console.WriteLine();

                         Expression.PostIncrementAssign(iParameter))//i自增加,相似於i++

                         )

                        //外部if爲假的時候執行如下代碼

                        , Expression.Break(labOut))//此處跳出外部循環

                ), labOut));

            Expression.Lambda<Action>(expResult).Compile()();

        }

以上兩段代碼實現的效果相同,能夠看出表達式樹實現相同的功能的複雜程度遠遠超出普通的方式,正常10行的代碼,表達式樹整整用了42行代碼才實現。

相關文章
相關標籤/搜索