Several ways and comparison of creating objects in.Net

Time:2022-8-2

In.Net, the easiest way to create an object is to directly use new (). In actual projects, we may also use the reflection method to create objects, if you have seen microsoft.extensions The source code of dependencyinjection, you will find that in order to ensure compatibility and performance in different scenes, a variety of reflection mechanisms are used internally. In this article, I compared several common reflection methods, introduced how they should be used, the simplicity and flexibility of each, and then made a benchmark to see the performance gap between them.

I have made the following order according to the simplicity and flexibility of use. There may be some other reflection methods, such as source generators. This article only tests the following.

  • Directly call the invoke() method of the constructorinfo object
  • Use activator.createinstance()
  • Use microsoft.extensions.dependencyinjection
  • Black technology Natasha
  • Use expression
  • Use reflection Emit create dynamic methods

Invoke method using standard reflection


Type typeToCreate = typeof(Employee);

ConstructorInfo ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes);

Employee employee = ctor.Invoke(null) as Employee;

The first step is to get the type of the object through typeof (). You can also call getconstructor method through GetType and pass in system.type The emptytypes parameter is actually an empty array (New type[0]), which returns the constructorinfo object, and then calls the invoke method to return an employee object.

This is one of the simplest and most flexible methods to use reflection, because similar methods can be used to call object methods, interfaces, properties, etc., but this is also one of the slowest reflection methods.

Use activator.createinstance

If you need to create objects, in Net framework and.Net core happen to have a specially designed static class, system Activator is very simple to use. You can also use generics, and you can pass in other parameters.


Employee employee = Activator.CreateInstance<Employee>();

Use microsoft.extensions.dependencyinjection

Next is in Net core, microsoft.extensions Dependencyinjection. After registering the type in the container, we use iserviceprovider to get the object. Here I use the life cycle of transient to ensure that a new object will be created every time


IServiceCollection services = new ServiceCollection();

services.AddTransient<Employee>();

IServiceProvider provider = services.BuildServiceProvider();

Employee employee = provider.GetService<Employee>(); 

Natasha

Natasha is a dynamic assembly construction library developed by Roslyn. It is an intuitive and fluent fluent API design. With the powerful power of Roslyn, you can create code at program runtime, including assemblies, classes, structures, enumerations, interfaces, methods, etc., to add new functions and modules. Here we use ninstance to create objects.

//Natasha initialization
NatashaInitializer.Initialize();

Employee employee = Natasha.CSharp.NInstance.Creator<Employee>().Invoke();

Use expression

Expression has existed for a long time in system.linq Expressions namespace, and is an integral part of various other functions (LINQ) and libraries (EF core). In many ways, it is similar to reflection because they allow code to be manipulated at run time.


NewExpression constructorExpression = Expression.New(typeof(Employee));
Expression<Func<Employee>> lambdaExpression = Expression.Lambda<Func<Employee>>(constructorExpression);
Func<Employee> func = lambdaExpression.Compile();
Employee employee = func();

Expression provides a high-level language for declarative code. The expression created in the first two lines is equivalent to () = > new employee (), then call the compile method to get a delegate of func < >, and finally call this func to return an employee object

Use emit

Emit is mainly in system.reflection Under the emit namespace, these methods allow us to directly create IL (intermediate code) code in the program. IL code refers to the “pseudo assembly code” output by the compiler when compiling the program, that is, the compiled DLL. When the program is running Net CLR, the JIT compiler converts these IL instructions into real assembly code.

Next, you need to create a new method at run time. It is very simple. There are no parameters, just create an employee object and return it directly


Employee DynamicMethod()
{
    return new Employee();
}

System.reflection.emit DynamicMethod dynamic creation method


 DynamicMethod dynamic = new("DynamicMethod", typeof(Employee), null, typeof(ReflectionBenchmarks).Module, false);

Create a DynamicMethod object, and then specify the method name, return value, method parameters and module. The last parameter false means that JIT visibility check is not skipped.

Now we have a method signature, but we don’t have a method body yet, and we need to fill in the method body. Here we need to convert c# code into IL code. In fact, it is like this


IL_0000: newobj instance void Employee::.ctor()
IL_0005: ret

You can visit this site, which can easily convert c# into IL code,https://sharplab.io/

Then use the ilgenerator to operate the IL code, create a func < > delegate, and finally execute the delegate to return an employee object


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(); 

Benchmarking

I have introduced several methods of creating objects above. Now I begin to use benchmarkdotnet for benchmarking. I also add the method of directly creating new employee() to the test list, and use it as a “baseline” to compare each other methods. At the same time, I put the preheating operation of some methods into the constructor for one execution. The final code is as follows


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(); 


    }  
}

Next, modify the program CS, note that you need to run the test in release mode


using BenchmarkDotNet.Running; 

namespace ReflectionBenchConsoleApp
{
    public class Program
    {
        public static void Main(string[] args)
        { 
            var sumary = BenchmarkRunner.Run<ReflectionBenchmarks>();
        }
    } 
   
}

test result

The environment here is.Net 6 preview5. Although it is simple to use the invoke () method of standard reflection, it is the slowest one, using activator Createinstance() and microsoft.extensions The time of dependencyinjection () is about the same, 16 times that of direct new creation, and the best performance is achieved by using expression. Natasha is really a black technology, which is a little faster than using emit, and the time of using emit is 1.8 times that of direct new creation. You should find the gap between various methods, but it should be noted that here is ns nanosecond, and a nanosecond is one billionth of a second.

Here is a brief comparison of several methods of creating objects. The test results may not be particularly accurate. Those who are interested can also test on the.Net framework. I hope it is useful to you!

Related links

https://andrewlock.net/benchmarking-4-reflection-methods-for-calling-a-constructor-in-dotnet/

https://github.com/dotnetcore/Natasha

This is the end of this article about several ways and comparisons of creating objects in.Net, and more related Net to create object content, please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!

Recommended Today

Accept these 7 plug-ins, which will make you even stronger when using vite

I believe that many partners have already started to use vue3 for development, and they must also use vite. These plug-ins I want to introduce today can make you even stronger when using vite for development. vite-plugin-restart Automatically restart the vite service by listening to file modification. The most common scenario is monitoringvite.config.jsand.env.developmentFile, we know […]