1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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ライセンスですし, 生成コードに権利は主張しないので, お気軽にご利用ください.

使用例

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ページなどです.

実装関連

プロジェクト設定関連

実現方法(実際の使い方)を検討

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)を参考に, 公式サンプルも見つつ実装したので, 自分でも完全に理解しきれているとは言えませんが, とりあえず形にはなっているんじゃないかなと思います.

IDEで生成コードを見れるのに…

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

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

ライセンス

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

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

使用方法

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

SampleControl.cs
using System.Windows.Controls;

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

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

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専用ではありますが, 是非ご利用いただけると幸いです.

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?