软件编程
位置:首页>> 软件编程>> C#编程>> 详解C#通过反射获取对象的几种方式比较

详解C#通过反射获取对象的几种方式比较

作者:HappyGirl快乐女孩  发布时间:2021-07-26 17:45:55 

标签:C#,反射,获取对象

在本文中,对比了常见的几种反射的方法,介绍了它们分别应该如何使用,每种的简易度和灵活度,然后做了基准测试,一起看看这之间的性能差距。

按照使用的简易度和灵活度,做了下边的排序,可能还有一些其他的反射方式,比如 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>();
       }
   }

}

测试结果

详解C#通过反射获取对象的几种方式比较

 环境是 .NET 6 preview5, 使用标准反射的 Invoke() 方法虽然简单,但它是最慢的一种,使用 Activator.CreateInstance() 和 Microsoft.Extensions.DependencyInjection() 的时间差不多,时间是直接 new 创建的16倍,使用表达式 Expression 表现最优秀,Natasha 真是黑科技,比用Emit 还快了一点,使用Emit 是直接 new 创建的时间的1.8倍。你应该发现了各种方式之间的差距,但是需要注意的是这里是 ns 纳秒,一纳秒是一秒的十亿分之一。

来源:https://blog.csdn.net/weixin_67336587/article/details/125214648

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com