ObservableObject

ObservableObject は、INotifyPropertyChanged および INotifyPropertyChanging インターフェイスを実装することによって監視可能なオブジェクトの基底クラスです。 これは、プロパティ変更の通知をサポートする必要があるあらゆる種類のオブジェクトの開始点として使用できます。

プラットフォーム API:ObservableObjectTaskNotifierTaskNotifier<T>

しくみ

ObservableObject の主要機能を以下に示します。

  • INotifyPropertyChanged および INotifyPropertyChanging のベースとなる実装を提供し、PropertyChanged および PropertyChanging イベントを公開します。
  • ObservableObject から継承する型からプロパティ値を簡単に設定し、適切なイベントを自動的に発生させるために使用できる一連の SetProperty メソッドを提供します。
  • SetProperty と類似しており、Task プロパティを設定して、割り当てられたタスクが完了したときに自動的に通知イベントを発生させる機能を持つ SetPropertyAndNotifyOnCompletion メソッドを提供します。
  • 通知イベントの発生方法をカスタマイズするために派生型においてオーバーライドできる OnPropertyChanged および OnPropertyChanging メソッドを公開します。

単純なプロパティ

カスタム プロパティに通知サポートを実装する方法の例を次に示します。

public class User : ObservableObject
{
    private string name;

    public string Name
    {
        get => name;
        set => SetProperty(ref name, value);
    }
}

指定された SetProperty<T>(ref T, T, string) メソッドは、プロパティの現在の値をチェックし、異なる場合は値を更新した後、関連するイベントも自動的に発生させます。 プロパティ名は [CallerMemberName] 属性を使用して自動的にキャプチャされるため、どのプロパティを更新するかを手動で指定する必要はありません。

観測可能でないモデルのラップ

たとえば、データベース アイテムを操作する場合の一般的なシナリオとして、データベース モデルのプロパティをリレーし、必要に応じてプロパティ変更の通知を発生させる "バインド可能な" ラップ モデルを作成することがあります。 これは、INotifyPropertyChanged インターフェイスを実装していないモデルに通知サポートを挿入したい場合にも必要となります。 ObservableObject は、このプロセスをより簡単にする専用メソッドを提供します。 次の例では、UserObservableObject を継承せずにデータベース テーブルを直接マッピングするモデルです。

public class ObservableUser : ObservableObject
{
    private readonly User user;

    public ObservableUser(User user) => this.user = user;

    public string Name
    {
        get => user.Name;
        set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
    }
}

このケースでは、SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) のオーバーロードを使用しています。 シグネチャは前のシグネチャよりも少し複雑ですが、これは、前のシナリオのようにバッキング フィールドにアクセスできない場合でも、コードを非常に効率的にするために必要なものです。 以下のようにこのメソッド シグネチャの各部分を詳しく確認することで、さまざまなコンポーネントの役割を理解することができます。

  • TModel は型引数で、ラップしているモデルの型を表します。 このケースでは、これは User クラスになります。 C# コンパイラは、SetProperty メソッドの呼び出し方法によって自動的にこれを推論するため、これを明示的に指定する必要はないことに注意してください。
  • T は、設定したいプロパティの型です。 TModel と同様に、これは自動的に推論されます。
  • T oldValue は最初のパラメーターであり、このケースでは user.Name を使用して、ラップしているプロパティの現在の値を渡しています。
  • T newValue はプロパティに設定する新しい値であり、ここでは value を渡しています。これはプロパティ セッター内の入力値です。
  • TModel model はラップしているターゲット モデルであり、このケースでは user フィールドに保存されているインスタンスを渡しています。
  • Action<TModel, T> callback は、プロパティの新しい値が現在の値と異なり、プロパティを設定する必要がある場合に呼び出される関数です。 これは、ターゲット モデルと設定する新しいプロパティ値を入力として受け取る、このコールバック関数によって行われます。 このケースでは、(u.Name = n を実行することで) 入力値 (ここでは n と命名) を Name プロパティに割り当てているだけです。 ここでは、現在のスコープから値をキャプチャしないようにし、コールバックへの入力として指定された値だけを操作することが重要です。これにより、C# コンパイラはコールバック関数をキャッシュし、いくつかのパフォーマンス改善を実行できます。 ここで user フィールドまたはセッターの value パラメーターに直接アクセスしないだけでなく、ラムダ式の入力パラメーターのみを使用しているのはこれが原因です。

SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) メソッドは非常にコンパクトな API を提供すると同時に、ターゲット プロパティの取得と設定の両方を処理するため、このメソッドを使用することでこれらのラップ プロパティの作成が非常に単純になります。

Note

特に状態パラメーターとコールバック パラメーターではなく型 Expression<Func<T>> のパラメーターを使用すると、この方法で実現できる LINQ 式を使用したこのメソッドの実装と比較した場合のパフォーマンスの向上は非常に大きくなります。 特に、このバージョンは LINQ 式を使用するバージョンよりも約 200 倍高速であり、メモリ割り当てをまったく行いません。

Task<T> プロパティの処理

プロパティが Task である場合は、タスクが完了したら通知イベントも発生させ、たとえば、タスクによって表される操作に関する読み込みインジケーターやその他の状態情報の表示のように、バインディングが適切なタイミングで更新されるようにする必要があります。 ObservableObject には、このシナリオ用の API があります。

public class MyModel : ObservableObject
{
    private TaskNotifier<int>? requestTask;

    public Task<int>? RequestTask
    {
        get => requestTask;
        set => SetPropertyAndNotifyOnCompletion(ref requestTask, value);
    }

    public void RequestValue()
    {
        RequestTask = WebService.LoadMyValueAsync();
    }
}

ここでは、SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string) メソッドはターゲット フィールドの更新、新しいタスク (存在する場合) の監視、そのタスクが完了したときの通知イベントの発生を処理します。 これにより、タスク プロパティにバインドするだけで、その状態が変化したときに通知を受け取ることが可能になります。 TaskNotifier<T>ObservableObject によって公開される特殊な型であり、ターゲットの Task<T> インスタンスをラップし、このメソッドで必要な通知ロジックを実現します。 一般的な Task しか存在しない場合は、TaskNotifier 型を直接使用することも可能です。

Note

SetPropertyAndNotifyOnCompletion メソッドは、Microsoft.Toolkit パッケージの NotifyTaskCompletion<T> 型の代わりに使用することが意図されたものです。 この型が使用されていた場合は、内部の Task (または Task<TResult>) プロパティのみに置き換えることができます。その後、SetPropertyAndNotifyOnCompletion メソッドを使用して値を設定し、通知の変更を発生させることができます。 NotifyTaskCompletion<T> 型によって公開されるすべてのプロパティは、Task インスタンスで直接利用できます。

  • MVVM Toolkit の実際の動作を確認するには、サンプル アプリ (複数の UI フレームワーク向け) を参照してください。
  • 単体テスト」では、さらに他の例を確認できます。