反射的妙用: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();
}

image


就這樣,動態生成類型並實現接口的操作就完成了。文章中涉及到的:OpCodes 大家或許不太理解相關的意思,要理解需要對IL代碼有一定的了解,大家可以自行去msdn進行了解。

如果動態實現的方法比較復雜,不知道怎么編寫相關IL代碼,教大家一種便捷的方式。

有一個工具叫ILDASM,可以查看相關代碼對應的 IL(中間語言)代碼。

在 vs 中集成 ILDASM

打開 工具 外部工具 添加

image

ILDASM工具在安裝 vs 后就存在,我的地址(也就是命令)是:

C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ildasm.exe

配置完畢后點擊應用,工具選項中就會出現 ILDASM 選項

image

下面就是 ILDASM 工具的界面信息,以及具體的代碼對照,大家先把需要動態生成的方法編寫完成后通過ILDASM工具查看代碼的接口再對照去編寫動態生成的代碼。

image

image

今天這篇文章就到這里了,下面我也要去繼續完善相關的代碼了,如果完成效果還行我也會繼續分享出來。

無緒分享


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM