在 .net 中,建立一个对象最简单的方法是直接使用 new (), 在实际的项目中,咱们可能还会用到反射的方法来建立对象,若是你看过 Microsoft.Extensions.DependencyInjection
的源码,你会发现,为了保证在不一样场景中的兼容性和性能,内部使用了多种反射机制。在本文中,我对比了常见的几种反射的方法,介绍了它们分别应该如何使用,每种的简易度和灵活度,而后作了基准测试,一块儿看看这之间的性能差距。git
我按照使用的简易度和灵活度,作了下边的排序,可能还有一些其余的反射方式,好比 Source Generators,本文中只针对如下几种进行测试。github
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 对象。express
这是使用反射的最简单和最灵活的方法之一,由于能够使用相似的方法来调用对象的方法、接口和属性等,可是这个也是最慢的反射方法之一。数组
若是你须要建立对象的话,在.NET Framework 和 .NET Core 中正好有一个专门为此设计的静态类,System.Activator, 使用方法很是的简单,还能够使用泛型,并且你还能够传入其余的参数。ide
Employee employee = Activator.CreateInstance<Employee>();
接下来就是在.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 是基于 Roslyn 开发的动态程序集构建库,直观和流畅的 Fluent API 设计,经过 roslyn 的强大赋能, 能够在程序运行时建立代码,包括 程序集、类、结构体、枚举、接口、方法等, 用来增长新的功能和模块,这里咱们用 NInstance 来建立对象。性能
// Natasha 初始化 NatashaInitializer.Initialize(); Employee employee = Natasha.CSharp.NInstance.Creator<Employee>().Invoke();
表达式 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对象ui
Emit 主要在 System.Reflection.Emit 命名空间下,这些方法容许咱们在程序中直接建立 IL (中间代码) 代码,IL 代码是指编译器在编译程序时输出的 "伪汇编代码", 也就是编译后的dll,当程序运行的时候,.NET CLR 中的 JIT编译器 将这些 IL 指令转换为真正的汇编代码。spa
接下来,须要在运行时建立一个新的方法,很简单,没有参数,只是建立一个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
你能够访问这个站点,它能够很方便的把C#转换成IL代码,https://sharplab.io/
而后使用 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();
上面我介绍了几种建立对象的方式,如今我开始使用 BenchmarkDotNet 进行基准测试,我也把 new Employee() 直接建立的方式加到测试列表中,并用它做为 "基线",来并比较其余的每种方法,同时我把一些方法的预热操做,放到了构造函数中一次执行,最终的代码以下
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/