Freigeben über


ObservableProperty-Attribut

Der ObservableProperty-Typ ist ein Attribut, das das Generieren beobachtbarer Eigenschaften aus kommentierten Feldern ermöglicht. Sein Zweck besteht darin, die Menge der Bausteine, die zum Definieren beobachtbarer Eigenschaften benötigt werden, erheblich zu reduzieren.

Hinweis

Damit dies funktioniert, müssen kommentierte Felder in einer partiellen Klasse mit der erforderlichen INotifyPropertyChanged-Infrastruktur vorhanden sein. Wenn der Typ geschachtelt ist, müssen auch alle Typen in der Struktur der Deklarationssyntax als partiell kommentiert werden. Wenn dies nicht erfolgt, führt dies zu Kompilierungsfehlern, da der Generator keine andere partielle Deklaration dieses Typs mit der angeforderten beobachtbaren Eigenschaft generieren kann.

Platform-APIs:ObservableProperty, NotifyPropertyChangedFor, NotifyCanExecuteChangedFor, NotifyDataErrorInfo, NotifyPropertyChangedRecipients, ICommand, IRelayCommand, ObservableValidator, PropertyChangedMessage<T>, IMessenger

Funktionsweise

Das ObservableProperty-Attribut kann verwendet werden, um ein Feld in einem partiellen Typ zu kommentieren, z. B.:

[ObservableProperty]
private string? name;

Außerdem generiert es auf folgende Weise eine beobachtbare Eigenschaft:

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

Dies ist auch mithilfe einer optimierten Implementierung möglich, sodass das Endergebnis noch erreicht wird.

Hinweis

Der Name der generierten Eigenschaft wird basierend auf dem Feldnamen erstellt. Der Generator geht davon aus, dass das Feld entweder lowerCamel, _lowerCamel oder m_lowerCamel heißt, und es transformiert es zu UpperCamel, um die vorgeschriebenen .NET-Namenskonventionen zu befolgen. Die resultierende Eigenschaft verfügt immer über öffentliche Zugriffsmethoden, das Feld kann jedoch mit beliebiger Sichtbarkeit deklariert werden (private wird empfohlen).

Ausführen von Code bei Änderungen

Der generierte Code ist tatsächlich etwas komplexer. Der Grund dafür ist, dass er auch einige Methoden verfügbar macht, die Sie implementieren können, um die Benachrichtigungslogik zu integrieren und bei Bedarf zusätzliche Logiken direkt vor oder nach dem Aktualisieren auszuführen. Das bedeutet, dass der generierte Code tatsächlich dem folgendem ähnelt:

public string? Name
{
    get => name;
    set
    {
        if (!EqualityComparer<string?>.Default.Equals(name, value))
        {
            string? oldValue = name;
            OnNameChanging(value);
            OnNameChanging(oldValue, value);
            OnPropertyChanging();
            name = value;
            OnNameChanged(value);
            OnNameChanged(oldValue, value);
            OnPropertyChanged();
        }
    }
}

partial void OnNameChanging(string? value);
partial void OnNameChanged(string? value);

partial void OnNameChanging(string? oldValue, string? newValue);
partial void OnNameChanged(string? oldValue, string? newValue);

Auf diese Weise können Sie jede beliebige dieser Methoden implementieren, um zusätzlichen Code einzufügen. Die ersten beiden sind nützlich, wenn Sie Logiken ausführen möchten, die nur auf den neuen Wert verweisen muss, auf den die Eigenschaft festgelegt wurde. Die anderen beiden sind nützlich, wenn Sie komplexere Logiken verwenden möchten, die auch die Zustände des alten und des neuen Werts aktualisieren müssen, der festgelegt wird.

Hier ist ein Beispiel dafür, wie die ersten beiden Überladungen verwendet werden können:

[ObservableProperty]
private string? name;

partial void OnNameChanging(string? value)
{
    Console.WriteLine($"Name is about to change to {value}");
}

partial void OnNameChanged(string? value)
{
    Console.WriteLine($"Name has changed to {value}");
}

Und hier ein Beispiel dafür, wie die anderen beiden Überladungen verwendet werden können:

[ObservableProperty]
private ChildViewModel? selectedItem;

partial void OnSelectedItemChanging(ChildViewModel? oldValue, ChildViewModel? newValue)
{
    if (oldValue is not null)
    {
        oldValue.IsSelected = true;
    }

    if (newValue is not null)
    {
        newValue.IsSelected = true;
    }
}

Sie können jede beliebige Anzahl der verfügbaren Methoden implementieren, oder auch keine. Wenn sie nicht implementiert sind (oder eine), werden der oder die gesamten Aufrufe nur vom Compiler entfernt, sodass es für Fälle, in denen diese zusätzliche Funktionalität nicht erforderlich ist, zu keinen Leistungseinbußen kommt.

Hinweis

Die generierten Methoden sind partielle Methoden ohne Implementierung, was bedeutet, dass Sie beim Implementieren keine explizite Barrierefreiheit für sie angeben können. Das bedeutet, dass Implementierungen dieser Methoden nur als partial-Methoden deklariert werden sollten und sie implizit immer über private Barrierefreiheit verfügen. Wenn Sie versuchen, eine explizite Barrierefreiheit hinzuzufügen (z. B. public oder private), tritt ein Fehler auf, da dies in C# nicht zulässig ist.

Benachrichtigen abhängiger Eigenschaften

Stellen Sie sich vor, Sie haben eine FullName-Eigenschaft, für die Sie eine Benachrichtigung auslösen möchten, wann immer Änderungen an Name auftreten. Sie können dies mithilfe des NotifyPropertyChangedFor-Attributs folgendermaßen erreichen:

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
private string? name;

Dies führt zu einer generierten Eigenschaft, die dem Folgenden entspricht:

public string? Name
{
    get => name;
    set
    {
        if (SetProperty(ref name, value))
        {
            OnPropertyChanged("FullName");
        }
    }
}

Benachrichtigen abhängiger Befehle

Stellen Sie sich vor, Sie haben einen Befehl, dessen Ausführungszustand vom Wert dieser Eigenschaft abhängig ist. Das heißt, wenn die Eigenschaft geändert wird, soll der Ausführungszustand des Befehls ungültig und erneut berechnet werden. Mit anderen Worten: ICommand.CanExecuteChanged soll erneut ausgelöst werden. Sie können diese mithilfe des NotifyCanExecuteChangedFor-Attributs erreichen:

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(MyCommand))]
private string? name;

Dies führt zu einer generierten Eigenschaft, die dem Folgenden entspricht:

public string? Name
{
    get => name;
    set
    {
        if (SetProperty(ref name, value))
        {
            MyCommand.NotifyCanExecuteChanged();
        }
    }
}

Damit dies funktioniert, benötigt der Zielbefehl eine IRelayCommand-Eigenschaft.

Anfordern einer Eigenschaftsüberprüfung

Wenn die Eigenschaft in einem Typ deklariert wird, der von ObservableValidator erbt, ist es auch möglich, sie mit beliebigen Überprüfungsattributen zu kommentieren und dann den generierten Setter anzufordern, um die Überprüfung dieser Eigenschaft auszulösen. Dies können Sie mithilfe des NotifyDataErrorInfo-Attributs erreichen:

[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[MinLength(2)] // Any other validation attributes too...
private string? name;

Dadurch wird die folgene Eigenschaft generiert:

public string? Name
{
    get => name;
    set
    {
        if (SetProperty(ref name, value))
        {
            ValidateProperty(value, "Value2");
        }
    }
}

Der generierte ValidateProperty-Aufruf überprüft dann die Eigenschaft und aktualisiert den Status des ObservableValidator-Objekts, sodass Komponenten der Benutzeroberfläche darauf reagieren und alle Überprüfungsfehler entsprechend anzeigen können.

Hinweis

Entwurfsbedingt werden nur Feldattribute, die von ValidationAttribute erben, an die generierte Eigenschaft weitergeleitet. Dies geschieht speziell zur Unterstützung von Datenüberprüfungsszenarien. Alle anderen Feldattribute werden ignoriert. Daher ist es derzeit nicht möglich, zusätzliche benutzerdefinierte Attribute zu einem Feld hinzuzufügen und sie auch auf die generierte Eigenschaft anzuwenden. Wenn dies erforderlich ist (z. B. zum Steuern der Serialisierung), sollten Sie stattdessen eine herkömmliche manuelle Eigenschaft verwenden.

Senden von Benachrichtigungsmeldungen

Wenn die Eigenschaft in einem Typ deklariert wird, der von ObservableRecipient erbt, können Sie das NotifyPropertyChangedRecipients-Attribut verwenden, um den Generator anzuweisen, auch Code einzufügen, um eine „Eigenschaft geändert“-Nachricht aufgrund der Änderung der Eigenschaft zu senden. Dadurch können registrierte Empfänger dynamisch auf die Änderung reagieren. Betrachten Sie dazu den folgenden Code:

[ObservableProperty]
[NotifyPropertyChangedRecipients]
private string? name;

Dadurch wird die folgene Eigenschaft generiert:

public string? Name
{
    get => name;
    set
    {
        string? oldValue = name;

        if (SetProperty(ref name, value))
        {
            Broadcast(oldValue, value);
        }
    }
}

Dieser generierte Broadcast-Aufruf sendet dann mithilfe der IMessenger-Instanz, die im aktuellen Ansichtsmodell verwendet wird, eine neue PropertyChangedMessage<T> an alle registrierten Abonnenten.

Hinzufügen benutzerdefinierter Attribute

In manchen Fällen kann es hilfreich sein, auch über einige benutzerdefinierte Attribute über die generierten Eigenschaften zu verfügen. Um dies zu erreichen, können Sie einfach das [property: ]-Ziel in Attributlisten über kommentierte Felder verwenden, und das MVVM-Toolkit leitet diese Attribute automatisch an die generierten Eigenschaften weiter.

Betrachten Sie beispielsweise ein Feld wie das folgende:

[ObservableProperty]
[property: JsonRequired]
[property: JsonPropertyName("name")]
private string? username;

Dadurch wird eine Username-Eigenschaft mit den beiden Attributen [JsonRequired] und [JsonPropertyName("name")] generiert. Sie können beliebig viele Attributlisten verwenden, die auf die Eigenschaft abzielen. Alle werden an die generierten Eigenschaften weitergeleitet.

Beispiele

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