起因
最近想自己鼓搗個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
工具查看代碼的接口再對照去編寫動態生成的代碼。
今天這篇文章就到這里了,下面我也要去繼續完善相關的代碼了,如果完成效果還行我也會繼續分享出來。