Microsoft MVP성태의 닷넷 이야기
.NET Framework: 908. C# - Source Generator 소개 [링크 복사], [링크+제목 복사],
조회: 23909
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)
(시리즈 글이 4개 있습니다.)
.NET Framework: 908. C# - Source Generator 소개
; https://www.sysnet.pe.kr/2/0/12223

.NET Framework: 909. C# - Source Generator를 적용한 XmlCodeGenerator
; https://www.sysnet.pe.kr/2/0/12228

.NET Framework: 1167. C# -Version 1 Source Generator 실습
; https://www.sysnet.pe.kr/2/0/12985

.NET Framework: 1168.  C# -IIncrementalGenerator를 적용한 Version 2 Source Generator 실습
; https://www.sysnet.pe.kr/2/0/12986




C# - Source Generator 소개

이 글의 내용은 Preview 버전을 기준으로 작성돼 현재 유효하지 않습니다. 정식 버전에서의 동작은 다음의 글에 설명했고,

C# -Version 1 Source Generator 실습
; https://www.sysnet.pe.kr/2/0/12985

이 글에 설명한 예제를 Version 1에 맞게 개정한 예제를 "vs2019_sg_sample_v1.zip" 파일로 새롭게 첨부했으니 그 프로젝트를 참고하시면 됩니다. (Visual Studio 2019/2022 모두에서 잘 동작합니다.)

또한, Version 1에 이어 C# 10 컴파일러부터는 Version 2 규약을 새롭게 지원합니다.

C# -IIncrementalGenerator를 적용한 Version 2 Source Generator 실습
; https://www.sysnet.pe.kr/2/0/12986




다음과 같은 소식이 있군요. ^^

Introducing C# Source Generators
; https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/

위의 글에서도 잘 설명하고 있지만, 그대로 따라해 보면서 한글로 다시 소개를 하겠습니다. ^^ 참고로, 저 글을 쓸 당시에는 비주얼 스튜디오에서 지원하지 않았지만, 현재 (16.6.1) 버전에서는 지원하므로 별도의 Preview 버전을 설치하지 않아도 무방합니다. (또한 위의 글에 따르면 .NET 5 Preview도 필요하다는데 제가 테스트한 바로는 .NET Core 3.1.2 환경에서 잘 동작했습니다.)




Source Generator가 뭔지는 위의 글에 포함된 그림에서 잘 설명하고 있습니다.

cs_src_gen_1.png

그러니까, 컴파일 시점에 개발자가 임의대로 다시 소스 코드를 구성해 (단, 기존 소스 코드의 변경은 불가능하지만) 끼워 넣을 수 있는 여지를 준 것입니다. 말로만 하면 심심하니 실습을 해보겠습니다.

우선, 콘솔 프로젝트를 하나 만들고 아래의 예제 코드를 입력합니다.

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            HelloWorldGenerated.HelloWorld.SayHello();
        }
    }
}

당연히, 위의 단계까지는 빌드하면 "error CS0103: The name 'HelloWorldGenerated' does not exist in the current context" 오류가 발생합니다. 이제부터 할 일은, 컴파일 시에 HelloWorldGenerated.HelloWorld 타입을 담는 소스 코드를 끼워 넣으면 되는데, 이 작업을 일종의 plug-in처럼 처리하게 됩니다. 이를 위해 ".NET Standard Library" 유형의 프로젝트를 생성하고 다음과 같이 .csproj 파일을 수정합니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.6.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
  </ItemGroup>

</Project>

또는 NuGet을 통해 패키지 참조를 해도 됩니다.

Install-Package Microsoft.CodeAnalysis.Analyzers -Version 3.0.0
Install-Package Microsoft.CodeAnalysis.CSharp.Workspaces -Version 3.6.0

이후 ISourceGenerator 인터페이스를 상속받아 "Generator" 특성이 붙은 타입을 정의하면,

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Text;

namespace HelloGenerator
{
    [Generator]
    public class MySourceGenerator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            // begin creating the source we'll inject into the users compilation
            var sourceBuilder = new StringBuilder(@"
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 compilation
            var syntaxTrees = context.Compilation.SyntaxTrees;

            // add the filepath of each tree to the class we're building
            foreach (SyntaxTree tree in syntaxTrees)
            {
                sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");");
            }

            // finish creating the source to inject
            sourceBuilder.Append(@"
        }
    }
}");

            // inject the created source into the users compilation
            context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
        }

        public void Initialize(InitializationContext context)
        {
            // No initialization required for this one
        }
    }
}

Roslyn의 컴파일 단계에 참여할 수 있는 plug-in이 완성됩니다. 남은 작업은, 위의 plug-in을 사용할 첫 번째 콘솔 프로젝트에 참조 추가를 하고, Preview 단계의 C# 컴파일러를 사용하도록 지정하면 되는데,

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\HelloGenerator\HelloGenerator.csproj" />
  </ItemGroup>

</Project>

특별히, plug-in이라는 점을 명시하기 위해 OutputItemType을 Analyzer로 설정하고, 또한 실제 실행 시 필요한 어셈블리는 아니므로 ReferenceOutputAssembly를 false로 지정해 줍니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\HelloGenerator\HelloGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>

</Project>

이렇게 하고 빌드하면, 컴파일 시점에 ISourceGenerator.Execute 메서드가 호출되고 그로 인해 HelloWorldGenerated.HelloWorld 타입이 정의되어 함께 빌드에 참여하게 되므로 예제 콘솔 프로젝트가 정상적으로 빌드가 됩니다.

그래서 예제 코드를 실행하면 다음과 같은 식의 출력을 볼 수 있습니다.

Hello from generated code!
The following syntax trees existed in the compilation that created this program:
 - D:\temp\ConsoleApp1\ConsoleApp1\Program.cs
 - D:\temp\ConsoleApp1\ConsoleApp1\obj\Debug\netcoreapp3.1\.NETCoreApp,Version=v3.1.AssemblyAttributes.cs
 - D:\temp\ConsoleApp1\ConsoleApp1\obj\Debug\netcoreapp3.1\ConsoleApp1.AssemblyInfo.cs

결과를 보면 알 수 있겠지만, 이러한 출력 결과는 일반적인 C#의 기존 코딩 방법으로는 할 수 없는 작업물입니다.

(첨부 파일은 이 글의 완전한 예제 프로젝트를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/27/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2020-07-13 05시32분
T4(TextTemplate) 와 비교해봤을 때 궁금한 점이 있습니다. 자동생성된 코드를 디버깅할 일이 있으면 T4같은 경우는 실행했을 때 tt 파일로 생성되는 cs 파일을 찾아 확인할 수 있는데요. SourceGenerator에서는 ReferenceOutputAssembly="true" 하여 생성되는 dll 을 decompile 하는 방법 밖에는 없는지요?
kernel
2020-07-13 06시46분
[kernel] 추후 제공할 예정이라고 하네요. ^^;
[guest]
2020-08-26 02시11분
New C# Source Generator Samples
 - CSV Generator
 - Mustache Generator
; https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
정성태
2021-01-28 10시26분
정성태
2021-03-03 02시10분
Visual Studio 2019 16.9 버전부터 솔루션 탐색기의 프로젝트 노드에서 "Analyzers" 하위에 "Source Generator로 생성된 소스 코드 목록을 보여줍니다.
정성태
2021-05-28 11시30분
Visual Studio 16.10 버전부터 "Launch" 항목에 "Roslyn Component"가 추가되어 Source Generator 개발에 대한 디버거 지원이 가능해졌습니다.

Visual Studio 2019 version 16.10 Release Notes
 - .NET Productivity
; https://learn.microsoft.com/en-us/visualstudio/releases/2019/release-notes#NETProductivity
정성태
2022-07-27 02시30분
[1234] 혹시
....bin\Debug\netstandard2.0\SourceGenerator.dll에서 만들 수 없습니다(파일이나 어셈블리 'Microsoft.CodeAnalysis, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' 또는 여기에 종속되어 있는 파일이나 어셈블리 중 하나를 로드할 수 없습니다. 지정된 파일을 찾을 수 없습니다.).
이렇게 dll을 찾을 수 없다고하면서 제너레이터가 정상동작하지 않고 있는데 어떤 문제로 보이시나요?
[guest]
2022-07-27 03시38분
Visual Studio 2022에서, 이 글에 있는 내용을 따라서 실습했는데 그런 오류가 발생한다는 건가요?
정성태
2022-07-27 04시01분
[1234] 죄송합니다 상황을 정확하게 말씀 안드렸네요
visual studio는 2019버전을 사용하고 있습니다.
그리고 이 글의 예제와 https://learn.microsoft.com/ko-kr/dotnet/csharp/roslyn-sdk/source-generators-overview 닷넷 문서의 예제도 따라해보았습니다.
그럼에도 동일하게
"....bin\Debug\netstandard2.0\SourceGenerator.dll에서 만들 수 없습니다(파일이나 어셈블리 'Microsoft.CodeAnalysis, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' 또는 여기에 종속되어 있는 파일이나 어셈블리 중 하나를 로드할 수 없습니다. 지정된 파일을 찾을 수 없습니다.)."
요런 warning 문구와 함께 정상동작하고있지 않습니다.

제너레이터 프로젝트는 .net standard 2.0
제너레이터를 참조하는 앱 프로젝트는 .net5.0 .net Framework 4.7.2 .NET Core 3.1.2 로 하였습니다.
[guest]
2022-07-27 06시15분
이번 글의 내용은 preview 버전을 기준으로 한 것이라 현재 유효하지 않습니다. 정식 버전에 바뀐 내용으로 새롭게 글을 작성한 것이 있으니,

C# -Version 1 Source Generator 실습
; https://www.sysnet.pe.kr/2/0/12985

위의 글을 참고해 다시 실습하시면 됩니다. 참고로, 위의 "Version 1" 문서에 따른 이번 글의 "HelloGenerator" 예제 프로젝트를 첨부 파일에 "vs2019_sg_sample_v1.zip"으로 올려 두었으니 그 프로젝트를 내려받아 빌드해 보시면 잘 돌아갈 것입니다.

혹시, 안 되는 부분이 있으면 이번 글의 덧글이 아닌 위의 12985 글에 대한 덧글로 남겨 주세요.

참고로, Version 2까지 나왔습니다.

 C# -IIncrementalGenerator를 적용한 Version 2 Source Generator 실습
; https://www.sysnet.pe.kr/2/0/12986
정성태

... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1755정성태9/22/201434275오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424608VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420604오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441067Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438933.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423828.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423730.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425379개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428428오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426097.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423034개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431051.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420985오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426957개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421332.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432502.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426517.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422042.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419755VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425568VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418162.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419825오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426351.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434465Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427075개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201422161개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...