起因

最近想自己鼓捣个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工具查看代码的接口再对照去编写动态生成的代码。

今天这篇文章就到这里了,下面我也要去继续完善相关的代码了,如果完成效果还行我也会继续分享出来。

无绪分享

最新文章

  1. TemplateMethod(模块方法模式)
  2. Linux远程执行Shell命令或脚本
  3. WebStorm调试node.js
  4. Git学习笔记(2)——版本的回退,和暂存区的理解
  5. Selenium WebDriver 处理cookie
  6. VirtualBox Win7 虚拟机 共享文件夹设置
  7. [分享] WIN7x64封装体积小于4G制作过程
  8. B-Tree indexs
  9. Geo-Fence
  10. C语言中的三值合一
  11. socket编程五种模型
  12. POJ - 1170 Shopping Offers (五维DP)
  13. js 代码命名规范系列
  14. 哥德尔,图灵和康托尔 part 2 停机问题
  15. python socket理论知识
  16. 重复数据删除(De-duplication)技术研究(SourceForge上发布dedup util)
  17. isNaN() 函数用于检查其参数是否是非数字值。如果是非数字值则返回true
  18. Ubuntu 配置FTP服务器
  19. Hibernate学习(一)创建数据表
  20. MySQL的启动和关闭

热门文章

  1. eval(input())
  2. 大型项目源码集合「GitHub 热点速览 v.21.39」
  3. centos 关于yum无法使用
  4. 使用three.js实现炫酷的酸性风格3D页面
  5. Monte-carlo-simulation
  6. sqlserver 2000 insert注入的问题
  7. 前端从web服务器或者CDN下载资源
  8. nginx访问权限问题
  9. JavaScript兼容性汇总
  10. python和shell 取日期为今天的行