Freigeben über


ObservableObject

ObservableObject ist eine Basisklasse für Objekte, die durch Implementieren der INotifyPropertyChanged-Objekte und INotifyPropertyChanging-Schnittstellen feststellbar sind. Sie kann als Ausgangspunkt für alle Arten von Objekten verwendet werden, die Eigenschaftsänderungsbenachrichtigungen unterstützen müssen.

Plattform-APIs: ObservableObject, TaskNotifier, TaskNotifier<T>

Funktionsweise

ObservableObject hat die folgenden Hauptfunktionen:

  • Sie stellt eine Basisimplementierung für INotifyPropertyChanged und INotifyPropertyChanging bereit, die die PropertyChanged- und PropertyChanging-Ereignisse verfügbar macht.
  • Sie stellt eine Reihe von SetProperty-Methoden bereit, die verwendet werden können, um Eigenschaftswerte von Typen, die ObservableObject erben, einfach festzulegen und die entsprechenden Ereignisse automatisch auszuheben.
  • Sie stellt die SetPropertyAndNotifyOnCompletion-Methode bereit, die analog zu SetProperty ist, aber mit der Möglichkeit, Task-Eigenschaften festzulegen und die Benachrichtigungsereignisse automatisch auszuheben, wenn die zugewiesenen Aufgaben abgeschlossen werden.
  • Sie macht die Methoden OnPropertyChanged und OnPropertyChanging verfügbar, die in abgeleiteten Typen außer Kraft gesetzt werden können, um anzupassen, wie die Benachrichtigungsereignisse ausgelöst werden.

Einfache Eigenschaft

Hier ist ein Beispiel für die Implementierung der Benachrichtigungsunterstützung für eine benutzerdefinierte Eigenschaft:

public class User : ObservableObject
{
    private string name;

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

Die bereitgestellte SetProperty<T>(ref T, T, string)-Methode überprüft den aktuellen Wert der Eigenschaft und aktualisiert ihn, falls anders, und löst dann auch automatisch die relevanten Ereignisse aus. Der Eigenschaftenname wird automatisch mithilfe des [CallerMemberName]-Attributs erfasst, sodass nicht manuell angegeben werden muss, welche Eigenschaft aktualisiert wird.

Umschließen eines nicht feststellbaren Modells

Ein häufiges Szenario, z. B. beim Arbeiten mit Datenbankelementen, besteht darin, ein umgebrochenes „bindbares“ Modell zu erstellen, das Eigenschaften des Datenbankmodells weitergibt, und löst bei Bedarf die Änderungsbenachrichtigungen der Eigenschaft aus. Dies ist auch erforderlich, wenn Benachrichtigungsunterstützung in Modelle eingefügt werden soll, die die INotifyPropertyChanged-Schnittstelle nicht implementieren. ObservableObject bietet eine dedizierte Methode, um diesen Prozess zu vereinfachen. Im folgenden Beispiel ist ein Modell eine User-Datenbanktabelle direkt zuzuordnen, ohne von ObservableObject zu erben:

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);
    }
}

In diesem Fall verwenden wir die SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)-Überladung. Die Signatur ist etwas komplexer als die vorherige. Dies ist erforderlich, damit der Code weiterhin extrem effizient ist, auch wenn wir keinen Zugriff auf ein Sicherungsfeld wie im vorherigen Szenario haben. Wir können jeden Teil dieser Methodensignatur detailliert durchgehen, um die Rolle der verschiedenen Komponenten zu verstehen:

  • TModel ist ein Typargument, das den Typ des Modells angibt, das wir umschließen. In diesem Fall ist es unsere User-Klasse. Beachten Sie, dass dies nicht explizit angegeben werden muss – der C#-Compiler leitet dies automatisch ab, indem wir die SetProperty-Methode aufrufen.
  • T ist der Typ der Eigenschaft, die festgelegt werden soll. Ähnlich wie TModel wird dies automatisch abgeleitet.
  • T oldValue ist der erste Parameter, und in diesem Fall verwenden wir user.Name, um den aktuellen Wert dieser Eigenschaft zu übergeben, die wir umschließen.
  • T newValue ist der neue Wert, der auf die Eigenschaft festgelegt werden soll, und hier übergeben wir value, was der Eingabewert innerhalb des Eigenschaftensetters ist.
  • TModel model ist das Zielmodell, das wir umschließen, in diesem Fall übergeben wir die im user-Feld gespeicherte Instanz.
  • Action<TModel, T> callback ist eine Funktion, die aufgerufen wird, wenn sich der neue Wert der Eigenschaft von der aktuellen unterscheidet und die Eigenschaft festgelegt werden muss. Dies erfolgt durch diese Rückruffunktion, die als Eingabe des Zielmodells und des festzulegenden neuen Eigenschaftswerts empfängt. In diesem Fall weisen wir der Eigenschaft einfach den Eingabewert (den wir n genannt haben) zur Name-Eigenschaft zu (durch u.Name = n). Es ist hier wichtig, die Erfassung von Werten aus dem aktuellen Bereich zu vermeiden und nur mit den Als Eingaben für den Rückruf angegebenen zu interagieren, da dadurch der C#-Compiler die Rückruffunktion zwischenspeichert und eine Reihe von Leistungsverbesserungen durchführt. Aus diesem Grund greifen wir nicht nur direkt auf das user-Feld hier oder den value-Parameter im Setter zu, sondern verwenden stattdessen nur die Eingabeparameter für den Lambda-Ausdruck.

Die SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)-Methode macht das Erstellen dieser Umschließungs-Eigenschaften extrem einfach, da sie sowohl das Abrufen als auch das Festlegen der Zieleigenschaften übernimmt und gleichzeitig eine extrem kompakte API bereitstellt.

Hinweis

Im Vergleich zur Implementierung dieser Methode mithilfe von LINQ-Ausdrücken, insbesondere durch einen Parameter vom Typ Expression<Func<T>> anstelle der Zustands- und Rückrufparameter, sind die Leistungsverbesserungen, die auf diese Weise erreicht werden können, wirklich signifikant. Insbesondere ist diese Version ~ 200x schneller als die, die LINQ-Ausdrücke verwendet, und macht überhaupt keine Speicherzuweisungen.

Behandeln von Task<T>-Eigenschaften

Wenn es sich bei einer Eigenschaft um eine Task-Eigenschaft handelt, müssen Sie das Benachrichtigungsereignis auch nach Abschluss der Aufgabe auslösen, sodass Bindungen zur richtigen Zeit aktualisiert werden. Beispiel: Anzeigen einer Ladeanzeige oder anderer Statusinformationen zu dem Vorgang, der durch den Vorgang dargestellt wird. ObservableObject verfügt über eine API für dieses Szenario:

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();
    }
}

Hier übernimmt die SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string)-Methode die Aktualisierung des Zielfelds, die Überwachung des neuen Vorgangs, falls vorhanden, und das Benachrichtigungsereignis wird ausgelöst, wenn diese Aufgabe abgeschlossen ist. Auf diese Weise ist es möglich, nur eine Bindung an eine Aufgabeneigenschaft zu erstellen und benachrichtigt zu werden, wenn sich der Status ändert. TaskNotifier<T> ist ein spezieller Typ, der von ObservableObject bereitgestellt wird, der eine Zielinstanz Task<T> umschließt und die erforderliche Benachrichtigungslogik für diese Methode aktiviert. Der TaskNotifier-Typ kann auch direkt verwendet werden, wenn Sie nur über einen allgemeinen Task verfügen.

Hinweis

Die SetPropertyAndNotifyOnCompletion-Methode soll die Verwendung des NotifyTaskCompletion<T>-Typs aus dem Microsoft.Toolkit-Paket ersetzen. Wenn dieser Typ verwendet wurde, kann er nur durch die innere Task- (oder Task<TResult>-) Eigenschaft ersetzt werden, und dann kann die SetPropertyAndNotifyOnCompletion-Methode verwendet werden, um den Wert festzulegen und Benachrichtigungsänderungen auszulösen. Alle vom Typ NotifyTaskCompletion<T> bereitgestellten Eigenschaften sind direkt auf Task-Instanzen verfügbar.

Beispiele

  • Sehen Sie sich die Beispiel-App (für mehrere Benutzeroberflächen-Frameworks) an, um das MVVM-Toolkit in Aktion zu sehen.
  • Weitere Beispiele finden Sie auch in den Komponententests.