热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

本文主要介绍了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();

使用 Microsoft.Extensions.DependencyInjection

接下来就是在.NET Core 中很熟悉的 IOC 容器,Microsoft.Extensions.DependencyInjection,把类型注册到容器中后,然后使用 IServiceProvider 来获取对象,这里使用了 Transient 的生命周期,保证每次都会创建一个新的对象

IServiceCollection services = new ServiceCollection();
 
services.AddTransient();
 
IServiceProvider provider = services.BuildServiceProvider();
 
Employee employee = provider.GetService();

Natasha

Natasha 是基于 Roslyn 开发的动态程序集构建库,直观和流畅的 Fluent API 设计,通过 roslyn 的强大赋能, 可以在程序运行时创建代码,包括 程序集、类、结构体、枚举、接口、方法等, 用来增加新的功能和模块,这里用 NInstance 来创建对象。

// Natasha 初始化
NatashaInitializer.Initialize();
 
Employee employee = Natasha.CSharp.NInstance.Creator().Invoke();

使用表达式 Expression

表达式 Expression 其实也已经存在很长时间了,在 System.Linq.Expressions 命名空间下, 并且是各种其他功能 (LINQ) 和库(EF Core) 不可或缺的一部分,在许多方面,它类似于反射,因为它们允许在运行时操作代码。

NewExpression cOnstructorExpression= Expression.New(typeof(Employee));
Expression> lambdaExpression = Expression.Lambda>(constructorExpression);
Func 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 emitActivator = dynamic.CreateDelegate(typeof(Func)) as Func;
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 _expressionActivator;
        private readonly Func _emitActivator;
        private readonly Func _natashaActivator;
 
 
        public ReflectionBenchmarks()
        { 
            _ctor = typeof(Employee).GetConstructor(Type.EmptyTypes); 
 
            _provider = new ServiceCollection().AddTransient().BuildServiceProvider(); 
 
            NatashaInitializer.Initialize();
            _natashaActivator = Natasha.CSharp.NInstance.Creator();
 
 
            _expressiOnActivator= Expression.Lambda>(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)) as Func;  
 
        }  
 
        [Benchmark(Baseline = true)]
        public Employee UseNew() => new Employee(); 
 
        [Benchmark]
        public Employee UseReflection() => _ctor.Invoke(null) as Employee;
 
        [Benchmark]
        public Employee UseActivator() => Activator.CreateInstance();  
 
        [Benchmark]
        public Employee UseDependencyInjection() => _provider.GetRequiredService();
 
 
        [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();
        }
    } 
 
}

测试结果

 环境是 .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

到此这篇关于详解C#通过反射获取对象的几种方式比较的文章就介绍到这了,更多相关C# 反射获取对象内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文介绍了一种在PHP中对二维数组根据某个字段进行排序的方法,以年龄字段为例,按照倒序的方式进行排序,并给出了具体的代码实现。 ... [详细]
author-avatar
东亚病夫出世_332
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有