網站首頁 編程語言 正文
在本文中,對比了常見的幾種反射的方法,介紹了它們分別應該如何使用,每種的簡易度和靈活度,然后做了基準測試,一起看看這之間的性能差距。
按照使用的簡易度和靈活度,做了下邊的排序,可能還有一些其他的反射方式,比如 Source Generators,本文中只針對以下幾種進行測試。
- 直接調用 ConstructorInfo 對象的Invoke()方法
- 使用 Activator.CreateInstance()
- 使用 Microsoft.Extensions.DependencyInjection
- 黑科技 Natasha?使用表達式 Expression
- 使用 Reflection.Emit 創建動態方法
使用標準反射的 Invoke 方法
Type typeToCreate = typeof(Employee);
ConstructorInfo ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes);
Employee employee = ctor.Invoke(null) as Employee;
第一步是通過 typeof() 獲取對象的類型,你也可以通過 GetType 的方式,然后調用 GetConstructor 方法,傳入 System.Type.EmptyTypes 參數,實際上它是一個空數組 (new Type[0]), 返回 ConstructorInfo對象, 然后調用 Invoke 方法,會返回一個 Employee 對象。
這是使用反射的最簡單和最靈活的方法之一,因為可以使用類似的方法來調用對象的方法、接口和屬性等,但是這個也是最慢的反射方法之一。
使用 Activator.CreateInstance
如果你需要創建對象的話,在.NET Framework 和 .NET Core 中正好有一個專門為此設計的靜態類,System.Activator, 使用方法非常的簡單,還可以使用泛型,而且你還可以傳入其他的參數。
Employee employee = Activator.CreateInstance<Employee>();
使用 Microsoft.Extensions.DependencyInjection
接下來就是在.NET Core 中很熟悉的 IOC 容器,Microsoft.Extensions.DependencyInjection,把類型注冊到容器中后,然后使用 IServiceProvider 來獲取對象,這里使用了 Transient 的生命周期,保證每次都會創建一個新的對象
IServiceCollection services = new ServiceCollection();
services.AddTransient<Employee>();
IServiceProvider provider = services.BuildServiceProvider();
Employee employee = provider.GetService<Employee>();
Natasha
Natasha 是基于 Roslyn 開發的動態程序集構建庫,直觀和流暢的 Fluent API 設計,通過 roslyn 的強大賦能, 可以在程序運行時創建代碼,包括 程序集、類、結構體、枚舉、接口、方法等, 用來增加新的功能和模塊,這里用 NInstance 來創建對象。
// Natasha 初始化
NatashaInitializer.Initialize();
Employee employee = Natasha.CSharp.NInstance.Creator<Employee>().Invoke();
使用表達式 Expression
表達式 Expression 其實也已經存在很長時間了,在 System.Linq.Expressions 命名空間下, 并且是各種其他功能 (LINQ) 和庫(EF Core) 不可或缺的一部分,在許多方面,它類似于反射,因為它們允許在運行時操作代碼。
NewExpression constructorExpression = Expression.New(typeof(Employee));
Expression<Func<Employee>> lambdaExpression = Expression.Lambda<Func<Employee>>(constructorExpression);
Func<Employee> func = lambdaExpression.Compile();
Employee employee = func();
?表達式提供了一種用于聲明式代碼的高級語言,前兩行創建了的表達式, 等價于 () => new Employee(),然后調用 Compile 方法得到一個 Func<> 的委托,最后調用這個 Func 返回一個Employee對象
使用 Emit
Emit 主要在 System.Reflection.Emit 命名空間下,這些方法允許在程序中直接創建 IL (中間代碼) 代碼,IL 代碼是指編譯器在編譯程序時輸出的 "偽匯編代碼", 也就是編譯后的dll,當程序運行的時候,.NET CLR 中的 JIT編譯器 將這些 IL 指令轉換為真正的匯編代碼。
接下來,需要在運行時創建一個新的方法,很簡單,沒有參數,只是創建一個Employee對象然后直接返回
Employee DynamicMethod()
{
return new Employee();
}
這里主要使用到了 System.Reflection.Emit.DynamicMethod 動態創建方法?
DynamicMethod dynamic = new("DynamicMethod", typeof(Employee), null, typeof(ReflectionBenchmarks).Module, false);
創建了一個 DynamicMethod 對象,然后指定了方法名,返回值,方法的參數和所在的模塊,最后一個參數 false 表示不跳過 JIT 可見性檢查。
現在有了方法簽名,但是還沒有方法體,還需要填充方法體,這里需要C#代碼轉換成 IL代碼,實際上它是這樣的
IL_0000: newobj instance void Employee::.ctor()
IL_0005: ret
然后使用 ILGenerator 來操作IL代碼, 然后創建一個 Func<> 的委托, 最后執行該委托返回一個 Employee 對象
ConstructorInfor ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes);
ILGenerator il = createHeadersMethod.GetILGenerator();
il.Emit(OpCodes.Newobj, Ctor);
il.Emit(OpCodes.Ret);
Func<Employee> emitActivator = dynamic.CreateDelegate(typeof(Func<Employee>)) as Func<Employee>;
Employee employee = emitActivator();
對比測試
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
namespace ReflectionBenchConsoleApp
{
public class Employee { }
public class ReflectionBenchmarks
{
private readonly ConstructorInfo _ctor;
private readonly IServiceProvider _provider;
private readonly Func<Employee> _expressionActivator;
private readonly Func<Employee> _emitActivator;
private readonly Func<Employee> _natashaActivator;
public ReflectionBenchmarks()
{
_ctor = typeof(Employee).GetConstructor(Type.EmptyTypes);
_provider = new ServiceCollection().AddTransient<Employee>().BuildServiceProvider();
NatashaInitializer.Initialize();
_natashaActivator = Natasha.CSharp.NInstance.Creator<Employee>();
_expressionActivator = Expression.Lambda<Func<Employee>>(Expression.New(typeof(Employee))).Compile();
DynamicMethod dynamic = new("DynamicMethod", typeof(Employee), null, typeof(ReflectionBenchmarks).Module, false);
ILGenerator il = dynamic.GetILGenerator();
il.Emit(OpCodes.Newobj, typeof(Employee).GetConstructor(System.Type.EmptyTypes));
il.Emit(OpCodes.Ret);
_emitActivator = dynamic.CreateDelegate(typeof(Func<Employee>)) as Func<Employee>;
}
[Benchmark(Baseline = true)]
public Employee UseNew() => new Employee();
[Benchmark]
public Employee UseReflection() => _ctor.Invoke(null) as Employee;
[Benchmark]
public Employee UseActivator() => Activator.CreateInstance<Employee>();
[Benchmark]
public Employee UseDependencyInjection() => _provider.GetRequiredService<Employee>();
[Benchmark]
public Employee UseNatasha() => _natashaActivator();
[Benchmark]
public Employee UseExpression() => _expressionActivator();
[Benchmark]
public Employee UseEmit() => _emitActivator();
}
}
接下來,還修改 Program.cs,注意這里需要在 Release 模式下運行測試
using BenchmarkDotNet.Running;
namespace ReflectionBenchConsoleApp
{
public class Program
{
public static void Main(string[] args)
{
var sumary = BenchmarkRunner.Run<ReflectionBenchmarks>();
}
}
}
測試結果
?環境是 .NET 6 preview5, 使用標準反射的 Invoke() 方法雖然簡單,但它是最慢的一種,使用 Activator.CreateInstance() 和 Microsoft.Extensions.DependencyInjection() 的時間差不多,時間是直接 new 創建的16倍,使用表達式 Expression 表現最優秀,Natasha 真是黑科技,比用Emit 還快了一點,使用Emit 是直接 new 創建的時間的1.8倍。你應該發現了各種方式之間的差距,但是需要注意的是這里是 ns 納秒,一納秒是一秒的十億分之一。
總結
這里簡單對比了幾種創建對象的方法,測試的結果也可能不是特別準確,有興趣的還可以在 .net framework 上面進行測試,希望對您有用!
相關鏈接?
https://andrewlock.net/benchmarking-4-reflection-methods-for-calling-a-constructor-in-dotnet/
https://github.com/dotnetcore/Natasha
原文鏈接:https://blog.csdn.net/weixin_67336587/article/details/125214648
相關推薦
- 2022-06-23 Android?Socket實現多個客戶端聊天布局_Android
- 2022-03-10 你知道如何自定義sort函數中的比較函數_C 語言
- 2022-10-06 QT獲取顯示當前時間和日期的方法(用QTime,QDate和QDateTime)_C 語言
- 2022-08-19 Python利用memory_profiler查看內存占用情況_python
- 2022-09-09 PyCharm?設置數據庫,查詢數據庫語句方式_python
- 2022-08-01 flask-SQLALchemy連接數據庫的實現示例_python
- 2022-04-19 C語言內存管理及初始化細節示例詳解_C 語言
- 2022-10-05 linux查看服務器開放的端口和啟用的端口多種方式_Linux
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支