反射的妙用:C#通过反射动态生成类型继承接口并实现
起因
最近想自己鼓捣个RPC,想着简化RPC调用方式,直接申明接口,然后根据接口的属性去配置RPC调用的相关信息。有一种说法叫申明式调用。
简单来说就是,申明一个interface,动态继承并实例化,然后打点调用。
今天这边篇章讲的就是前半部分:动态继承并实例化。
相关知识点
反射、IL(中间语言)
框架背景
asp.net core
主要思路
通过反射,去动态生成class,并继承和实现interface。
相关属性说明
AssemblyBuilder:表示动态程序集
ModuleBuilder:表示动态程序集内的动态模块
TypeBuilder:表示动态类型
MethodBuilder:表示动态方法
ILGenerator:IL代码生成器
上述几点是这边文章中会用到的一些对象。
开干
第一步:得到类型构建器
/// <summary>
/// 生成动态类型
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="assemblyName">程序集名称</param>
/// <returns></returns>
private static TypeBuilder getTypeBuilder<T>()
{
// T类型所属的程序集名称
AssemblyName assName = typeof(T).Assembly.GetName();
// 动态程序集(Run表示该程序集只运行不保存)
AssemblyBuilder assyBuilder = AssemblyBuilder.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
// 在程序集中创建动态模块,模块名自定义
ModuleBuilder modBuilder = assyBuilder.DefineDynamicModule("MyMod");
// 动态类名
String newTypeName = "User";
// 动态类的属性,Class和Public
TypeAttributes newTypeAttribute = TypeAttributes.Class | TypeAttributes.Public;
// 动态类型的父类,这里不需要所以为null
Type newTypeParent = null;
// 动态类实现需要实现的接口
Type[] newTypeInterfaces = new Type[] { typeof(T) };
// 得到动态类型构建器
return modBuilder.DefineType(newTypeName, newTypeAttribute, newTypeParent, newTypeInterfaces);
}
第二步:完善类型信息
/// <summary>
/// 完善类型信息并生成
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Type BuildType<T>()
{
// 第一步得到的类型构建器
var typeBuilder = getTypeBuilder<T>();
// 获取类型的所有方法并遍历
MethodInfo[] targetMethods = typeof(T).GetMethods();
foreach (MethodInfo targetMethod in targetMethods)
{
// 只针对Public方法
if (targetMethod.IsPublic)
{
// 得到方法的各个参数的类型
ParameterInfo[] paramInfo = targetMethod.GetParameters();
// 方法的参数类型
Type[] paramType = new Type[paramInfo.Length];
for (int i = 0; i < paramInfo.Length; i++)
{
paramType[i] = paramInfo[i].ParameterType;
}
// 传入方法签名,得到方法构建器(方法名、方法属性、返回参数类型、方法参数类型)
MethodBuilder methodBuilder = typeBuilder.DefineMethod(targetMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, targetMethod.ReturnType, paramType);
// 要生成具体类,方法的实现是必不可少的,而方法的实现是通过Emit IL代码来产生的
// 得到IL生成器
ILGenerator ilGen = methodBuilder.GetILGenerator();
// 定义一个字符串(为了判断方法是否被调用)
ilGen.Emit(OpCodes.Ldstr, "我被调用了");
// 调用WriteLine函数
ilGen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
// 定义object类型的局部变量
LocalBuilder local = ilGen.DeclareLocal(typeof(object));
// 将索引为 0 的局部变量加载到栈的最顶层
ilGen.Emit(OpCodes.Ldloc_0, local);
// 判断是否需要返回值
if (methodBuilder.ReturnType == typeof(void))
{
ilGen.Emit(OpCodes.Pop);
}
else
{
// 判断返回类型是否是值类型
if (methodBuilder.ReturnType.IsValueType)
{
ilGen.Emit(OpCodes.Unbox_Any, methodBuilder.ReturnType);
}
else
{
// 强制转换变量为指定类型(返回值 类型)
ilGen.Emit(OpCodes.Castclass, methodBuilder.ReturnType);
}
}
// 返回
ilGen.Emit(OpCodes.Ret);
}
}
return typeBuilder.CreateType();
}
第三步:注入
前两步已经将动态生成类型并继承接口的过程描述完成了,我们现在将生成的动态类型注入到框架并使用。
// 先准备一个接口
public interface IUserService
{
string getname();
}
// 自定义注入中间件
public static IServiceCollection AddEmit<T>(this IServiceCollection service)
{
// 生成的动态类型
var type = DynamicImplementation.BuildType<T>();
// 继承的接口
var itype = typeof(T);
// 注入
service.AddScoped(itype, type);
return service;
}
// startup文件
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddEmit<IUserService>();
}
第四步:调用
private readonly IUserService _userService;
public HomeController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
public IActionResult Get()
{
_userService.getname();
return Ok();
}

就这样,动态生成类型并实现接口的操作就完成了。文章中涉及到的:OpCodes 大家或许不太理解相关的意思,要理解需要对IL代码有一定的了解,大家可以自行去msdn进行了解。
如果动态实现的方法比较复杂,不知道怎么编写相关IL代码,教大家一种便捷的方式。
有一个工具叫ILDASM,可以查看相关代码对应的 IL(中间语言)代码。
在 vs 中集成 ILDASM
打开 工具 ⋙ 外部工具 ⋙ 添加

ILDASM工具在安装 vs 后就存在,我的地址(也就是命令)是:
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ildasm.exe
配置完毕后点击应用,工具选项中就会出现 ILDASM 选项

下面就是 ILDASM 工具的界面信息,以及具体的代码对照,大家先把需要动态生成的方法编写完成后通过ILDASM工具查看代码的接口再对照去编写动态生成的代码。


今天这篇文章就到这里了,下面我也要去继续完善相关的代码了,如果完成效果还行我也会继续分享出来。
反射的妙用:C#通过反射动态生成类型继承接口并实现的更多相关文章
- 使用CodeDom动态生成类型
.NET 3.5的时候加入了匿名类型这个特性,我们可以直接使用 new {name="abc"} 来直接生成一个对象.这个特性现在应用的地方很多,比如dapper的查询参数都是用匿 ...
- Roslyn 编译器Api妙用:动态生成类并实现接口
在上一篇文章中有讲到使用反射手写IL代码动态生成类并实现接口. 反射的妙用:C#通过反射动态生成类型继承接口并实现 有位网友推荐使用 Roslyn 去脚本化动态生成,今天这篇文章就主要讲怎么使用 Ro ...
- c# 表达式目录树拷贝对象(根据对象类型动态生成表达式目录树)
表达式目录树,在C#中用Expression标识,这里就不介绍表达式目录树是什么了,有兴趣可以自行百度搜索,网上资料还是很多的. 这里主要分享的是如何动态构建表达式目录树. 构建表达式目录树的代码挺简 ...
- [.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程
[.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程 本节导读:本节主要介绍什么是.NET反射特性,.NET反射能为我们做些什么,最后介绍几种常用的 ...
- .Net 中的反射(动态创建类型实例) - Part.4
动态创建对象 在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它.可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以 ...
- Java下的框架编程(反射,泛型,元数据,CGLib,代码动态生成,AOP,动态语言嵌入)
Java 虽然没有动态语言般暴起,但仍然天连天,水接水的生出好多框架技术---反射(reflection),泛型(generics),元数据(annotation),proxies(proxy/cgl ...
- .Net 中的反射(动态创建类型实例)
动态创建对象 在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它.可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以 ...
- <经验杂谈>C#中一种最简单、最基本的反射(Reflection):通过反射获取方法函数
说起反射之前和很多用C#/.net的同仁们一样,相比于一般应用层对数据的增删改查总有点觉得深奥到难以理解.其实程序这东西,用过.实践过就很简单,我一直这么认为. 先说下概念:反射 Reflection ...
- java反射基础知识(五)反射应用实践
详解Java反射各种应用 Java除了给我们提供在编译期得到类的各种信息之外,还通过反射让我们可以在运行期间得到类的各种信息.通过反射获取类的信息,得到类的信息之后,就可以获取以下相关内容: Cl ...
随机推荐
- python二级 第9套
1. prInt.默认输出空格 2. 我的这种想法也行不通啊 format() 一次只能有一个未知量 2. 分割的结果就是列表 3. 对比"大学" 上一套的split('&q ...
- 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 百篇博客分析OpenHarmony源码 | v12.04
百篇博客系列篇.本篇为: v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...
- 好久没发文了,一篇Vue3的Composition API使用奉上
Composition API Composition API是Vue3中推荐的组件代码书写方式,相较于传统的Options API来说,它能让业务逻辑处理和后期代码维护变的更加简单. 首先我们来看O ...
- cadvisor+prometheus+alertmanager+grafana完成容器化监控告警(一)
一.概况 1.拓扑图 2.名词解释 Grafana 可视化监控容器运行情况 Prometheus: 开源系统监视和警报工具包 Alertmanager 一个独立的组件,负责接收并处理来自Prometh ...
- Spatial Statistics Tools(空间统计工具)
空间统计工具 1.分析模式 # Process: 增量空间自相关 arcpy.IncrementalSpatialAutocorrelation_stats("", "& ...
- 题解 「BZOJ4919 Lydsy1706月赛」大根堆
题目传送门 题目大意 给出一个 \(n\) 个点的树,每个点有权值,从中选出一些点,使得满足大根堆的性质.(即一个点的祖先节点如果选了那么该点的祖先节点的权值一定需要大于该点权值) 问能选出来的大根堆 ...
- 【转-Andrew_qian】stm32中断嵌套全攻略
断断续续学习STM32一学期了,时间过的好快,现在对STM32F103系列单片机的中断嵌套及外部中断做一个总结,全当学习笔记.废话不多说,ARM公司的Cortex-m3 内核,支持256个中断,其中包 ...
- 第五课第四周实验一:Embedding_plus_Positional_encoding 嵌入向量加入位置编码
目录 变压器预处理 包 1 - 位置编码 1.1 - 位置编码可视化 1.2 - 比较位置编码 1.2.1 - 相关性 1.2.2 - 欧几里得距离 2 - 语义嵌入 2.1 - 加载预训练嵌入 2. ...
- OO面向对象第三次作业总结
面向对象第三次作业总结 一.JML基础梳理及工具链 注释结构 行注释://@annotation 块注释:/*@ annotation @*/ 两种注释都是放在被注释部分上面. 常见表达式 原子表达式 ...
- jzoj6094
题目描述 给定一个循环流(每个点均满足流量平衡条件),这个循环流有$n$个点,且每条边的流量只有 $1$ 或$ 2$,其中$a$条边流量为$1$,$b$条边流量为$2$,判断是否存在一个流满足上述条件 ...