在 .NET 中创建对象的几种方式的对比

在 .net 中,创建一个对象最简单的方法是直接使用 new (), 在实际的项目中,我们可能还会用到反射的方法来创建对象,如果你看过 Microsoft.Extensions.DependencyInjection 的源码,你会发现,为了保证在不同场景中的兼容性和性能,内部使用了多种反射机制。在本文中,我对比了常见的几种反射的方法,介绍了它们分别应该如何使用,每种的简易度和灵活度,然后做了基准测试,一起看看这之间的性能差距。

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

你可以访问这个站点,它可以很方便的把C#转换成IL代码,https://sharplab.io/[1]

然后使用 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/

https://github.com/dotnetcore/Natasha

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 .NET ,可以使用 iTextSharp 或 PDFSharp 库来读取 PDF 文件。下面是使用 iTextSharp 库读取 PDF 文本的示例代码: ```csharp using iTextSharp.text.pdf; using iTextSharp.text.pdf.parser; string pdfFilePath = @"C:\example.pdf"; using (PdfReader reader = new PdfReader(pdfFilePath)) { StringBuilder sb = new StringBuilder(); for (int i = 1; i <= reader.NumberOfPages; i++) { sb.Append(PdfTextExtractor.GetTextFromPage(reader, i)); } string pdfText = sb.ToString(); } ``` 首先,需要使用 `PdfReader` 类打开 PDF 文件。然后,可以使用 `PdfTextExtractor` 类从每一页提取文本。最后,将所有文本合并到一个字符串。 注意,这种方法只能读取 PDF 包含的文本信息,不能读取图像等其他类型的内容。如果需要读取图像等其他类型的内容,可以考虑使用其他库或工具。 ### 回答2: 在.NET平台上,我们可以使用iTextSharp来读取PDF文件。 iTextSharp是一个开源的.NET库,它提供了一套丰富的API,用于创建、操作和读取PDF文档。以下是使用iTextSharp读取PDF文件的步骤: 1. 首先,我们需要在项目引用iTextSharp库。可以通过NuGet包管理器安装iTextSharp库,或者手动将其添加到项目的引用。 2. 创建一个PDFReader对象,将要读取的PDF文件路径作为参数传递给该对象的构造函数。 ```csharp string filePath = "path/to/pdf/file.pdf"; PdfReader reader = new PdfReader(filePath); ``` 3. 获取PDF文件的总页数。 ```csharp int totalPages = reader.NumberOfPages; ``` 4. 遍历每一页,使用PdfTextExtractor类提取文本内容。 ```csharp for (int page = 1; page <= totalPages; page++) { string text = PdfTextExtractor.GetTextFromPage(reader, page); // 处理提取到的文本内容 Console.WriteLine(text); } ``` 5. 在读取完毕后,记得关闭PDFReader对象。 ```csharp reader.Close(); ``` 通过以上步骤,我们就可以在.NET平台上使用iTextSharp库来读取PDF文件,并处理提取到的文本内容。需要注意的是,iTextSharp还提供了许多其他功能,例如创建和操作PDF文件,添加图像或水印等。 ### 回答3: 在使用.NET读取PDF文件时,有几种常见的方法可以实现。 首先,可以使用iTextSharp库来读取和处理PDF文件。iTextSharp是一个开源的PDF库,可以在.NET平台上进行操作。使用iTextSharp,可以打开PDF文件,并通过遍历每一页的内容来读取文本。可以使用iTextSharp提供的类和方法,如PdfReader和PdfTextExtractor来提取PDF的文本信息。通过对文本内容的解析,可以获取所需的数据。 另一种方法是使用Adobe Acrobat SDK来读取PDF文件。Adobe Acrobat是一款常用的PDF阅读器,其SDK提供了各种功能和接口,包括读取和编辑PDF文件的能力。通过使用Acrobat SDK,可以编写具有PDF文件读取功能的.NET应用程序。例如,可以使用Acrobat SDK提供的JavaScript API来操作PDF文件,实现对文件的读取和解析。 此外,还可以使用其他第三方库,如Spire.PDF和Syncfusion.PDF等来读取PDF文件。这些库提供了比较简单易用的API,可以在.NET平台上进行PDF文件处理。通过使用这些库,可以轻松地打开PDF文件,并从提取所需的内容。 需要注意的是,PDF文件可能包含图像、表格、链接等不同类型的内容,而不仅仅是文本。因此,在读取PDF时要考虑到这些不同内容的解析和处理。 综上所述,通过使用iTextSharp、Adobe Acrobat SDK或其他第三方库,可以在.NET平台上实现读取PDF文件的功能,从而满足对PDF文件内容进行处理和提取的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值