원본 생성기

이 문서에서는 .NET Compiler Platform("Roslyn") SDK의 일부로 제공되는 원본 생성기에 대한 개요를 제공합니다. 원본 생성기를 사용하면 C# 개발자가 컴파일되는 사용자 코드를 검사할 수 있습니다. 생성기는 사용자의 컴파일에 추가되는 새 C# 원본 파일을 즉시 만들 수 있습니다. 이러한 방식으로 컴파일하는 동안 실행되는 코드가 있습니다. 프로그램을 검사하여 나머지 코드와 함께 컴파일되는 추가 소스 파일을 생성합니다.

원본 생성기는 C# 개발자가 작성할 수 있는 새로운 종류의 구성 요소로, 다음 두 가지 주요 작업을 수행할 수 있습니다.

  1. 컴파일되는 모든 사용자 코드를 나타내는 컴파일 개체를 검색합니다. 이 개체를 검사할 수 있으며 현재의 분석기와 마찬가지로 컴파일 중인 코드에 대한 구문 및 의미 모델과 함께 작동하는 코드를 작성할 수 있습니다.

  2. 컴파일 중에 컴파일 개체에 추가할 수 있는 C# 소스 파일을 생성합니다. 즉, 코드를 컴파일하는 동안 추가 소스 코드를 컴파일에 대한 입력으로 제공할 수 있습니다.

이러한 두 가지를 결합하면 원본 생성기가 매우 유용합니다. 컴파일하는 동안 컴파일러가 빌드하는 모든 풍부한 메타데이터를 사용하여 사용자 코드를 검사할 수 있습니다. 그런 다음 생성기는 분석한 데이터를 기반으로 하는 동일한 컴파일로 C# 코드를 다시 내보낸다. Roslyn 분석기에 익숙한 경우 원본 생성기를 C# 소스 코드를 내보낼 수 있는 분석기라고 생각할 수 있습니다.

원본 생성기는 아래에 시각화된 컴파일 단계로 실행됩니다.

원본 생성의 다양한 부분을 설명하는 그래픽

원본 생성기는 모든 분석기와 함께 컴파일러에 의해 로드되는 .NET Standard 2.0 어셈블리입니다. .NET Standard 구성 요소를 로드하고 실행할 수 있는 환경에서 사용할 수 있습니다.

중요

현재는 .NET Standard 2.0 어셈블리만 원본 생성기로 사용할 수 있습니다.

일반적인 시나리오

최신 기술에서 사용하는 분석을 바탕으로 사용자 코드를 검사하고 정보나 코드를 생성하는 대표적인 세 가지 방법이 있습니다.

  • 런타임 리플렉션,
  • MSBuild 작업 저글링,
  • (이 문서에서는 설명하지 않는) IL(중간 언어) 위빙입니다.

원본 생성기는 이들 각각의 방법보다 개선될 수 있습니다.

런타임 리플렉션

런타임 리플렉션은 오래 전에 .NET에 추가된 강력한 기술입니다. 이를 사용하는 수많은 시나리오가 있습니다. 일반적인 시나리오는 앱이 시작될 때 사용자 코드의 일부 분석을 수행하고 해당 데이터를 사용하여 작업을 생성하는 것입니다.

예를 들어 ASP.NET Core 웹 서비스가 처음 실행되면 리플렉션을 사용하여 정의한 구문을 검색하여 컨트롤러 및 razor 페이지와 같은 것을 “연결”할 수 있습니다. 이렇게 하면 강력한 추상화로 간단한 코드를 작성할 수 있지만 런타임에 성능 저하가 발생합니다. 웹 서비스 또는 앱이 처음 시작될 때 코드에 대한 정보를 검색하는 모든 런타임 리플렉션 코드 실행이 완료될 때까지 요청을 수락할 수 없습니다. 이 성능 저하는 크지 않지만 자체 앱에서 개선할 수 없는 고정 비용입니다.

원본 생성기를 사용하면 시작의 컨트롤러 검색 단계가 컴파일 시간에 대신 발생할 수 있습니다. 생성기는 소스 코드를 분석하고 앱을 "연결"하는 데 필요한 코드를 내보낼 수 있습니다. 원본 생성기를 사용하면 오늘 런타임에 발생하는 작업이 컴파일 시간으로 푸시될 수 있으므로 시작 시간이 더 빨라질 수 있습니다.

MSBuild 작업 저글링

원본 생성기는 런타임 시 리플렉션에 제한되지 않도록 성능을 개선하여 형식도 검색할 수 있습니다. 일부 시나리오에서는 컴파일에서 데이터를 검사할 수 있도록 MSBuild C# 작업(CSC라고 함)을 여러 번 호출합니다. 짐작하시겠지만, 컴파일러를 두 번 이상 호출하면 앱을 구축하는 데 걸리는 총 시간이 영향을 받습니다. 원본 생성기는 일부 성능 혜택을 제공할 뿐만 아니라 도구가 올바른 추상화 수준에서 작동할 수 있도록 해주므로, 이와 같이 MSBuild 작업을 저글링할 필요가 없도록 원본 생성기를 사용할 방법을 조사하고 있습니다.

원본 생성기에서 제공할 수 있는 또 다른 기능은 컨트롤러와 razor 페이지 간의 ASP.NET Core 라우팅이 작동하는 방식과 같은 일부 "문자열 형식" API의 사용을 없애는 것입니다. 원본 생성기를 사용하면 컴파일 시간 세부 정보로 생성되는 데 필요한 문자열로 라우팅을 강력하게 입력할 수 있습니다. 이렇게 하면 잘못 입력된 문자열 리터럴이 요청이 올바른 컨트롤러에 맞지 않는 횟수를 줄입니다.

원본 생성기 시작

이 가이드에서는 ISourceGenerator API를 사용하여 원본 생성기를 만드는 방법을 살펴봅니다.

  1. .NET 콘솔 애플리케이션을 만듭니다. 이 예제에서는 .NET 6를 사용합니다.

  2. Program 클래스를 다음 코드로 바꿉니다. 다음 코드는 최상위 문을 사용하지 않습니다. 이 첫 번째 소스 생성기는 해당 클래스에 부분 메서드를 작성하므로 클래식 양식이 Program 필요합니다.

    namespace ConsoleApp;
    
    partial class Program
    {
        static void Main(string[] args)
        {
            HelloFrom("Generated Code");
        }
    
        static partial void HelloFrom(string name);
    }
    

    참고

    이 샘플을 있는 그대로 실행할 수 있지만 아무 일도 발생하지 않습니다.

  3. 지금부터는 partial void HelloFrom 메서드를 구현하는 원본 생성기 프로젝트를 만들겠습니다.

  4. TFM(대상 프레임워크 모니커)을 netstandard2.0 대상으로 하는 .NET 표준 라이브러리 프로젝트를 만듭니다. NuGet 패키지 Microsoft.CodeAnalysis.AnalyzersMicrosoft.CodeAnalysis.CSharp를 추가합니다.

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
      </ItemGroup>
    
    </Project>
    

    원본 생성기 프로젝트는 netstandard2.0 TFM를 대상으로 해야 합니다. 그렇지 않으면 정상적으로 작동하지 않습니다.

  5. 다음과 같이 자체 원본 생성기를 지정하는 HelloSourceGenerator.cs라는 새 C# 파일을 만듭니다.

    using Microsoft.CodeAnalysis;
    
    namespace SourceGenerator
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Code generation goes here
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    

    원본 생성기는 Microsoft.CodeAnalysis.ISourceGenerator 인터페이스를 구현하고 Microsoft.CodeAnalysis.GeneratorAttribute가 있어야 합니다. 모든 원본 생성기에 초기화가 필요한 것은 아니며, 이 예제 구현의 경우입니다. 여기서 ISourceGenerator.Initialize 은 비어 있습니다.

  6. ISourceGenerator.Execute 메서드의 내용을 다음 구현으로 바꿉니다.

    using Microsoft.CodeAnalysis;
    
    namespace SourceGenerator
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Find the main method
                var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
    
                // Build up the source code
                string source = $@"// <auto-generated/>
    using System;
    
    namespace {mainMethod.ContainingNamespace.ToDisplayString()}
    {{
        public static partial class {mainMethod.ContainingType.Name}
        {{
            static partial void HelloFrom(string name) =>
                Console.WriteLine($""Generator says: Hi from '{{name}}'"");
        }}
    }}
    ";
                var typeName = mainMethod.ContainingType.Name;
    
                // Add the source code to the compilation
                context.AddSource($"{typeName}.g.cs", source);
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    

    context 개체에서는 컴파일 진입점 또는 Main 메서드에 액세스할 수 있습니다. mainMethod 인스턴스는 IMethodSymbol이며, 메서드 또는 메서드형 기호(생성자, 소멸자, 연산자 또는 속성/이벤트 접근자 포함)를 나타냅니다. 메서드는 Microsoft.CodeAnalysis.Compilation.GetEntryPoint 프로그램의 진입점에 대한 를 반환 IMethodSymbol 합니다. 다른 메서드를 사용하면 프로젝트에서 메서드 기호를 찾을 수 있습니다. 이 개체에서 포함하는 네임스페이스(있는 경우)와 형식에 대해 추론할 수 있습니다. 이 예제의 는 source 보간된 구멍이 포함된 네임스페이스 및 형식 정보로 채워지는 생성할 소스 코드를 템플릿하는 보간된 문자열입니다. source는 힌트 이름과 함께 context에 추가됩니다. 이 예제에서 생성기는 콘솔 애플리케이션에서 메서드의 partial 구현을 포함하는 생성된 새 소스 파일을 만듭니다. 원본 생성기를 작성하여 원하는 원본을 추가할 수 있습니다.

    GeneratorExecutionContext.AddSource 메서드의 hintName 매개 변수는 고유한 이름이라면 무엇이든 가능합니다. 대부분의 경우에는 이름에 ".g.cs"".generated.cs" 같은 명시적 C# 파일 확장명을 제공합니다. 파일 이름을 사용하면 원본으로 생성 중인 파일을 식별할 수 있습니다.

  7. 이제 작동하는 생성기가 생겼지만, 이를 콘솔 애플리케이션에 연결해야 합니다. 원래 콘솔 애플리케이션 프로젝트를 편집하고 다음을 추가하여 프로젝트 경로를 위에서 만든 .NET Standard 프로젝트의 경로로 바꿉니다.

    <!-- Add this as a new ItemGroup, replacing paths and names appropriately -->
    <ItemGroup>
        <ProjectReference Include="..\PathTo\SourceGenerator.csproj"
                          OutputItemType="Analyzer"
                          ReferenceOutputAssembly="false" />
    </ItemGroup>
    

    이 새 참조는 기존 프로젝트 참조가 아니며 및 ReferenceOutputAssembly 특성을 포함 OutputItemType 하도록 수동으로 편집해야 합니다. 의 OutputItemType 및 특성에 대한 자세한 내용은 일반적인 MSBuild 프로젝트 항목: ProjectReference를 참조하세요.ReferenceOutputAssemblyProjectReference

  8. 이제 콘솔 애플리케이션을 실행하면 생성된 코드가 실행되어 화면에 출력되는 것을 볼 수 있습니다. 콘솔 애플리케이션 자체는 메서드를 HelloFrom 구현하지 않고 원본 생성기 프로젝트에서 컴파일하는 동안 생성된 원본입니다. 다음 텍스트는 애플리케이션의 출력 예제입니다.

    Generator says: Hi from 'Generated Code'
    

    참고

    IntelliSense를 확인하고 오류를 제거하려면 Visual Studio를 다시 시작하여 도구 환경을 적극적으로 개선해야 할 수 있습니다.

  9. Visual Studio 사용하는 경우에는 원본에서 생성한 파일을 볼 수 있습니다. 솔루션 탐색기 창에서 종속성>분석기> SourceGeneratorSourceGenerator.HelloSourceGenerator>를 확장하고 Program.g.cs 파일을 두 번 클릭합니다.

    Visual Studio: 솔루션 탐색기 원본 생성 파일.

    이 생성된 파일을 열면 Visual Studio에서 파일이 자동으로 생성되고 편집할 수 없음을 나타냅니다.

    Visual Studio: 자동 생성된 Program.g.cs 파일.

  10. 빌드 속성을 설정하여 생성된 파일을 저장하고 생성된 파일이 저장되는 위치를 제어할 수도 있습니다. 콘솔 애플리케이션의 프로젝트 파일에서 요소를 <PropertyGroup>에 추가하고 <EmitCompilerGeneratedFiles> 해당 값을 true로 설정합니다. 프로젝트를 다시 빌드합니다. 이제 생성된 파일은 obj/Debug/net6.0/generated/SourceGenerator/SourceGenerator.HelloSourceGenerator 아래에 만들어집니다. 경로의 구성 요소는 빌드 구성, 대상 프레임워크, 원본 생성기 프로젝트 이름 및 생성기의 정규화된 형식 이름에 매핑됩니다. 애플리케이션의 프로젝트 파일에 요소를 추가하여 <CompilerGeneratedFilesOutputPath> 더 편리한 출력 폴더를 선택할 수 있습니다.

다음 단계

원본 생성기 Cookbook은 이러한 예제 중 일부를 해결하는 몇 가지 권장 방법을 설명합니다. 또한 직접 시도해 볼 수 있는 GitHub에서 사용할 수 있는 샘플 세트도 있습니다.

다음 문서에서 원본 생성기에 대해 자세히 알아볼 수 있습니다.