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