.NET standard2.x時代のMVVMライブラリ

## Microsoft.Toolkit.Mvvm

(2023年時点では、`CommunityToolkit.Mvvm` に名称が変更されています）

[WindowsCommunityToolkit](https://github.com/windows-toolkit/WindowsCommunityToolkit) の中にあるMVVM用ライブラリの[紹介Youtube](https://www.youtube.com/watch?v=NYfEpvH-o14)に以下のようなベンチマークが出ており、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/ac9e2593-dd8f-5766-ef81-1767bb4244d8.png)
そのパフォーマンスと省メモリーに興味がわいたので試してみました。
触って2日目ですので軽いご紹介です。

https://github.com/windows-toolkit/MVVM-Samples/blob/master/docs/mvvm/Introduction.md

2022年1月13日更新：
GitHub のソースコード移動のためURL更新

2022年4月25日
新バージョン [ver8の新機能](https://qiita.com/hqf00342/items/d12bb669d1ac6fed6ab6)を別投稿しています。

## 特徴

[MVVM Light](http://www.mvvmlight.net/)にインスパイヤされた、よりモダンで軽量なライブラリ。同作者も携わっているとのこと。

  - .NET Standard2.0対応
  - .NET , .net Framework , mono , Xamarin , Uno などで動作。フレームワークに依存しない
  - Mvvm部分だけ単独で利用可能(Toolkit全体は不要)。
  - MVVMLightやPrismに比べるとかなり省メモリらしい。
  - `INotifyPropertyChanged`, `ICommand`, `Messenger`, `DI(IoC)`の4機能を提供。それぞれ独立しているので使いたいものだけ使える。
  - MITライセンス

## インストール

nugetで ~~`Microsoft.Toolkit.Mvvm`~~ `CommunityToolkit.Mvvm`をインストールすればOK。
.NET5以降なら依存関係なし、40kBぐらいのDLLが１つのみでした。

## 使い方

4つの機能がありますが独立しているのでObservableObjectだけ利用するなど好きなものを使えます。詳しくは[サンプルドキュメント](https://github.com/windows-toolkit/MVVM-Samples/tree/master/docs/mvvm)や[ソースコード](https://github.com/CommunityToolkit/dotnet/tree/main/CommunityToolkit.Mvvm)で確認してください。

| 提供機能               | 実装(例)            | Namespace |
|------------------------|--------------------|----|
| INotifyPropertyChanged | [ObservableObject](#observableobject) |`CommunityToolkit.Mvvm.ComponentModel`|
| ICommand               | [RelayCommand](#relaycommand) | `CommunityToolkit.Mvvm.Input` |
| Messenger              | [WeakReferenceMessenger](#messenger) | `CommunityToolkit.Mvvm.Messeging` |
| DI, IoC                | [IoC](#iocdi)| `CommunityToolkit.Mvvm.DependencyInjection` |

（修正：名前空間は`Microsoft.Toolkit.Mvvm`から`CommunityToolkit.Mvvm`に変更になりました）

### ObservableObject

`INotifyPropertyChanged` の実装です。他のMVVMフレームワークでいう `BindableBase` などと同様で継承して使います。

```csharp:VM1.cs
//using Microsoft.Toolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;

public class VM1 : ObservableObject
{
    private string _name;
    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}
```
また、`INofityPropertyChanged` を実装していないModelクラスをラップして`PropertyChanged`に反応させることもできます。

```cs:Person.cs
//ViewModelで使いたいModelクラス
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
```

```cs:VM1.cs
//ViewModelでラップしてPropertyChangedに反応させる
public class VM1 : ObservableObject
{
    private Person _person;
    public int Age 
    {
        get => _person.Age;
        set => SetProperty(_person.Age, value, _person, (p, newval) => p.Age = newval);
    }
    public string Name 
    {
        get => _person.Name;
        set => SetProperty(_person.Name, value, _person, (p, newval) => p.Name = newval);
        //SetProperty(古値, 新値, 対象オブジェクト, 値更新(古値!=新値)時に実行するcallback);
    }
}
```

`ObservableObject`の派生として、IMessenger 機能を有した`ObservableRecipient`(後述)や
INotifyDataErrorInfo も実装した`ObservableValidator`もあるのでそれらの機能が必要になったときにVMの継承を変えればMessengerやValidationが使えるようになります。

### RelayCommand

`ICommand`の実装です。使い勝手も他MVVMライブラリでいう`DelegateCommand`等とほぼ同様。

```csharp:VM1.cs
//using Microsoft.Toolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Input;

public class VM1 : ObservableObject
{
    public ICommand ClickCommand { get; }

    public VM1()
    {
        ClickCommand = new RelayCommand(Click);
    }

    private void Click() { Name = ""; }
}
```
`CanExecute` や `CommandParameter` にも当然対応。

```csharp
RelayCommand(Action execute);
RelayCommand(Action execute, Func<bool> canExecute);
RelayCommand(Action<T?> execute);
RelayCommand(Action<T?> execute, Predicate<T?> canExecute);
```

非同期版もあり `Func<Task>`を利用できます。非同期版は `CancellationToken` を受けることも可能です。これでだいぶ使いやすくなりました。

```csharp
AsyncRelayCommand(Func<Task>)
AsyncRelayCommand(Func<Task>, Func<Boolean>)
AsyncRelayCommand(Func<CancellationToken,Task>)
AsyncRelayCommand(Func<CancellationToken,Task>, Func<Boolean>)
AsyncRelayCommand<T>(Func<T,Task>)
AsyncRelayCommand<T>(Func<T,Task>, Predicate<T>)
AsyncRelayCommand<T>(Func<T,CancellationToken,Task>)
AsyncRelayCommand<T>(Func<T,CancellationToken,Task>, Predicate<T>)	
```

### Messenger

おそらくこのライブラリの一番の特徴はこの Messenger 機能で、MvvmLight と比べるとメモリ利用は100分の1以下、実行速度も5～10倍以上というベンチマーク結果がでています。（[紹介Youtube](https://www.youtube.com/watch?v=NYfEpvH-o14)から）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/ac9e2593-dd8f-5766-ef81-1767bb4244d8.png)
弱参照でメモリーを自動管理してくれる[WeakReferenceMessenger](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.toolkit.mvvm.Messaging.WeakReferenceMessenger?view=win-comm-toolkit-dotnet-7.0)と強参照版の[StrongReferenceMessenger](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.toolkit.mvvm.Messaging.StrongReferenceMessenger?view=win-comm-toolkit-dotnet-7.0)がありますが、今回は弱参照の方を使ってViewとViewModel間のメッセージングをしてみます。

まずは送受信用メッセージとして小さなクラスを作ります。

```csharp:MyMessage.cs
//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;

public class MyMessage : ValueChangedMessage<string>
{
    public MyMessage(string value) : base(value)
    {
    }
}
```
参照型ならOKなので、stringをそのまま送信しても動きますが今回はライブラリが用意している値変更用クラス `ValueChangedMessage<T>` を継承しました。（[ソース](https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.Mvvm/Messaging/Messages/ValueChangedMessage%7BT%7D.cs) 見るとわかりますが、Value プロパティがあるだけの実質1行のクラスです）

このメッセージをViewとViewModel間で送受信します。送信側は単純です。

```csharp
//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;

//送信したいところで
WeakReferenceMessenger.Default.Send(new MyMessage("Hello"));
```

受信側は、まずコンストラクタなどで受信コールバックを登録します。これで受信できるようになります。

```csharp:
//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;

//受信関数recv()を登録(コンストラクタ等)
WeakReferenceMessenger.Default.Register<MyMessage>(this, recv);

private void recv(object recipient, MyMessage message)
{
    //受信処理
    Console.WriteLine(message.Value); //"Hello"と出力
}
```

ViewModelで受信する場合は継承元クラスを`ObservableRecipient`にすることで登録処理をすこし省略できます。`ObservableObject`を継承しているので PropertyChanged イベントも対応しています。

```csharp:VM2.cs
//using Microsoft.Toolkit.Mvvm.ComponentModel;
//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;

public class VM2 :  ObservableRecipient, IRecipient<MyMessage>
{
    public VM2()
    {
        //メッセージ受信を有効
        IsActive = true;
    }

    //受信部：IRecipientインターフェースの実装
    public void Receive(MyMessage message)
    {
        Console.WriteLine(message.Value); //"Hello"と出力
    }
}
```

いらなくなったらメッセージ購読解除。

```csharp
WeakReferenceMessenger.Default.Unregister<MyMessage>(this);
```

強参照版も使い方はほぼ一緒です。
他に用意されているメッセージタイプ等については[サンプルのドキュメント](https://github.com/windows-toolkit/MVVM-Samples/blob/master/docs/mvvm/Messenger.md)や[ソースコード](https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Mvvm/Messaging/Messages)をご確認ください。

### IoC/DI

Inversion of Control(制御の反転)を実装したDI向けクラス。
ソースコードを見ればわかりますが必要最小限のヘルパーです。そのためIServiceProviderを実装する別のDIライブラリが必要になります。ここでは `Microsoft.Extensions.DependencyInjection`を使って自作のILoggerでも登録してみます。

登録するサービスはサンプルとして自作のMyLoggerにしてみます。

```csharp:ILogger.cs
public interface ILogger
{
    void Log(string text);
}
```
```csharp:MyLogger.cs
public class MyLogger : ILogger
{
    public void Log(string text) => Console.WriteLine(text);
}
```
これをサービスとして登録します。今回はWPFにしたのでapp.xaml.csで登録します。`IoC.Default`というstaticプロパティが用意されているのでそれを使いシングルトンとして登録します。ConfigureServices()に指定する引数 IServceProvider は`Microsoft.Extensions.DependencyInjection`を利用してます。

```csharp:app.xaml.cs
//using Microsoft.Toolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

public partial class App : Application
{
    public App()
    {
        this.InitializeComponent();
        Ioc.Default.ConfigureServices(
            new ServiceCollection()
            .AddSingleton<ILogger,MyLogger>()
            .BuildServiceProvider());
    }
}
```

登録したMyLoggerをViewModelで使ってみます。

```csharp:VM2.cs
//using Microsoft.Toolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.DependencyInjection;

public void Receive(MyMessage message)
{
    Ioc.Default.GetService<ILogger>().Log(message.Value);
}
```

## 参考

**ソースコード**
<del> https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Mvvm </del>
2022年1月現在、以下に移動しています。
https://github.com/CommunityToolkit/dotnet

**サンプルとドキュメント**
https://github.com/windows-toolkit/MVVM-Samples/tree/master/docs/mvvm

**公式ドキュメント**
  - [Microsoft.Toolkit.Mvvm.ComponentModel](https://docs.microsoft.com/en-us/dotnet/api/microsoft.toolkit.mvvm.componentmodel?view=win-comm-toolkit-dotnet-7.0)
  - [Microsoft.Toolkit.Mvvm.Input](https://docs.microsoft.com/en-us/dotnet/api/microsoft.toolkit.mvvm.input?view=win-comm-toolkit-dotnet-7.0)
  - [Microsoft.Toolkit.Mvvm.Messaging](https://docs.microsoft.com/en-us/dotnet/api/microsoft.toolkit.mvvm.messaging?view=win-comm-toolkit-dotnet-7.0)
  - [Microsoft.Toolkit.Mvvm.DependencyInjection](https://docs.microsoft.com/en-us/dotnet/api/microsoft.toolkit.mvvm.dependencyinjection?view=win-comm-toolkit-dotnet-7.0)