C#使用表达式树动态调用方法并实现99乘法表
我们在使用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行代码才实现。