Obserwowalny obiekt

ObservableObject jest klasą bazową dla obiektów, które są obserwowalne dzięki implementacji interfejsów INotifyPropertyChanged i INotifyPropertyChanging. Może służyć jako punkt wyjścia dla wszystkich rodzajów obiektów, które muszą obsługiwać powiadomienia o zmianie właściwości.

Interfejsy API platformy:ObservableObject, TaskNotifier, TaskNotifier<T>

Jak to działa

ObservableObject ma następujące główne funkcje:

  • Zapewnia on podstawową implementację elementu INotifyPropertyChanged i INotifyPropertyChanging, uwidaczniając zdarzenia PropertyChanged i PropertyChanging .
  • Udostępnia szereg metod SetProperty, których można używać do łatwego ustawiania wartości właściwości dla typów dziedziczących po ObservableObject, a także do automatycznego wywoływania odpowiednich zdarzeń.
  • Zapewnia metodę SetPropertyAndNotifyOnCompletion, która jest analogiczna do metody SetProperty, ale umożliwia ustawianie właściwości Task oraz automatyczne wywoływanie zdarzeń powiadomień po zakończeniu przypisanych zadań.
  • Udostępnia metody OnPropertyChanged i OnPropertyChanging, które można przesłonić w typach pochodnych, aby dostosować sposób wywoływania zdarzeń powiadomień.

Prosta właściwość

Oto przykład implementacji obsługi powiadomień dla właściwości niestandardowej:

public class User : ObservableObject
{
    private string name;

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

Podana SetProperty<T>(ref T, T, string) metoda sprawdza bieżącą wartość właściwości i aktualizuje ją, jeśli jest inna, a następnie automatycznie zgłasza odpowiednie zdarzenia. Nazwa właściwości jest automatycznie przechwytywana za pomocą atrybutu [CallerMemberName] , dlatego nie trzeba ręcznie określać, która właściwość jest aktualizowana.

Zawijanie nieoserwowalnego modelu

Typowy scenariusz, na przykład podczas pracy z elementami bazy danych, polega na utworzeniu modelu zawijania "możliwego do powiązania", który przekazuje właściwości modelu bazy danych i zgłasza zmiany właściwości w razie potrzeby. Jest to również konieczne, gdy chcesz wstrzyknąć obsługę powiadomień do modeli, które nie implementują interfejsu INotifyPropertyChanged . ObservableObject Udostępnia dedykowaną metodę, aby ten proces był prostszy. W poniższym przykładzie model User bezpośrednio odwzorowuje tabelę bazy danych, bez dziedziczenia po 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);
    }
}

W tym przypadku używamy przeciążenia SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string). Podpis jest nieco bardziej złożony niż poprzedni — jest to konieczne, aby kod nadal był bardzo wydajny, nawet jeśli nie mamy dostępu do pola zapasowego, takiego jak w poprzednim scenariuszu. Możemy szczegółowo omówić każdą część tej sygnatury metody, aby zrozumieć rolę poszczególnych elementów:

  • TModel jest argumentem typu wskazującym typ modelu, który opakowujemy. W tym przypadku będzie to nasza klasa User. Pamiętaj, że nie musimy jawnie określać tego elementu — kompilator języka C# wywnioskuje to automatycznie, wywołując metodę SetProperty .
  • T jest typem właściwości, którą chcemy ustawić. Podobnie jak w TModel, jest to automatycznie wywnioskowane.
  • T oldValue jest pierwszym parametrem, a w tym przypadku używamy user.Name, aby przekazać bieżącą wartość właściwości, którą opakowujemy.
  • T newValue to nowa wartość ustawiana dla właściwości, a tutaj przekazujemy value, czyli wartość wejściową w setterze właściwości.
  • TModel model to model docelowy, który opakowujemy; w tym przypadku przekazujemy instancję przechowywaną w polu user.
  • Action<TModel, T> callback to funkcja, która zostanie wywołana, jeśli nowa wartość właściwości jest inna niż bieżąca, a właściwość musi zostać ustawiona. Zostanie to wykonane przez tę funkcję wywołania zwrotnego, która jako dane wejściowe otrzymuje model docelowy i nową wartość właściwości do ustawienia. W tym przypadku po prostu przypisujemy wartość wejściową (o nazwie n) do Name właściwości (wykonując polecenie u.Name = n). Ważne jest, aby uniknąć przechwytywania wartości z bieżącego zakresu i korzystać tylko z tych podanych jako dane wejściowe wywołania zwrotnego, ponieważ umożliwia to kompilatorowi języka C# buforowanie funkcji wywołania zwrotnego i wykonywanie szeregu ulepszeń wydajności. To dlatego nie uzyskujemy tutaj po prostu bezpośredniego dostępu do pola user ani do parametru value w setterze, lecz zamiast tego używamy wyłącznie parametrów wejściowych wyrażenia lambda.

Metoda SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) sprawia, że tworzenie tych właściwości opakowujących jest niezwykle proste, ponieważ dba zarówno o pobieranie, jak i ustawianie właściwości docelowych przy jednoczesnym zapewnieniu niezwykle kompaktowego interfejsu API.

Uwaga

W porównaniu z implementacją tej metody przy użyciu wyrażeń LINQ, w szczególności za pomocą parametru typu Expression<Func<T>> zamiast parametrów stanu i wywołania zwrotnego, ulepszenia wydajności, które można osiągnąć w ten sposób, są naprawdę znaczące. W szczególności ta wersja jest ok. 200x szybsza niż ta używająca wyrażeń LINQ i w ogóle nie wykonuje żadnych alokacji pamięci.

Obsługa właściwości Task<T>

Jeśli właściwość ma typ Task, konieczne jest również wywołanie zdarzenia powiadomienia po ukończeniu zadania, aby powiązania były aktualizowane we właściwym momencie, na przykład w celu wyświetlenia wskaźnika ładowania lub innych informacji o stanie operacji reprezentowanej przez to zadanie. ObservableObject ma interfejs API dla tego scenariusza:

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) W tym miejscu metoda zajmie się aktualizowaniem pola docelowego, monitorowaniem nowego zadania, jeśli jest obecne, i wywoływaniem zdarzenia powiadomienia po zakończeniu tego zadania. W ten sposób można po prostu powiązać z właściwością zadania i otrzymywać powiadomienia o zmianie stanu. TaskNotifier<T> to specjalny typ udostępniany przez ObservableObject, który opakowuje docelową instancję Task<T> i zapewnia logikę powiadomień wymaganą przez tę metodę. Typ TaskNotifier jest również dostępny do użycia bezpośrednio, jeśli masz tylko ogólne Task .

Uwaga

Metoda SetPropertyAndNotifyOnCompletion ma zastąpić użycie NotifyTaskCompletion<T> typu z Microsoft.Toolkit pakietu. Jeśli ten typ był używany, można go zastąpić samą wewnętrzną właściwością Task (lub Task<TResult>), a następnie za pomocą metody SetPropertyAndNotifyOnCompletion ustawić jej wartość i wywołać powiadamianie o zmianach. Wszystkie właściwości udostępniane przez typ NotifyTaskCompletion<T> są dostępne bezpośrednio w wystąpieniach Task.

Przykłady

  • Zapoznaj się z przykładową aplikacją (dla wielu struktur interfejsu użytkownika), aby zobaczyć, jak działa zestaw narzędzi MVVM Toolkit.
  • Więcej przykładów można również znaleźć w testach jednostkowych.