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行代码才实现。
C#使用表达式树动态调用方法并实现99乘法表的更多相关文章
- struts2 type="redirectAction"重定向 与动态调用方法
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE struts PUBLIC "-/ ...
- Struts2 动态调用方法
struts2动态调用方法有两种方式 方式一:用通配符进行调用: Action方法: package com.bjyinfu.struts.actions; public class CatchDyn ...
- Xcode中Objc动态调用方法同时避免警告的几个办法
我们在Xcode中使用objc写代码的时候往往会碰到动态调用方法的时候. 如果是静态调用这很常见,不会有任何问题: [self performSelector:@selector(method)]; ...
- Guava动态调用方法
前言 大家在Coding的时候,经常会遇到这样一个情况,根据不同的条件去执行对应的代码.我们通常的处理方式是利用if-else判断,或者直接switch-case,特别是jdk1.6之后,swith开 ...
- C# 知识点笔记:IEnumerable<>的使用,利用反射动态调用方法
IEnumerable<T>的使用 创建一个IEnumerable对象 List<string> fruits = new List<string> { " ...
- 表达式树动态拼接lambda
动态拼接lambda表达式树 前言 最近在优化同事写的代码(我们的框架用的是dapperLambda),其中有一个这样很普通的场景——界面上提供了一些查询条件框供用户来进行过滤数据.由于dappe ...
- spring动态调用方法
有的时候为了程序的灵活性,需要根据参数动态的调用方法.代码框架大致spring为主,下面是具体代码: 接口: 实现类(实现类中有一个从spring容器中取的对象) 这是最初我直接用反射去调用的代码: ...
- 利用java反射动态调用方法,生成grid数据
项目中需要java后台查询并组装前台grid的数据,数据行数不定,数据行定义不定,开始用了最原始的方法,写了几百行,就是前台需要什么字段后台拼接什么字段,java代码冗余量非常大,并且不够灵活,一旦前 ...
- php中怎么使用call_user_func动态调用方法
php中可使用call_user_func进行方法的动态调用,可以动态调用普通函数.类方法以及带参数的类方法1.定义一个普通函数getCurrentDate,用于获取今天日期.call_user_fu ...
随机推荐
- A站(ACFun)爬虫爬取并播放、下载视频(JAVA)
本文使用的工具有vlc+ffmpeg+phantomjs 一般视频网站的视频地址暴露的方式有两种: 1.html直接暴露 2.通过ajax异步返回. A站使用的方式是第二种.针对第一种方式,直接使用j ...
- [转]【jsp】
建立时间:6.30 &7.12& 7.24& 7.27 7月心比较浮躁,几乎没怎么学习编程 一.JSP技术 1.jsp脚本和注释 jsp脚本: 1)<%java代码%&g ...
- CentOS7.5下实现MySQL5.7主从同步
这里使用两台Linux主机(一台充当MySQL主服务器,另一台充当MySQL从服务器),MySQL用yum安装,版本均为5.7,下表是它们所使用的操作系统以及IP地址. 两台Linux主机所使用的操作 ...
- request.user怎么来的
1.登录认证(auth认证登录后login后设置了session等信息包含用户的pk) >>>>> 2.用户再次请求登录的时候,通过 ...
- Nginx——端口负载均衡
前言 Nginx做的代理后面SpringBoot的项目,1N3T的架构,Tomcat的配置也进行了相应的调优. 配置 这里主要来简单的说下Nginx的端口负载均衡,具体的大家可以参考 Nginx文档 ...
- Django学习(不定期更新)
基于luffy项目的疑问点解决 删除掉.git文件夹,应该是该项目不需要git 在git克隆代码之后,初始化git,自动创建git仓库需要的目录,这些文件夹存在于项目下的.git文件夹中 .git文件 ...
- 为什么说JAVA程序员必须掌握SpringBoot?
原文链接:https://w.url.cn/s/AuDahfb SpringBoot 2.0 的推出又激起了一阵学习 SpringBoot 热,那么, SpringBoot 诞生的背景是什么?Spri ...
- Linux下进程间通信方式——信号量(Semaphore)
1.信号量 信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据 ...
- ASP.NET Core 简介
.NET Core 是 .NET Framework 的新一代版本,是微软开发的第一个具有跨平台 ( Windows.Mac OSX .Linux ) 能力的应用程序开发框 ASP.NET Core ...
- PATB1048数字加密
关于代码都是可以在PAT上跑通的 自己是在VS2017上写的,所以会有语句system("pause");,表示暂定方便查看结果. *在一个是VS2017中使用scanf会报错,所 ...