咱們在使用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行代碼才實現。