C# 9.0 のソース ジェネレーターで遊んだ

WPF, Xamarin.Forms, UWP で開発する人にとっては毎回頭を悩ませる `INofityPropertyChanged` インターフェースの実装を題材にして C# 9.0 のソース ジェネレーターで遊んでみました。

因みに公式のサンプルにも既に `INotifyPropertyChanged` の実装を自動生成するサンプルはあったりします。

[Introducing C# Source Generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/a7c376dc-3127-aeb5-0ea2-1ed303891913.png)

こちらのブログからたどれるサンプル集の中に `AutoNotifyGenerator.cs` というのがあって、それがサンプルでの `INotifyPropertyChanged` の実装を生成するソース ジェネレーターです。

[AutoNotifyGenerator.cs](https://github.com/dotnet/roslyn-sdk/blob/master/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs)

これは本当に基本的な機能しかないので、これを参考にちょっと自分なりのを作ってみました。

ジェネレーター自体は所定の NuGet パッケージを参照追加して `ISourceGenerator` を実装したクラスに `Generator` 属性つけるくらいなので簡単なのですが、人が記述したソースコードを参照するための roslyn 系の `XxxxDeclarationSyntax` や `IXxxxSymbol` あたりの使い方を調べるのが大変です。なかなか情報がない…。

## 作ったもの

愚痴は置いておいて今回試しに作ってみたのは以下のようなコードを書いておくと

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

    [AutoNotify]
    private string _lastName;

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

こんな感じのソースを生成してくれるものです。(厳密には、ちょっと違いますがこんなイメージ）

```csharp
public partial class MainPageViewModel : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  // PropertyChangedEventArgs が無駄に沢山作られないように一応配慮
  private static readonly PropertyChangedEventArgs XxxxPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(Xxxx));
  private static readonly PropertyChangedEventArgs LastNamePropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(LastName));
  private static readonly PropertyChangedEventArgs FullNamePropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(FullName));

  public string Xxxx // AutoNotify でプロパティ名を明示的に指定したらそれを使う
  {
    get => this._firstName;
    set
    {
      if (EqualityComparer<string>.Default.Equals(this._firstName, value)) { return; }
      this._firstName = value;
      PropertyChanged?.Invoke(this, XxxxPropertyChangedEventArgs);
      // FullName プロパティに AutoNotify がついてるので、FullName プロパティで参照しているプロパティが変更されたときに自動で変更通知とばす
      PropertyChanged?.Invoke(this, FullNamePropertyChangedEventArgs);
      
      XxxxChanged();
    }
  }

  partial void XxxxChanged();

  public string LastName // 普通はフィールド名からいい感じにプロパティ名作る
  {
    get => this._lastName;
    set
    {
      if (EqualityComparer<string>.Default.Equals(this._lastName, value)) { return; }
      this._lastName= value;
      PropertyChanged?.Invoke(this, LastNamePropertyChangedEventArgs);
      // FullName プロパティに AutoNotify がついてるので、FullName プロパティで参照しているプロパティが変更されたときに自動で変更通知とばす
      PropertyChanged?.Invoke(this, FullNamePropertyChangedEventArgs);
      
      LastNameChanged();
    }
  }

  partial void LastNameChanged();
}
```

勉強用としては、いい題材だったと思います。ソースコードは GitHub にあげておきました。

https://github.com/runceel/MvvmGenerator

実際に使えるようにしようと思うと、後は DataAnnotations くらいには対応したいかな？？

作ってる過程で岩永さんが、すごい参考になるアナライザーを作ってたということを教えてくれたので、参考にさせてもらいました。多謝！！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">一応、1回通しで computed property の中身追って参照されてるプロパティを自動判定ってのはできはするというか、 <a href="https://t.co/MILha1ATUS">https://t.co/MILha1ATUS</a> では自動判定やってたり。</p>&mdash; ++C++; // 管理人: 岩永 (@ufcpp) <a href="https://twitter.com/ufcpp/status/1311616871045554176?ref_src=twsrc%5Etfw">October 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>