.NET 에서 대상 을 만 드 는 몇 가지 방식 과 비교

9589 단어 .NET생 성 대상
.net 에서 대상 을 만 드 는 가장 간단 한 방법 은 new()를 직접 사용 하 는 것 입 니 다.실제 프로젝트 에서 우 리 는 반사 적 인 방법 으로 대상 을 만 들 수 있 습 니 다.만약 에 Microsoft.Extensions.Dependency Injection 의 소스 코드 를 보 았 다 면 서로 다른 장면 에서 의 호환성 과 성능 을 확보 하기 위해 내부 에 여러 가지 반사 체 제 를 사용 한 것 을 발견 할 수 있 습 니 다.본 논문 에서 저 는 흔히 볼 수 있 는 몇 가지 반사 방법 을 비교 하여 그들 이 각각 어떻게 사용 해 야 하 는 지,모든 간단 도와 유연성 을 소개 한 다음 에 기준 테스트 를 한 다음 에 이 간 의 성능 차 이 를 살 펴 보 았 습 니 다.
저 는 사용 하 는 간이 도와 유연성 에 따라 아래 의 순 서 를 매 겼 습 니 다.예 를 들 어 Source Generators 등 다른 반사 방식 도 있 을 수 있 습 니 다.본 고 는 다음 과 같은 몇 가지 만 테스트 했 습 니 다.
  • ConstructorInfo 대상 을 직접 호출 하 는 Invoke()방법
  • Activator.CreateInstance()사용
  • Microsoft.Extensions.Dependency Injection 사용
  • 흑 과학기술 Natasha
  • 표현 식 표현 식 사용
  • Reflection.Emit 를 사용 하여 동적 방법 만 들 기
  • 표준 반사 Invoke 방법 사용 하기
    
    Type typeToCreate = typeof(Employee);
    
    ConstructorInfo ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes);
    
    Employee employee = ctor.Invoke(null) as Employee;
    
    
    첫 번 째 단 계 는 type of()를 통 해 대상 의 유형 을 가 져 오 는 것 입 니 다.GetType 방식 을 통 해 GetConstructor 방법 을 호출 하여 System.Type.Empty Types 인 자 를 전달 할 수 있 습 니 다.실제로 빈 배열(new Type[0])입 니 다.Constructor Info 대상 으로 돌아 간 다음 Invoke 방법 을 호출 하면 Employee 대상 을 되 돌려 줍 니 다.
    이것 은 반 사 를 사용 하 는 가장 간단 하고 유연 한 방법 중 하나 이다.유사 한 방법 으로 대상 의 방법,인터페이스 와 속성 등 을 호출 할 수 있 지만 이것 도 가장 느 린 반사 방법 중 하나 이기 때문이다.
    Activator.CreateInstance 사용 하기
    대상 을 만 들 필요 가 있다 면.NET Framework 와.NET Core 에 이 를 위 한 정적 클래스 가 있 습 니 다.System.activator 는 사용 방법 이 매우 간단 하고 범 형 을 사용 할 수 있 으 며 다른 매개 변 수 를 입력 할 수 있 습 니 다.
    
    Employee employee = Activator.CreateInstance<Employee>();
    
    Microsoft.Extensions.Dependency Injection 사용 하기
    다음은.NET Core 에 익숙 한 IOC 용기,Microsoft.Extensions.Dependency Injection 입 니 다.용기 에 유형 을 등록 한 후에 저 희 는 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 도 존재 한 지 오래 되 었 습 니 다.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 preview 5 입 니 다.표준 반 사 를 사용 하 는 Invoke()방법 은 간단 하지만 가장 느 린 것 입 니 다.Activator.Create Instance()와 Microsoft.Extensions.Dependency Injection()을 사용 하 는 시간 차 가 많 지 않 습 니 다.시간 은 직접 new 가 만 든 16 배 입 니 다.표현 식 Expression 을 사용 하 는 것 이 가장 우수 합 니 다.Natasha 는 정말 흑 과학기술 입 니 다.Emit 를 사용 하 는 것 보다 조금 빠 르 고,Emit 를 사용 하 는 것 은 직접 new 를 만 드 는 시간의 1.8 배 에 달 합 니 다.여러 가지 방식 간 의 차 이 를 발견 해 야 하지만 주의해 야 할 것 은 여기 가 ns 나 초 이 고 1 나 초 는 1 초의 10 억 분 의 1 이다.
    여기 서 몇 가지 대상 을 만 드 는 방법 을 간단하게 비교 하 였 습 니 다.테스트 결과 가 특별히 정확 하지 않 을 수도 있 습 니 다.관심 있 는 것 은.net framework 에서 테스트 할 수 있 습 니 다.도움 이 되 기 를 바 랍 니 다!
    관련 링크
    https://andrewlock.net/benchmarking-4-reflection-methods-for-calling-a-constructor-in-dotnet/
    https://github.com/dotnetcore/Natasha
    .NET 에서 대상 을 만 드 는 몇 가지 방식 과 비교 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은.NET 생 성 대상 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

    좋은 웹페이지 즐겨찾기