ソース ジェネレーターをWPFで使う場合の注意点

# 概要

C# 9.0のソースジェネレーターの実例としてよく挙げられるのが、`INotifyPropertyChanged`です。
そこで実際にWPFに組み込もうとしたところ、結構苦労したので、その注意点を説明します。

# 組み込むソースジェネレータ

`INotifyPropertyChanged`を実装するソースジェネレータは [公式サンプル](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/)もありますが、より多機能な@okazukiさんのプロジェクトを組み込みます。

https://qiita.com/okazuki/items/a56bdedb63c8596b899c

# テストアプリ

題材とするテストアプリです。ViewModelは上記の記事のままです。ソースジェネレータを組み込んでいないので、まだビルドすることはできません。

### ViewModel

```csharp
public partial class MainWindowViewModel
{
    [AutoNotify("Xxxx")]
    private string _firstName;

    [AutoNotify]
    private string _lastName;

    [AutoNotify]
    public string FullName => $"{Xxxx} {LastName}";
}
```

### View

```xml
<Window
   x:Class="MvvmGenerator.WpfTests.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="clr-namespace:MvvmGenerator.WpfTests"
   Width="300" Height="250">
   <Window.DataContext>
      <local:MainWindowViewModel />
   </Window.DataContext>
   <StackPanel>
      <TextBox Text="{Binding Xxxx}" />
      <TextBox Text="{Binding LastName}" />
      <TextBlock Text="{Binding FullName}" />
   </StackPanel>
</Window>
```

# ソースジェネレータプロジェクト

コンソールアプリケーションではプロジェクト参照でも動きますが、現時点（2021/04/11）ではWPFでは動きません。

## nugetパッケージ化

まず、ソースジェネレータプロジェクトをnugetパッケージ化する必要があります。
通常のパッケージとは違うので、いくつか変更するところがあります。

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>preview</LangVersion>
    <Nullable>enable</Nullable>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <IncludeSymbols>false</IncludeSymbols>
    <DevelopmentDependency>true</DevelopmentDependency>
    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>    <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage>
    <Version>1.3.0</Version>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.0" PrivateAssets="all" />
  </ItemGroup>

  <Target Name="_AddAnalyzersToOutput">
    <ItemGroup>
      <TfmSpecificPackageFile Include="$(OutputPath)\MvvmGenerator.dll" PackagePath="analyzers/dotnet/cs" />
    </ItemGroup>
  </Target>
</Project>
```



| プロパティ名                          | 値                               | 説明                                                     |
| ------------------------------------- | -------------------------------- | -------------------------------------------------------- |
| IncludeBuildOutput                    | false                            | パッケージに通常のdll組み込みはしない                    |
| IncludeSymbols                        | false                            | パッケージにシンボルファイル(.pdb)は含めない             |
| SuppressDependenciesWhenPacking       | true                             | プロジェクトで使用しているパッケージの依存関係は含めない |
| DevelopmentDependency                 | true                             | 開発用のパッケージとして認識されるようにする。           |
| TargetsForTfmSpecificContentInPackage | $(Targ... | パッケージに組み込むカスタムターゲットを指定する         |

`TargetsForTfmSpecificContentInPackage`から下記のカスタムターゲットを指定します。

```xml
<Target Name="_AddAnalyzersToOutput">
  <ItemGroup>
    <TfmSpecificPackageFile Include="$(OutputPath)\MvvmGenerator.dll" PackagePath="analyzers/dotnet/cs" />
  </ItemGroup>
</Target>
```

.csprojファイルの編集後ビルドして、.nupkgファイルを作成します。

作成したnupkgファイルの中を見てみると、通常のnupkgファイルとは異なった構成になっているのが分かります。

![image-20210411163613090.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120077/b6bc7d61-d117-45c6-009f-12fb4c6074c2.png)

## ローカルでパッケージ配布

作成したパッケージはnuget.orgを使用してもいいですが、自分が簡単に使うだけなら、ローカルフォルダに置いてnugetレポジトリとして、VisualStudioに登録することで使えるようになります。

詳しいやり方は以下を参照してください。

https://kiyokura.hateblo.jp/entry/20121219/p1

# WPFプロジェクト

## .csproj編集

作成したnugetパッケージをWPFプロジェクトに追加します。
ここでも.csprojファイルの修正が必要です。

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
   <IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MvvmGenerator" Version="1.3.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>

```

`<IncludePackageReferencesDuringMarkupCompilation>`を`true`に設定します。この長いプロパティは最近追加されたもののようで、まだMicrosoft Docsにも含まれていません。

## 動作確認

この状態でビルドして動作させます。

2つの`TextBox`に入力された内容が、下の`TextBlock`に反映されることから、`INotifyPropertyChanged`が正しく実装されていることが分かります。

![image-20210411172220272.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120077/f3d2314e-c498-0725-0c95-8967f72e9522.png)



# 参考

https://github.com/ufcpp/ValueChangedGenerator/
https://zenn.dev/naminodarie/articles/32973a36fcbe99

https://github.com/dotnet/wpf/issues/4067

https://github.com/dotnet/wpf/issues/3963

# ソースコード

今回の完全なソースコードをGithubにおいておきます。okazukiさんの記事のレポジトリをフォークしたものです。
https://github.com/soi013/MvvmGenerator

# 環境

VisualStudio 2019 Version 16.9.3
.NET 5
C# 9.0

# 謝辞

WPFで組み込もうとしたところ、うまく動かなかったので、@okazukiさんに助けてもらいました。ありがとうございました。