目录
  • 使用标准反射的 invoke 方法
  • 使用 activator.createinstance
  • 使用 microsoft.extensions.dependencyinjection
  • natasha
  • 使用表达式 expression
  • 使用 emit
  • 基准测试
  • 相关链接

在 .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/

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

到此这篇关于.net中创建对象的几种方式和对比的文章就介绍到这了,更多相关.net 创建对象内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!