ObservableObject
ObservableObject
は、INotifyPropertyChanged
および INotifyPropertyChanging
インターフェイスを実装することによって監視可能なオブジェクトの基底クラスです。 これは、プロパティ変更の通知をサポートする必要があるあらゆる種類のオブジェクトの開始点として使用できます。
プラットフォーム API:
ObservableObject
、TaskNotifier
、TaskNotifier<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
は、このプロセスをより簡単にする専用メソッドを提供します。 次の例では、User
は ObservableObject
を継承せずにデータベース テーブルを直接マッピングするモデルです。
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