CommunityToolkit.Mvvm V8 入門

* 2022/12/14
  - 「WeakとStrong」の節に少し追記

# はじめに
.NET Community Toolkit の v8.0.0 が８月にリリースされました。
これは雑に言うと、Windows開発用のWindows Community ToolkitからWindows 固有でないAPIが独立して作成された最初のバージョンです。
上記Toolkitに、CommunityToolkit.Mvvmが含まれています。

.NET 6 で Incremental Source Generator（ISG）が登場し、多くのボイラープレートコードが高速に生成できるようになりました。
CommunityToolkit.Mvvmでも同様にISGを活用し、多くのコード生成を実現しています。
※ちなみにコード生成機能自体は7.1.0でもあったようです。

本稿ではv8のうち、特にコード生成に関連する部分（の一部）を紹介します。
自動生成コードとある程度格闘できる人向けの記事ということで、記事内ではXamlは原則省略します。


# 基本的な使い方
## NotifyPropertyChangedを実装したViewModelを作成する
ViewModelとしたいクラスに`INotifyPropertyChanged`属性と`partial`を付与するだけです。

```c#:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
}
```

## 監視可能なプロパティを作成する
バッキングフィールドを先に記述し、そのバッキングフィールドへ`ObservableProperty`属性を付与します。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
+   [ObservableProperty]
+   private string _text = string.Empty; //Text というプロパティが生成される
}
```

生成されるプロパティは以下の通り[^likeThis]です。
`inheritdoc`が付与されるのはかなり嬉しいポイントです。
[^likeThis]:見易さのため一部省略、改変しています。

<details>
<summary>クリックして展開</summary>

```c#:generated
partial class MainWindowViewModel
{
    /// <inheritdoc cref="_text"/>
    public string Text
    {
        get => _text;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
                _text = value;
                OnPropertyChanged(new PropertyChangedEventArgs("Text"));
            }
        }
    }
}
```
</details>

### 生成されるプロパティの名前
プロパティの名前は、バッキングフィールドの名前を元に以下の通り決定されます。

1. プレフィックスの削除
   * `m_`で始まる場合は`m_`を削除
   * `_`で始まる場合は**一連の**アンダーバーを削除
2. 残った文字列の先頭１文字を大文字に

つまり、以下のフィールドに対しては`Text`というプロパティが生成されます。
`m_text`, `_text`, `____text`, `text`  


## 監視可能なプロパティに依存したプロパティの変更を通知する
`NotifyPropertyChangedFor`属性を利用することで、自身が更新された際に他のプロパティの更新通知を投げることができます。
タイトル含め少しわかりにくいですが、コードを見ればシンプルです。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    [ObservableProperty]
+   [NotifyPropertyChangedFor(nameof(TextUpper))] //Textの変更時に、OnPropertyChanged("TextUpper")がコールされる
    private string _text = string.Empty; //Text というプロパティが生成される

+   public string TextUpper => _text.ToUpper();
}
```

生成されるプロパティは以下の通り[^likeThis]です。
[^likeThis]:見易さのため一部省略、改変しています。

<details>
<summary>クリックして展開</summary>

`TextUpper`への変更通知が追加されています。

```diff_c_sharp:generated
partial class MainWindowViewModel
{
    /// <inheritdoc cref="_text"/>
    public string Text
    {
        get => _text;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
                _text = value;
                OnPropertyChanged(new PropertyChangedEventArgs("Text"));
+               OnPropertyChanged(new PropertyChangedEventArgs("TextUpper"));
            }
        }
    }
}
```
</details>

## コマンドを作成する
コマンドにしたい関数へ、`RelayCommand`属性を付与します。
引数には`CommandParameter`で指定した値が利用されます。
なお、返り値は`void`である必要があります。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    //...

+   [RelayCommand]
+   private void SyncMethod() //SyncMethodCommand が生成される
+   {        
+       //...
+   }

+   [RelayCommand]
+   private void SyncMethodWithParam(string str) //SyncMethodWithParamCommand が生成される
+   {        
+       //...
+   }
}
```


生成されるコマンドは以下の通り[^likeThis]です。

<details>
<summary>クリックして展開</summary>

```c#:generated
partial class MainWindowViewModel
{
    /// <summary>The backing field for <see cref="SyncMethodCommand"/>.</summary>
    private RelayCommand? syncMethodCommand;
    /// <summary>Gets an <see cref="IRelayCommand"/> instance wrapping <see cref="SyncMethod"/>.</summary>
    public IRelayCommand SyncMethodCommand
        => syncMethodCommand ??= new RelayCommand(new Action(SyncMethod));

    /// <summary>The backing field for <see cref="SyncMethodWithParamCommand"/>.</summary>
    private RelayCommand<string>? syncMethodWithParamCommand;
    /// <summary>Gets an <see cref="IRelayCommand{T}"/> instance wrapping <see cref="SyncMethodWithParam"/>.</summary>
    public IRelayCommand<string> SyncMethodWithParamCommand
        => syncMethodWithParamCommand ??= new RelayCommand<string>(new Action<string>(SyncMethodWithParam));
}
```

</details>


### 生成されるコマンドの名前
コマンドの名前は、関数の名前を元に以下の通り決定されます。

1. プレフィックスの削除
   * `On`で始まる場合は`On`を削除
2. 残った文字列の末尾に`Command`を付与


## 非同期コマンドを作成する
基本的に、同期コマンドと同じです。
返り値は`Task`か`Task`を継承した型である必要があります。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    //...

+   [RelayCommand]
+   private async Task AsyncMethodAsync() //AsyncMethodCommand が生成される
+   {
+   }

+   [RelayCommand]
+   private async Task AsyncMethodWithParamAsync(string str) //AsyncMethodWithParamCommand が生成される
+   {
+   }
}
```

生成される非同期コマンドは以下の通り[^likeThis]です。

<details>
<summary>クリックして展開</summary>

```c#:generated
partial class MainWindowViewModel
{
    /// <summary>The backing field for <see cref="AsyncMethodCommand"/>.</summary>
    private AsyncRelayCommand? asyncMethodCommand;
    /// <summary>Gets an <see cref="IAsyncRelayCommand"/> instance wrapping <see cref="AsyncMethodAsync"/>.</summary>
    public IAsyncRelayCommand AsyncMethodCommand
        => asyncMethodCommand ??= new AsyncRelayCommand(new Func<Task>(AsyncMethodAsync));

    /// <summary>The backing field for <see cref="AsyncMethodWithParamCommand"/>.</summary>
    private AsyncRelayCommand<string>? asyncMethodWithParamCommand;
    /// <summary>Gets an <see cref="IAsyncRelayCommand{T}"/> instance wrapping <see cref="AsyncMethodWithParamAsync"/>.</summary>
    public IAsyncRelayCommand<string> AsyncMethodWithParamCommand
        => asyncMethodWithParamCommand ??= new AsyncRelayCommand<string>(new Func<string, Task>(AsyncMethodWithParamAsync));
}
```
</details>


### 生成される非同期コマンドの名前
非同期コマンドの名前は、関数の名前を元に以下の通り決定されます。

1. プレフィックスの削除
   * `On`で始まる場合は`On`を削除
2. サフィックスの削除
   * `Async`で終わる場合は`Async`を削除
3. 残った文字列の末尾に`Command`を付与


### 非同期コマンドのキャンセル
非同期コマンドにするメソッドの引数に`CancellationToken`を指定することで、実行中のコマンドを容易にキャンセルすることができます。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    //...

+   [RelayCommand]
+   private async Task CancelableAsyncMethodAsync(CancellationToken token) //CancelableAsyncMethodCommand が生成される
+   {
+   }

+   [RelayCommand]
+   private async Task CancelableAsyncMethodWithParamAsync(string str, CancellationToken token) //CancelableAsyncMethodWithParamCommand が生成される
+   {
+       //tokenをチェックしながらの重い処理...
+   }
}
```

実際にキャンセルするには対応するCommandの`Cancel`を呼び出します。
例えばキャンセルボタンを設ける場合、以下のように記述します。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    //...

    [RelayCommand]
    private async Task CancelableAsyncMethodWithParamAsync(string str, CancellationToken token) //CancelableAsyncMethodWithParamCommand が生成される
    {
        //tokenをチェックしながらの重い処理...
    }

+   [RelayCommand]
+   private void Cancel()
+   {
+       CancelableAsyncMethodWithParamCommand.Cancel(); //これ
+   }
}
```

# やや複雑な使い方
## コマンドの実行可否を制御する
いわゆるCanExecuteの使い方です。
「基本的な」に書くか少し迷いましたが、ぱっと見では属性間の関係性が分かりづらいため、こちらに書きます。

1. まず、コマンドの実行可否を返すプロパティ、またはメソッドを定義します
   &rArr; 下記の例だと`CanExecuteSyncMethod `
2. 対象コマンドの`RelayCommand`属性に1.で定義したプロパティ or メソッドを指定します
   &rArr; 同、`[RelayCommand(CanExecute = nameof(CanExecuteSyncMethod))]`
3. 最後に、実行可否に影響するプロパティに`NotifyCanExecuteChangedFor`属性を付与します
   &rArr; 同、`[NotifyCanExecuteChangedFor(nameof(SyncMethodCommand))]`

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(TextUpper))]
+   [NotifyCanExecuteChangedFor(nameof(SyncMethodCommand))] //3. このプロパティが影響するCommandを指定する
    private string _text = string.Empty;

+   private bool CanExecuteSyncMethod => Text.Length > 0; //1. メソッドでも可。フィールドは不可
+   //private bool CanExecuteSyncMethod() => Text.Length > 0; //メソッド版

-   [RelayCommand]
+   [RelayCommand(CanExecute = nameof(CanExecuteSyncMethod))] //2. 実行可否のプロバイダを指定する
    private void SyncMethod() //SyncMethodCommand が生成される
    {
    }
}
```

生成されるプロパティ及びコマンドは以下の通り[^likeThis]変化します。

<details>
<summary>クリックして展開</summary>

プロパティには`NotifyCanExecuteChanged`が追加されています。

```diff_c_sharp:generated
partial class MainWindowViewModel
{
    /// <inheritdoc cref="_text"/>
    public string Text
    {
        get => _text;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
                //...
+               SyncMethodCommand.NotifyCanExecuteChanged();
            }
        }
    }
}
```

コマンドの生成時に、`CanExecuteSyncMethod`が引き渡されるようになります。

```diff_c_sharp:generated
partial class MainWindowViewModel
{
    /// <summary>The backing field for <see cref="SyncMethodCommand"/>.</summary>
    private RelayCommand? syncMethodCommand;
    /// <summary>Gets an <see cref="IRelayCommand"/> instance wrapping <see cref="SyncMethod"/>.</summary>
    public IRelayCommand SyncMethodCommand
-       => syncMethodCommand ??= new RelayCommand(new Action(SyncMethod));
+       => syncMethodCommand ??= new RelayCommand(new Action(SyncMethod), () => CanExecuteSyncMethod);
}
```

</details>

## プロパティの変更前後に処理を挟む
`ObservableProperty`を用いて作成した監視可能なプロパティが、バッキングフィールドへ値を反映する前後に処理を差し込むことができます。

「監視可能なプロパティの作成」では省略しましたが、以下のような監視可能プロパティに対し、、、

```C#:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    [ObservableProperty]
    private string _text = string.Empty;
}
```

実装を持たないpartialメソッドが２つ作成されます。（以下だと、`OnTextChanging`と`OnTextChanged`）
```diff_C_sharp:generated
internal partial class MainWindowViewModel
{
    public string Text
    {
        get => _text;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
+               OnTextChanging(value); //ここで呼び出し
                _text = value;
+               OnTextChanged(value); //ここで呼び出し
                OnPropertyChanged(new PropertyChangedEventArgs("Text"));
            }
        }
    }

    /// <summary>Executes the logic for when <see cref="Text"/> is changing.</summary>
+   partial void OnTextChanging(string value); //こいつと
    /// <summary>Executes the logic for when <see cref="Text"/> just changed.</summary>
+   partial void OnTextChanged(string value); //こいつ
}
```

これらpartialメソッドを実装することで、処理を差し込みます。

値変更前後でデバッグ出力をするには、以下のように実装します。

```diff_C_sharp:手書きコード
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    [ObservableProperty]
    private string _text = string.Empty;

    //このメソッドをコールすると、
    private void Test()
    {
        Text = "After";
    }

+   partial void OnTextChanging(string value)
+   {
+       System.Diagnostics.Debug.WriteLine($"Changing: Text = {Text}, value = {value}");
+       // => Changing: Text = , value = After
+   }

+   partial void OnTextChanged(string value)
+   {
+       System.Diagnostics.Debug.WriteLine($"Changed: Text = {Text}, value = {value}");
+       // => Changed: Text = After, value = After
+   }
}
```

ちなみにpartialメソッドを実装しない場合、プロパティ更新時のpartialメソッド呼び出しはコンパイラーによって削除されます。

## 値のバリデーション

バリデーションを行うには、ViewModelに`ObservableValidator`を継承させます。
これにより`INotifyPropertyChanged`インターフェースが重複してしまうため、`INotifyPropertyChanged`属性は削除します。

さらにバリデーション対象となるプロパティ（のバッキングフィールド）へ`NotifyDataErrorInfo`属性を付与し、チェックしたい内容に応じて[`Required`](https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.dataannotations.requiredattribute?view=net-7.0)属性などの[`ValidationAttribute`](https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.dataannotations.validationattribute?view=net-7.0)を継承した属性を付与します。

とはいえ、コード生成に関連するのは`NotifyDataErrorInfo`属性のみで、その他は従来の（or WPF本来の）ものです。

```diff_C_sharp:手書きコード
- [INotifyPropertyChanged]
- internal partial class MainWindowViewModel
+ internal partial class MainWindowViewModel : ObservableValidator
{
+   [Required]
+   [NotifyDataErrorInfo]
    [ObservableProperty]
    private string _text = string.Empty;
}
```

生成されるプロパティは以下の通り[^likeThis]変化します。

<details>
<summary>クリックして展開</summary>

`NotifyDataErrorInfo`属性を付けたことにより、`ValidateProperty`が呼び出されています。
また、バッキングフィールドへ付与した`Required`がそのまま転記されています。

```diff_c_sharp
partial class MainWindowViewModel
{
    /// <inheritdoc cref="_text"/>
+   [RequiredAttribute()]
    public string Text
    {
        get => _text;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
                OnTextChanging(value);
                _text = value;
+               ValidateProperty(value, "Text");
                OnTextChanged(value);
                OnPropertyChanged(new PropertyChangedEventArgs("Text"));
            }
        }
    }
}
```
</details>

従来のバリデーションと同様に`ValidationAttribute`を継承した独自属性を定義するか、[`CustomValidation`](https://learn.microsoft.com/ja-jp/dotnet/api/system.componentmodel.dataannotations.customvalidationattribute?view=net-7.0)属性を用いて独自のバリデーションを仕込むこともできます。

## プロパティの変更をブロードキャストする
ViewModelに`ObservableRecipient`属性を付与し、変更をブロードキャストしたいプロパティに`NotifyPropertyChangedRecipients`属性を付与します。

```diff_C_sharp:手書きコード
+ [ObservableRecipient]
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
+   [NotifyPropertyChangedRecipients]
    [ObservableProperty]
    private string _text = string.Empty;
}
```

生成されるプロパティは以下の通り[^likeThis]変化します。

<details>
<summary>クリックして展開</summary>

`Broadcast`の呼び出しが追加されます。

```diff_c_sharp
partial class MainWindowViewModel
{
    /// <inheritdoc cref="_text"/>
    public string Text
    {
        get => _text;
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
                OnTextChanging(value);
                _text = value;
                OnTextChanged(value);
                OnPropertyChanged(new PropertyChangedEventArgs("Text"));
+               Broadcast(__oldValue, value, "Text");
            }
        }
    }
}
```
</details>

ブロードキャストは後述するメッセンジャーを用いて行われますが、このメッセンジャーは自動生成されるコンストラクタで初期化されます。
具体的には、以下のような[^likeThis]コンストラクタが生成されます。
```c#:generated
partial class MainWindowViewModel
{
    public MainWindowViewModel() : this(WeakReferenceMessenger.Default)
    {
    }

    public MainWindowViewModel(IMessenger messenger)
    {
        Messenger = messenger;
    }
}
```

ここには少し罠があって、独自で実装したコンストラクタが存在する場合、上記のコンストラクタは生成されません。
そのため、コンストラクタを自力実装する場合（≒たいていの場合）、`Messenger`プロパティにインスタンスをセットしてあげる必要があります。

なおこの罠は、`ObservableRecipient`を継承することでも回避できます。

さて、最終的にメッセージは以下のよう[^likeThis]に送信されます。（`T`は通知されるプロパティの型）
```c#:generated
void Broadcast()
{
    PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue);
    Messenger.Send<PropertyChangedMessage<T>>(message);
}
```

詳細は後述しますが、受信は以下のように行います。
ここでは`PropertyChangedMessage<T>`が送受信されることが分かれば十分です。

```c#:MainWindowViewModel.cs
Messenger.Register<MainWindowViewModel, PropertyChangedMessage<string>>(this, static (recipient, message) =>
{
    //NotifyPropertyChangedを購読するのと同じように書ける
    switch (message.PropertyName)
    {
        case nameof(Text):
            //なんかする
            break;
    }
});
```

# その他
## メッセンジャー
コード生成を利用しないAPIで、`IMessenger`インターフェースとして提供されており、オブジェクト間のメッセージ交換をサポートします。
組み込みの`IMessenger`を利用する場合、`WeakReferenceMessenger`クラスか`StrongReferenceMessenger`クラスの`Default`静的プロパティから取得できます。
必要に応じて独自にインスタンス化（new）したものを利用することもできます。

### 受信と送信
#### 送受信するメッセージの定義
まず、必要に応じてメッセージを定義します。
`CommunityToolkit.Mvvm.Messaging.Messages.ValueChangedMessage<T>`を継承すると簡単です。

```C#:MyMessage.cs
internal class MyMessage : ValueChangedMessage<string>
{
    public MyMessage(string value) : base(value) { } //インテリセンス任せ
}
```

#### 送信
ボタンクリックでメッセージを送信には、以下のようにすればOKです。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    [RelayCommand]
    private void SendMessage()
    {
+       WeakReferenceMessenger.Default.Send<MyMessage>(new("Mesage from MainWindowViewModel")); //送信！
    }
}
```

#### 受信
上記メッセージを別ウィンドウで受信する場合、以下のように記述します。
```diff_c_sharp:SubWindow.xaml.cs
public partial class SubWindow : Window
{
    public SubWindow()
    {
        InitializeComponent();

+       WeakReferenceMessenger.Default.Register<SubWindow, MyMessage>(this, static (s, e) =>
+       {
+           //SendMessageCommandをExecuteすると、
+           //「Received: Mesage from MainWindowViewModel」と出力
+           System.Diagnostics.Debug.WriteLine($"Received: {e.Value}");
+       });
    }
}
```

##### 他のパターン
手間ですが、`IRecipient<T>`を実装したクラスを受信者として登録することもできます。
```C#:MyRecipient.cs
internal class MyRecipient : IRecipient<MyMessage>
{
    public void Receive(MyMessage message)
    {
        Debug.WriteLine($"MyRecipient Received: {message.Value}");
    }
}
```

```diff_c_sharp:SubWindow.xaml.cs
public partial class SubWindow : Window
{
    //WeakRefの場合、MyRecipient参照を握っておく必要あり
+   private readonly MyRecipient _recipient = new();

    public SubWindow()
    {
        InitializeComponent();

        //ここでMyRecipientをnewした場合、WeakReferenceMessengerが強い参照を持たないため
        //コンストラクタを抜けたタイミングでMyRecipientがGCの対象となってしまう。
        //WeakReferenceMessenger.Default.Register(new MyRecipient()); ダメ！
+       WeakReferenceMessenger.Default.Register(_recipient);

        WeakReferenceMessenger.Default.Register<SubWindow, MyMessage>(this, static (s, e) =>
        {
            //SendMessageCommandをExecuteすると、
            //「Message: Mesage from MainWindowViewModel」と出力
            System.Diagnostics.Debug.WriteLine($"Message: {e.Value}");
        });
    }
}
```

コメントに書いていますが、`WeakReferenceMessenger`の場合は後述の理由で少し注意が必要です。

### リクエスト
単方向の送受信だけでなく、レスポンスを要求することもできます。

#### 送受信するメッセージの定義
こちらは`CommunityToolkit.Mvvm.Messaging.Messages.RequestMessage<T>`を継承すると簡単です。
※非同期版の`AsyncRequestMessage<T>`も存在します。

```C#:MyRequestMessage.cs
internal class MyRequestMessage : RequestMessage<string>
{
    public DateTime RequestedAt { get; }

    //例ではDateTimeを渡す
    public MyRequestMessage(DateTime requestedAt)
    {
        RequestedAt = requestedAt;
    }
}
```

#### リクエスト
ボタンクリックでリクエストするには、以下のようにすればOKです。

```diff_c_sharp:MainWindowViewModel.cs
[INotifyPropertyChanged]
internal partial class MainWindowViewModel
{
    //.. 

    [RelayCommand]
    private void SendRequest()
    {
        //リクエスト送信
        MyRequestMessage response = Messenger.Send<MyRequestMessage>(new(DateTime.Parse("2022/12/13")));

        Debug.WriteLine($"RequestedAt = {response.RequestedAt}, Response = {response.Response}");
        // => 「RequestedAt = 2022/12/13 0:00:00, Response = Response from SubWindow.」
    }
}
```

#### レスポンス
上記メッセージを別ウィンドウで受信しレスポンスを返すには、以下のように記述します。
```diff_c_sharp:SubWindow.xaml.cs
public partial class SubWindow : Window
{
    public SubWindow()
    {
        //...

+       WeakReferenceMessenger.Default.Register<SubWindow, MyRequestMessage>(this, static (recipient, message) =>
+       {
+           //RequestMessage<T>のTに対応する型を返す
+           message.Reply("Response from SubWindow.");
+       });
    }
}
```


### WeakとStrong
`IMessenger`の実装には`WeakReferenceMessenger`と`StrongReferenceMessenger`の二つが存在します。
`WeakReferenceMessenger`は名前の通り弱参照を利用しており、受信者がGCされることを阻害しません。

例えば以下のように`Window`のコードビハインドでメッセージを受信するケースだと、`StrongReferenceMessenger`の場合は`Window`がクローズされたあとも`SubWindow`のデストラクタは呼ばれません。一方、`WeakReferenceMessenger`の場合はデストラクタが呼ばれます。

この性質は、小規模ダイアログを後始末する手間や、ViewModelの後始末のし辛さに対しては便利です。

```C#:SubWindow.xaml.cs
public partial class SubWindow : Window
{
    public SubWindow()
    {
        InitializeComponent();

        //thisへの参照をやんわり掴む（GCを阻害しない）
        //WeakReferenceMessenger.Default.Register<SubWindow, PropertyChangedMessage<string>>(this, static (_, _) => { });

        //thisへの参照をがっちり掴む
        StrongReferenceMessenger.Default.Register<SubWindow, PropertyChangedMessage<string>>(this, static (_, _) => { });
    }

    //StrongReferenceMessengerを用いた場合、Windowをクローズしても以下は実行されない（当然、GC.Collect()を呼んでもダメ）
    //WeakReferenceMessengerだと、GCが走ったタイミングで"Destructed"と表示される
    ~SubWindow()
    {
        System.Diagnostics.Debug.WriteLine("Destructed");
    }
}
```

#### 注意点
確かに便利なのですが、上の方で触れた`IRecipient<T>`を利用して別途受信者を準備するケースなどでは、意図せず受信者が消滅してしまうこともあり取扱いには注意が必要です。
「起動してしばらくは動くのに、しばらくすると（≒GCのタイミングが来ると）受信しなくなる」といった、問題に気が付きづらい不具合に繋がります。
（まぁ、`StrongReferenceMessenger`によるメモリリークもそれはそれで気付きにくいのですが。。。）

# 参考リンク
[Announcing .NET Community Toolkit 8.0! MVVM, Diagnostics, Performance, and more!](https://devblogs.microsoft.com/dotnet/announcing-the-dotnet-community-toolkit-800/)