DependencyPropertyを実装するSource Generatorを作った話

VisualStudio 2022 preview1.0が公開されましたね.  新しいものというのはやる気を呼び起こしてくれるもので, 今回はSource Generatorをリリースできました.

https://github.com/TetsuOtter/TR.SourceGenerator.DependencyPropertyGen

# TL;DR (DependencyPropertyGenの使用について)
.NET 6.0 SDK (v6.0.100-preview.5) for Windows x64にて動作を確認しています.  
**.NET 5.0 SDK (v5.0.301)では動作しません(ｵｲ**

NuGetからライブラリを追加してクラスに属性を付けるだけで, 任意の依存関係プロパティ(DependencyProperty)を自動実装するライブラリです.  ライブラリ自体はMITライセンスですし, 生成コードに権利は主張しないので, お気軽にご利用ください.

https://www.nuget.org/packages/TR.SourceGenerator.DependencyPropertyGen

## 使用例

```C#:SampleControl.cs
using System.Windows.Controls;

namespace TR.SourceGenerator.DependencyPropertyGen.Sample
{
	//DependencyPropertyを自動実装するために属性を付ける
	[DependencyPropertyGen(typeof(string), "MyText")]
	public partial class SampleControl : Control
	{
		//略
	}
}
```

# 動作環境(重要)
TL;DRにも書きましたが, 重要なのでここにも書きます.

**.NET 5.0 SDK (v5.0.301)では動作しません!**

v5.0.301 (…というかMicrosoft Build Engine version 16.10.1?) でビルドしようとすると, 「error CS0616: 'TR.SourceGenerator.DependencyPropertyGen' は属性クラスではありません」って怒られます.  自分の実装が悪いんでしょうけど, どうせ半年後には.NET6になって解消されるでしょうと期待して, 機能追加を優先するつもりです.

---
以下, 開発にまつわる(?)話です

# 作成のきっかけ
さて, 依存関係プロパティの実装が面倒だと思ったことはありませんか?  あれって最短2行で書けますけど, それでもやっぱり自分には面倒に感じてしまいます (Cならマクロで簡単になるのに…って)

INotifyPropertyChangedの実装を行うSource Generatorはサンプルもありますし, たぶんどこかで配布もされているのではないかと思います.  しかし, パッと探しただけだと依存関係プロパティを自動実装するSource Generatorは見当たりませんでした (それって需要が(ry

依存関係プロパティなんて自分でコントロールを作るときぐらいしか使わないでしょうし, 加えて自分でコントロールを作る機会ってほとんどないとは思いますが, 面倒だと思ったら改善したくなるもので, 今回作ってみました.

# 作り方を調べた
さすがにドキュメントを読んだだけで作れるほどの力はないので, とにかく解説サイトやサンプルコード, 実際にSource Generatorを作成された方のコードを参考にさせていただきました.

以下, 参考にしたWebページなどです.

## 実装関連

https://github.com/RyotaMurohoshi/ValueObjectGenerator

https://github.com/dotnet/roslyn-sdk/blob/main/samples/CSharp/SourceGenerators

https://aonasuzutsuki.hatenablog.jp/entry/2019/05/07/104305

## プロジェクト設定関連

https://stackoverflow.com/questions/65010453/how-to-pack-a-c-sharp-9-source-generator-and-upload-it-to-the-nuget

http://neue.cc/2020/12/15_597.html

# 実現方法(実際の使い方)を検討
C# Source Generatorでは, 既存のコードの削除/改変を行えません.  できるのは新規追加のみです.  従って, 実現する使用方法として, 次の2つを考えました.

1. 1つの依存関係プロパティに対して1つのAttributeを自動実装先のクラスに付与し, Attributeの引数にて型やプロパティ名等の情報を与えて生成させる方法
2. Attributeの引数で指定されたinterfaceに宣言されたプロパティを, すべて依存関係プロパティとしてコード生成する方法

1の方法だと, Source Generatorの実装が簡単であり, またプロパティごとの設定(メタデータの指定など)を簡単に実現可能であるというメリットがありますが, 自動実装させるプロパティの数だけ属性を付ける必要があり, あまり美しくありません.  加えて, これは実装してから判明したことですが, 自動実装されたAttributeにVisualStudioのコード補完は効かないらしく, 若干不便です.  まぁでもこれは将来的に改善されるかもしれませんが.

2の方法だと, Source Generatorの実装は面倒ですが, Attributeを目的のクラスに1つ追加するだけで実装ができるため, コードがシンプルになります.


今回は, まだRoslyn SDKを用いた構文解析の使い方を熟知していないため, 1の方法で機能を実現することにしました.  
将来的には2の方法も使用できるようにしたいです.

# 実装してみた
とりあえず, 実際に世に出ているSource Generatorのコードを読んで, 改造して, どのように動くかの理解に努めました.

主にRyotaMurohoshiさんのコード([RyotaMurohoshi/ValueObjectGenerator](https://github.com/RyotaMurohoshi/ValueObjectGenerator))を参考に, 公式サンプルも見つつ実装したので, 自分でも完全に理解しきれているとは言えませんが, とりあえず形にはなっているんじゃないかなと思います.

https://github.com/TetsuOtter/TR.SourceGenerator.DependencyPropertyGen

# IDEで生成コードを見れるのに…
初めはVS2019 Community版で作業していましたが, なぜか「IDEでは生成されたコードを確認できるのに, いざビルドにかけると"`DependencyPropertyGen`はAttributeクラスちゃうで"と怒られる」という現象に見舞われ…  
原因を調べることも考えましたが, 他にやりたいこともあったので, とりあえず放置することにしました.

で, VS2022 previewがリリースされて久々にやる気が出たので, VS2022 preview (正確には.NET6 SDK?)でちょっと修正したりしたらうまく動いたという  
相変わらず.NET5 SDK環境だとうまく動かないんですが, 今回はこの修正を後回しにしてひとまずリリースすることにしました.

# ライセンス
DependencyPropertyGen自体にはMITライセンスを設定しています.

生成されたコードに著作権を主張できるとは思えませんが, もし仮に著作権を主張できる場合も, 私からは生成コードに対してCC0ライセンスを適用することにしています.

# 使用方法
例えば, 次のようなコードを用意したとします.

```C#:SampleControl.cs
using System.Windows.Controls;

namespace TR.SourceGenerator.DependencyPropertyGen.Sample
{
	//DependencyPropertyを自動実装するために属性を付ける
	[DependencyPropertyGen(typeof(string), "MyText")]
	public partial class SampleControl : Control
	{
		//略
	}
}
```

この場合, 自動生成されたソースコードは次のようになります.  

```C#:TR-SourceGenerator-DependencyPropertyGen-Sample-SampleControl-MyText.cs
using System.Windows;
namespace TR.SourceGenerator.DependencyPropertyGen.Sample
{
	public partial class SampleControl
	{
		public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register(nameof(MyText), typeof(string), typeof(SampleControl_Base) );


		public string MyText
		{
			get => (string)GetValue(MyTextProperty);
			 set => SetValue(MyTextProperty, value);
		}
	}
}
```

プロパティのgetterについて, 参照型のキャストは`as`を使うと良いんでしょうが, 参照型かどうかの判別が面倒だったので, 今回は採用を見送りました.

さて, 使用できる属性の引数パターンは次のとおりです.

- `DependencyPropertyGenAttribute(Type _type, string _name)`
- `DependencyPropertyGenAttribute(Type _type, string _name, bool hasSetter)`
- `DependencyPropertyGenAttribute(Type _type, string _name, string metaDVarName, bool hasSetter = true)`
- `DependencyPropertyGenAttribute(Type _type, string _name, string metaDVarName, bool hasSetter, string setterAccessibility)`

また, 引数の意味は次の表のとおりです.

|Type|Name|Description|
|---|---|---|
|Type|_type|プロパティの型を指定する|
|string|_name|プロパティの名前を指定する|
|string|metaDVarName|Propertyetadata型フィールドの名前を指定する|
|bool|hasSetter|プロパティがsetterを持つかどうか|
|string|setterAccessibility|プロパティのsetterのアクセス修飾子|

`metaDVarName`について, ここに指定された文字列がそのまま`DependencyProperty.Register`メソッドの第4引数として記述されるだけなので, 別にコンストラクタを書いてしまっても大丈夫です.

`hadSetter`について, setterが存在しない状況というのが想像できませんが, 一応入れてあります.

`setterAccessibility`について, setterをpublicにしてもよければ`null`なり`string.Empty`なり入れてあげてください.  internalやprotected等, 何らかの文字列が指定されると, setterの`SetValue`メソッドは`DependencyPropertyKey`を使用して実行されます.

# さいごに
.NET6が正式リリースされるまでには.NET5 SDKでDependencyPropertyGenを使えるようにしたいと思います.

現状.NET6専用ではありますが, 是非ご利用いただけると幸いです.

https://www.nuget.org/packages/TR.SourceGenerator.DependencyPropertyGen