🛴

[C#]VS2022を使用したSource Generator入門

2023/12/30に公開

C#には Source Generator というメタプログラミング機能があります。

簡単に言えば、

  • コンパイルが実行される前(人間がコーディングをしている最中)に
  • Source Generator がC#のソースコードを勝手に書いてくれる
    という機能です。

メタプログラミングという性質上、万人が使う機能ではありません。初学者向けの情報は非常に少ないと思います。

さらに通常とは異なる開発環境も必要と来たもんだ。

これでは「開発」というスタートラインに立つのが非常に難しい、と思います。(参考:スタート地点に立とうと藻掻いた形跡はこちら

そこで理屈抜きで「こうしたら開発環境ができるよ」という手順だけをお届けします。
(なぜこうする必要があるのかについては本記事では説明しません。あしからず)

また、2023年12月現在の情報であることにも留意ください。今後変わる可能性は高いです。

.NET Compiler SDK のインストール

Visual Studio Installer を使用して「.NET Compiler SDK」をインストールしてください。

大体の人は同SDKをインストールしていないはずです。(普通は要らないからしょうがない)

新規プロジェクトの作成

クラスライブラリを「.net standard 2.0」で新規作成してください。

.NET6とか.NET8とかは使用できません。必ず「.net standard 2.0」を選択してください。

csprojの上書き

プロジェクトを作成したら、プロジェクトファイルを開き、以下の内容で上書きしてください。

変更前

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

変更後

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>12</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <AnalyzerLanguage>cs</AnalyzerLanguage>
    <Nullable>enable</Nullable>
    <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);PackBuildOutputs</TargetsForTfmSpecificContentInPackage>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <DevelopmentDependency>true</DevelopmentDependency>
    <IncludeSymbols>false</IncludeSymbols>
    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
    <IsRoslynComponent>true</IsRoslynComponent>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  <!-- for nuget publish -->
  <Target Name="PackBuildOutputs" DependsOnTargets="SatelliteDllsProjectOutputGroup;DebugSymbolsProjectOutputGroup">
    <ItemGroup>
      <TfmSpecificPackageFile Include="$(TargetDir)\*.dll" PackagePath="analyzers\dotnet\cs" />
      <TfmSpecificPackageFile Include="@(SatelliteDllsProjectOutputGroupOutput->'%(FinalOutputPath)')" PackagePath="analyzers\dotnet\cs\%(SatelliteDllsProjectOutputGroupOutput.Culture)\" />
    </ItemGroup>
  </Target>
</Project>

IncrementalGeneratorクラスの追加

クラス名は任意です。お好きな名称で記述して下さい。

中身は空でいいです。

using Microsoft.CodeAnalysis;

[Generator(LanguageNames.CSharp)]
public partial class HogeGenerator : IIncrementalGenerator
{
  public void Initialize(IncrementalGeneratorInitializationContext context)
  {
    //TODO
  }
}

上記のコードを書くとエラー一覧にRS1036警告が出ることがあります。

RS1036 'HogeGenerator': アナライザーまたはソース ジェネレーターを含むプロジェクトでは、プロパティ '<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>' を指定する必要があります HogeSourceGenerator

警告文を見る限り、csprojに設定をしろと読めるのですが、設定すると動かくなった経験もありますので、気持ちは悪いですが無視して作業を進めてください

ユニットテストプロジェクトの追加

xUnitなどのテストプロジェクトを追加し、SourceGeneratorプロジェクトを参照してください。なお、テストプロジェクトは.NET6、.NET8を使用して構いません。(テストプロジェクトを .net standard 2.0 にする必要はありまえん)

そして、ユニットテストのcsprojを開き、以下のように加工してください。

変更前(プロジェクト参照のみ抜粋)

<ItemGroup>
  <ProjectReference Include="HogeSourceGenerator.csproj" />
</ItemGroup>

変更後

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

同じプロジェクトを2回参照していますが、これで合っています。

デバッグプロファイラの追加と選択

デバッグのプロパティをクリックします。場所が分かりづらいので、以下の画像を参考にしてください。

新しいプロファイルを「Roslyn Component」で作成します。

Target Project に先程追加したテストプロジェクトを追加しておきます。

ここまでできたらダイアログを閉じます。

そして、デバッグプロファイラを変更します。

デバッグの開始

あとはHogeGeneratorにブレイクポイントを追加して、デバッグを実行してみましょう。

ブレイクポイントでブレイクが発生すれば環境構築成功です。

ここから先は・・・

ここまでくればスタート地点には立てています。あとは開発をするだけです。

本記事で紹介するのは、ここまでです。

あとは他のサイトを紹介して終わりたいと思います。

環境構築参考サイト

2022年のC# (Incremental) Source Generator開発手法
https://neue.cc/2022/12/16_IncrementalSourceGenerator.html

ものすごい参考になりました。私の記事は上記記事の手順だけを書いたも同然です。

開発方法参考サイト

PrivateProxy
https://github.com/Cysharp/PrivateProxy

「開発環境参考サイト」でも紹介したneuecc氏作成のOSSです。

同ライブラリについての概要はこちら参照。

.NET 8 UnsafeAccessor を活用したライブラリ PrivateProxy を公開しました
https://neue.cc/2023/09/21_privateproxy.html

PrivateProxyというライブラリを公開しました。つまるところ、privateフィールド/プロパティ/メソッドにアクセスするライブラリ

上記サイトを読んだ上で、以下のソースコードを読んでください。

https://github.com/Cysharp/PrivateProxy/blob/main/src/PrivateProxy.Generator/PrivateProxyGenerator.cs

これで何をどうしたらいいか、ざっくりわかります。

PrivateProxyのソースコードをDLして、デバッグ実行するのも非常に効果的です。

開発時の注意点

下記は「開発時」特有の注意点です。ライブラリ利用時には、このような現象はありません。

その1

アナライザーに赤丸印が付きますが、気にしないでください。

その2

アナライザーに自動生成されたソースコードが表示されます。このソースコードは以下の特性があります。

  • Visual Studio を開くといつの間にか勝手に作成している
  • IncrementalGeneratorクラスのコードを変更しても更新はされない

更新したい場合はプロジェクトファイルを一度終了させてから、開き直してください。

Discussion