我們在使用C#編程的時候,經常使用反射來動態調用方法,但有時候需要動態的生成方法,下麵介紹使用表達式樹的方式來自動生成方法,並調用。 首先需要說明什麼是表達式,熟悉Linq的程式猿都用過類似於下麵的代碼:t=>t.Length<=25; 在C#中=>代表這是一個Lambda表達式,它用來對數組進行查 ...
我們在使用C#編程的時候,經常使用反射來動態調用方法,但有時候需要動態的生成方法,下麵介紹使用表達式樹的方式來自動生成方法,並調用。
首先需要說明什麼是表達式,熟悉Linq的程式猿都用過類似於下麵的代碼:t=>t.Length<=25;
在C#中=>代表這是一個Lambda表達式,它用來對數組進行查詢,統計,排序,去重的功能非常有用。而表達式樹就是通過動態的創建一個Lambda的方式來實現相關的功能。
下麵是一個類似於JS中apply函數的示例。
使用表達式樹,一定要引用System.Linq.Expressions;其中的Expression類有很多的方法可以定義一個方法所需要的所有東西。
public class CommonTest
{
public object TestMethodCall(int age, string name)
{
Console.WriteLine($"{name}'s Age is {age}");
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行代碼才實現。