詳解C#通過反射獲取對(duì)象的幾種方式比較
在本文中,對(duì)比了常見的幾種反射的方法,介紹了它們分別應(yīng)該如何使用,每種的簡(jiǎn)易度和靈活度,然后做了基準(zhǔn)測(cè)試,一起看看這之間的性能差距。
按照使用的簡(jiǎn)易度和靈活度,做了下邊的排序,可能還有一些其他的反射方式,比如 Source Generators,本文中只針對(duì)以下幾種進(jìn)行測(cè)試。
- 直接調(diào)用 ConstructorInfo 對(duì)象的Invoke()方法
- 使用 Activator.CreateInstance()
- 使用 Microsoft.Extensions.DependencyInjection
- 黑科技 Natasha•使用表達(dá)式 Expression
- 使用 Reflection.Emit 創(chuàng)建動(dòng)態(tài)方法
使用標(biāo)準(zhǔn)反射的 Invoke 方法
Type typeToCreate = typeof(Employee); ConstructorInfo ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes); Employee employee = ctor.Invoke(null) as Employee;
第一步是通過 typeof() 獲取對(duì)象的類型,你也可以通過 GetType 的方式,然后調(diào)用 GetConstructor 方法,傳入 System.Type.EmptyTypes 參數(shù),實(shí)際上它是一個(gè)空數(shù)組 (new Type[0]), 返回 ConstructorInfo對(duì)象, 然后調(diào)用 Invoke 方法,會(huì)返回一個(gè) Employee 對(duì)象。
這是使用反射的最簡(jiǎn)單和最靈活的方法之一,因?yàn)榭梢允褂妙愃频姆椒▉碚{(diào)用對(duì)象的方法、接口和屬性等,但是這個(gè)也是最慢的反射方法之一。
使用 Activator.CreateInstance
如果你需要?jiǎng)?chuàng)建對(duì)象的話,在.NET Framework 和 .NET Core 中正好有一個(gè)專門為此設(shè)計(jì)的靜態(tài)類,System.Activator, 使用方法非常的簡(jiǎn)單,還可以使用泛型,而且你還可以傳入其他的參數(shù)。
Employee employee = Activator.CreateInstance<Employee>();
使用 Microsoft.Extensions.DependencyInjection
接下來就是在.NET Core 中很熟悉的 IOC 容器,Microsoft.Extensions.DependencyInjection,把類型注冊(cè)到容器中后,然后使用 IServiceProvider 來獲取對(duì)象,這里使用了 Transient 的生命周期,保證每次都會(huì)創(chuàng)建一個(gè)新的對(duì)象
IServiceCollection services = new ServiceCollection(); services.AddTransient<Employee>(); IServiceProvider provider = services.BuildServiceProvider(); Employee employee = provider.GetService<Employee>();
Natasha
Natasha 是基于 Roslyn 開發(fā)的動(dòng)態(tài)程序集構(gòu)建庫,直觀和流暢的 Fluent API 設(shè)計(jì),通過 roslyn 的強(qiáng)大賦能, 可以在程序運(yùn)行時(shí)創(chuàng)建代碼,包括 程序集、類、結(jié)構(gòu)體、枚舉、接口、方法等, 用來增加新的功能和模塊,這里用 NInstance 來創(chuàng)建對(duì)象。
// Natasha 初始化 NatashaInitializer.Initialize(); Employee employee = Natasha.CSharp.NInstance.Creator<Employee>().Invoke();
使用表達(dá)式 Expression
表達(dá)式 Expression 其實(shí)也已經(jīng)存在很長(zhǎng)時(shí)間了,在 System.Linq.Expressions 命名空間下, 并且是各種其他功能 (LINQ) 和庫(EF Core) 不可或缺的一部分,在許多方面,它類似于反射,因?yàn)樗鼈冊(cè)试S在運(yùn)行時(shí)操作代碼。
NewExpression constructorExpression = Expression.New(typeof(Employee)); Expression<Func<Employee>> lambdaExpression = Expression.Lambda<Func<Employee>>(constructorExpression); Func<Employee> func = lambdaExpression.Compile(); Employee employee = func();
表達(dá)式提供了一種用于聲明式代碼的高級(jí)語言,前兩行創(chuàng)建了的表達(dá)式, 等價(jià)于 () => new Employee(),然后調(diào)用 Compile 方法得到一個(gè) Func<> 的委托,最后調(diào)用這個(gè) Func 返回一個(gè)Employee對(duì)象
使用 Emit
Emit 主要在 System.Reflection.Emit 命名空間下,這些方法允許在程序中直接創(chuàng)建 IL (中間代碼) 代碼,IL 代碼是指編譯器在編譯程序時(shí)輸出的 "偽匯編代碼", 也就是編譯后的dll,當(dāng)程序運(yùn)行的時(shí)候,.NET CLR 中的 JIT編譯器 將這些 IL 指令轉(zhuǎn)換為真正的匯編代碼。
接下來,需要在運(yùn)行時(shí)創(chuàng)建一個(gè)新的方法,很簡(jiǎn)單,沒有參數(shù),只是創(chuàng)建一個(gè)Employee對(duì)象然后直接返回
Employee DynamicMethod() { return new Employee(); }
這里主要使用到了 System.Reflection.Emit.DynamicMethod 動(dòng)態(tài)創(chuàng)建方法
DynamicMethod dynamic = new("DynamicMethod", typeof(Employee), null, typeof(ReflectionBenchmarks).Module, false);
創(chuàng)建了一個(gè) DynamicMethod 對(duì)象,然后指定了方法名,返回值,方法的參數(shù)和所在的模塊,最后一個(gè)參數(shù) false 表示不跳過 JIT 可見性檢查。
現(xiàn)在有了方法簽名,但是還沒有方法體,還需要填充方法體,這里需要C#代碼轉(zhuǎn)換成 IL代碼,實(shí)際上它是這樣的
IL_0000: newobj instance void Employee::.ctor()
IL_0005: ret
然后使用 ILGenerator 來操作IL代碼, 然后創(chuàng)建一個(gè) Func<> 的委托, 最后執(zhí)行該委托返回一個(gè) Employee 對(duì)象
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();
對(duì)比測(cè)試
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 模式下運(yùn)行測(cè)試
using BenchmarkDotNet.Running; namespace ReflectionBenchConsoleApp { public class Program { public static void Main(string[] args) { var sumary = BenchmarkRunner.Run<ReflectionBenchmarks>(); } } }
測(cè)試結(jié)果
環(huán)境是 .NET 6 preview5, 使用標(biāo)準(zhǔn)反射的 Invoke() 方法雖然簡(jiǎn)單,但它是最慢的一種,使用 Activator.CreateInstance() 和 Microsoft.Extensions.DependencyInjection() 的時(shí)間差不多,時(shí)間是直接 new 創(chuàng)建的16倍,使用表達(dá)式 Expression 表現(xiàn)最優(yōu)秀,Natasha 真是黑科技,比用Emit 還快了一點(diǎn),使用Emit 是直接 new 創(chuàng)建的時(shí)間的1.8倍。你應(yīng)該發(fā)現(xiàn)了各種方式之間的差距,但是需要注意的是這里是 ns 納秒,一納秒是一秒的十億分之一。
總結(jié)
這里簡(jiǎn)單對(duì)比了幾種創(chuàng)建對(duì)象的方法,測(cè)試的結(jié)果也可能不是特別準(zhǔn)確,有興趣的還可以在 .net framework 上面進(jìn)行測(cè)試,希望對(duì)您有用!
相關(guān)鏈接
https://andrewlock.net/benchmarking-4-reflection-methods-for-calling-a-constructor-in-dotnet/
https://github.com/dotnetcore/Natasha
到此這篇關(guān)于詳解C#通過反射獲取對(duì)象的幾種方式比較的文章就介紹到這了,更多相關(guān)C# 反射獲取對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#之Windows自帶打印功能的實(shí)現(xiàn)
這篇文章主要介紹了C#之Windows自帶打印功能的實(shí)現(xiàn)方式,具有很好的價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06淺談C#手機(jī)號(hào)換成111XXXX1111 這種顯示的解決思路
下面小編就為大家?guī)硪黄獪\談C#手機(jī)號(hào)換成111XXXX1111 這種顯示的解決思路。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11C# 常用協(xié)議實(shí)現(xiàn)模版及FixedSizeReceiveFilter示例(SuperSocket入門)
本文主要介紹了常用協(xié)議實(shí)現(xiàn)模版及FixedSizeReceiveFilter示例。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01C#使用System.Net.Mail類實(shí)現(xiàn)郵件發(fā)送
這篇文章介紹了C#使用System.Net.Mail類實(shí)現(xiàn)郵件發(fā)送的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07