以前紹介したMVVMライブラリ CommunityToolkit.Mvvm のバージョンが ver8 になりそうなのでpreviewをご紹介。
ver7の軽さはそのまま。ver8 の目玉はMVVMコードの記述簡易化です。
ver7の紹介記事はこちら
https://qiita.com/hqf00342/items/40a753edd8e37286f996
2022年8月注記
- ver8.0正式版リリースで「ICommand」属性が「RelayCommand」属性に名称変更されたので記事も修正してます。日本語公式ドキュメントも確認してください。
- 2022年8月現在、VisualStudio17.2.6以降 + .NET6.0.3以降でソースジェネレータが2重に動作する不具合があります→ .NET6.0.9 で修正されました(2022-9-14)。
新しいViewModelの記述方法
早速ですが新旧の書き方比較をしてみます。
最初に旧来(ver7)の方法で、変更通知できる Name
プロパティ と GreetCommand
コマンドを書いてみます。
internal class Vm : ObservableObject
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
private IRelayCommand<string> greetCommand;
public IRelayCommand<string> GreetCommand => greetCommand ??= new RelayCommand<string>(Greet);
private void Greet(string user)
{
Debug.WriteLine($"Hello {user}!");
}
}
これをver8で書くと以下のコードになります。
[INotifyPropertyChanged]
internal partial class Vm
{
[ObservableProperty]
private string name;
[RelayCommand]
private void Greet(string user) {
Debug.WriteLine($"Hello {user}!");
}
}
ずいぶんと短くなりました。作ったプロパティやコマンドすら見当たらないですが、1つずつ説明していきます。
コードを簡潔に
C#の進化とともにMVVMのコード記述量は減りましたがそれでもまだ面倒でスニペットや便利なライブラリが良く使われます。海外では ReactiveUI、日本では ReactivePropertyあたりでしょうか。 Fody のようにILを直接書き換えるのも面白い試みです。
(興味ある方は @soi さんの INotifyPropertyChangedプロパティ実装方法まとめ C#3からC#7、Fodyも をご覧ください)
今回のMVVM Toolkit ver8では roslyn のソースジェネレータで解決してます。
面倒なコードはコンパイラが生成しているのでライブラリにありがちなオーバヘッドもありません。
ViewModelクラス定義
ver7までは ObservableObject
(他ライブラリでいうBindableBase) を継承していました。
internal class Vm : ObservableObject
{
}
上の書き方でもOKですが、ver8からINotifyPropertyChanged
属性で作れるようになります。またソースジェネレータのために partial
にします。
[INotifyPropertyChanged]
internal partial class Vm
{
}
C# は多重継承ができないため、データバインディング用に基底クラスを使うとほかのクラスを継承できなかった問題が解決します。
ICommandの記述
ICommandはこれまで以下のように実装していました。
private IRelayCommand<string> greetCommand;
public IRelayCommand<string> GreetCommand => greetCommand ??= new RelayCommand<string>(Greet);
private void Greet(string user)
{
Debug.WriteLine($"Hello {user}!");
}
ver8からは[RelayCommand]
属性を付けることで ~Command
が自動生成されます。(previewでは「ICommand」属性でしたがver8.0正式版で「RelayCommand」属性に変更)
[RelayCommand]
private void Greet(string user)
{
Debug.WriteLine($"Hello {user}!");
}
XAMLからの使い方は変わりません。
<Button Content="Greet" Command="{Binding GreetCommand}" CommandParameter="test" />
変更通知プロパティ
INotifyPropertyChanged実装プロパティはいままでは以下のように記述していました。C#の進化とライブラリによってだいぶ短く書けるようになりました。
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
ver8からは以下のように書けます。
[ObservableProperty]
private string name;
privateフィールド名の先頭が大文字になった Name
プロパティが自動生成されます。
コードビハインド側からは間違えてしまいそうですがname(フィールド)ではなくName(プロパティ)を使うようにします。
XAMLでの使い方は変わりません。
<TextBlock Text="{Binding Name}" />
また、変更通知プロパティで変更前と変更後に処理を挟みたい時があります。Onプロパティ名Changing()
、Onプロパティ名Changed()
というメソッドを用意しておくと勝手に呼び出されるようになります。(詳しくはpreview3の紹介記事を参照)
[ObservableProperty]
private string name;
partial void OnNameChanging(string name)
=>Console.WriteLine($"Nameプロパティが {name} に変更されようとしてます");
partial void OnNameChanged(string name)
=>Console.WriteLine($"Nameプロパティが {name} に変更されました");
おわりに
MVVMのコード記述が減るのは大歓迎ですが、この書き方で良いのかは疑問が残ります。
僕らが望んでいたのは表(プロパティ等)を書いたら裏(backing-field等)を自動生成してくれる、だったのですが逆になってます。確かにソースジェネレータならこうなるよな、とは思うのですが・・。
コード書く人はVisualStudioのインテリセンスで何とかなりますが、コードを読む人は自動生成された表側を推測しないといけないのが難点です。
ver8 の自動生成機能はソースジェネレータを利用しているため roslyn 4.x(VisualStudioならば2022)以降が必要になります。環境が用意できない場合でもソースジェネレータ以外の機能は使えます。
Messenger、コレクション系の通知機能も増えていますが正式リリース時に気が向いたら書きたいと思います。