VisualStudio 2022 preview1.0が公開されましたね. 新しいものというのはやる気を呼び起こしてくれるもので, 今回はSource Generatorをリリースできました.
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ライセンスですし, 生成コードに権利は主張しないので, お気軽にご利用ください.
使用例
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ページなどです.
実装関連
プロジェクト設定関連
実現方法(実際の使い方)を検討
C# Source Generatorでは, 既存のコードの削除/改変を行えません. できるのは新規追加のみです. 従って, 実現する使用方法として, 次の2つを考えました.
- 1つの依存関係プロパティに対して1つのAttributeを自動実装先のクラスに付与し, Attributeの引数にて型やプロパティ名等の情報を与えて生成させる方法
- Attributeの引数で指定されたinterfaceに宣言されたプロパティを, すべて依存関係プロパティとしてコード生成する方法
1の方法だと, Source Generatorの実装が簡単であり, またプロパティごとの設定(メタデータの指定など)を簡単に実現可能であるというメリットがありますが, 自動実装させるプロパティの数だけ属性を付ける必要があり, あまり美しくありません. 加えて, これは実装してから判明したことですが, 自動実装されたAttributeにVisualStudioのコード補完は効かないらしく, 若干不便です. まぁでもこれは将来的に改善されるかもしれませんが.
2の方法だと, Source Generatorの実装は面倒ですが, Attributeを目的のクラスに1つ追加するだけで実装ができるため, コードがシンプルになります.
今回は, まだRoslyn SDKを用いた構文解析の使い方を熟知していないため, 1の方法で機能を実現することにしました.
将来的には2の方法も使用できるようにしたいです.
実装してみた
とりあえず, 実際に世に出ているSource Generatorのコードを読んで, 改造して, どのように動くかの理解に努めました.
主にRyotaMurohoshiさんのコード(RyotaMurohoshi/ValueObjectGenerator)を参考に, 公式サンプルも見つつ実装したので, 自分でも完全に理解しきれているとは言えませんが, とりあえず形にはなっているんじゃないかなと思います.
IDEで生成コードを見れるのに…
初めはVS2019 Community版で作業していましたが, なぜか「IDEでは生成されたコードを確認できるのに, いざビルドにかけると"DependencyPropertyGen
はAttributeクラスちゃうで"と怒られる」という現象に見舞われ…
原因を調べることも考えましたが, 他にやりたいこともあったので, とりあえず放置することにしました.
で, VS2022 previewがリリースされて久々にやる気が出たので, VS2022 preview (正確には.NET6 SDK?)でちょっと修正したりしたらうまく動いたという
相変わらず.NET5 SDK環境だとうまく動かないんですが, 今回はこの修正を後回しにしてひとまずリリースすることにしました.
ライセンス
DependencyPropertyGen自体にはMITライセンスを設定しています.
生成されたコードに著作権を主張できるとは思えませんが, もし仮に著作権を主張できる場合も, 私からは生成コードに対してCC0ライセンスを適用することにしています.
使用方法
例えば, 次のようなコードを用意したとします.
using System.Windows.Controls;
namespace TR.SourceGenerator.DependencyPropertyGen.Sample
{
//DependencyPropertyを自動実装するために属性を付ける
[DependencyPropertyGen(typeof(string), "MyText")]
public partial class SampleControl : Control
{
//略
}
}
この場合, 自動生成されたソースコードは次のようになります.
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専用ではありますが, 是非ご利用いただけると幸いです.