Source Generators를 사용하면 C# 개발자가 컴파일되는 사용자 코드를 검사(Inspect)할 수 있다. Source Generators는 컴파일 과정중에 새 C# 코드를 추가 할 수 있다. 이러한 방식으로 컴파일 과정둥에 동작하는 코드가 있다면, 해당 코드는 프로그램을 검사(Inspect)하고 새로운 코드를 생성하여 기존에 있는 코드와 같이 컴파일 되도록 한다.
Source Generators는 개발자에게 새로운 두가지에 기능을 제공한다.
코드에서 컴파일된 객체(compilation object)를 검색(Retrieve) 할 수 있다. 해당 객체는 검사 할 수 있으며 syntax and semantic models과 함께 동작하는 코드를 작성 할 수 있다.
컴파일 과정중에 새로운 객체를 추가 할 수 있다. 다른 말로 표현하면, 컴파일 과정중에 새로운 소스코드를 추가할 수 있다는 얘기이다.
Source Generators는 아래의 그림처럼 동작한다.
Source Generators 동작 다이어그램
일반적인 적용 방법
런타임 리플렉션
Juggling MSBuild tasks.
Intermediate Language (IL) weaving (본 글에서는 다루지 않는다).
usingMicrosoft.CodeAnalysis;namespaceSourceGenerator{ [Generator]publicclassHelloSourceGenerator:ISourceGenerator{publicvoidExecute(GeneratorExecutionContextcontext){// Code generation goes here}publicvoidInitialize(GeneratorInitializationContextcontext){// No initialization required for this one}}}
// HelloWorldGernerator.csusingSystem.Text;usingMicrosoft.CodeAnalysis;usingMicrosoft.CodeAnalysis.Text;namespaceSourceGeneratorSamples{ [Generator]publicclassHelloWorldGenerator:ISourceGenerator{publicvoidExecute(GeneratorExecutionContextcontext){// begin creating the source we'll inject into the users compilationStringBuildersourceBuilder=newStringBuilder(@"
using System;
namespace HelloWorldGenerated
{
public static class HelloWorld
{
public static void SayHello()
{
Console.WriteLine(""Hello from generated code!"");
Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");// using the context, get a list of syntax trees in the users compilationIEnumerable<SyntaxTree>syntaxTrees=context.Compilation.SyntaxTrees;// add the filepath of each tree to the class we're buildingforeach(SyntaxTreetreeinsyntaxTrees){sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");");}// finish creating the source to injectsourceBuilder.Append(@"
}
}
}");// inject the created source into the users compilationcontext.AddSource("helloWorldGenerated",SourceText.From(sourceBuilder.ToString(),Encoding.UTF8));}publicvoidInitialize(GeneratorInitializationContextcontext){// No initialization required}}}
public void Execute(GeneratorExecutionContext context)에 생성할 코드에 대한 내용을 채운다. public void Initialize(GeneratorInitializationContext context)는 syntax and semantic models을 캐싱하여 사용할 수 있다. 이는 Execute()가 한 번만 불리는 것이 아니라 여러 번 불릴 수 있기 때문이라고 추측한다.
UseHelloWorldGernerator
생성된 코드를 사용할 수 있는 프로젝트를 새로 생성한다. 본 글에서는 Console Project를 가정한다.
1
2
3
4
5
6
7
8
9
10
11
12
// UseHelloWorldGernerator.csnamespaceGeneratedDemo{publicstaticclassUseHelloWorldGenerator{publicstaticvoidRun(){// The static call below is generated at build time, and will list the syntax trees used in the compilationHelloWorldGenerated.HelloWorld.SayHello();}}}
컴파일 과정에서 생성될 HelloWorldGenerated를 사용하는 코드를 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Program.csusingSystem;namespaceGeneratedDemo{classProgram{staticvoidMain(string[]args){// Run the various scenariosConsole.WriteLine("Running HelloWorld:\n");UseHelloWorldGenerator.Run();}}}