ObservableObject

ObservableObject — это базовый класс для объектов, которые можно наблюдать благодаря реализации интерфейсов INotifyPropertyChanged и INotifyPropertyChanging. Его можно использовать в качестве отправной точки для всех типов объектов, которые должны поддерживать уведомления об изменении свойств.

API платформы:ObservableObject, , TaskNotifierTaskNotifier<T>

Принцип работы

ObservableObject имеет следующие основные функции:

  • Она предоставляет базовую реализацию для INotifyPropertyChanged и INotifyPropertyChanging и предоставляет доступ к событиям PropertyChanged и PropertyChanging.
  • Он предоставляет ряд SetProperty методов, которые можно использовать для легкого задания значений свойств из типов, наследуемых от ObservableObject, и автоматического вызова соответствующих событий.
  • Он предоставляет метод SetPropertyAndNotifyOnCompletion, который аналогичен SetProperty, но позволяет задавать свойства Task и автоматически вызывать события уведомления после завершения назначенных задач.
  • Он предоставляет методы 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] , поэтому не нужно вручную указывать, какое свойство обновляется.

Обёртывание ненаблюдаемой модели

Распространённый сценарий, например при работе с объектами базы данных, — создать обёрточную "bindable"-модель, которая предоставляет свойства модели базы данных и при необходимости отправляет уведомления об изменении свойств. Это также необходимо, если требуется внедрить поддержку уведомлений в модели, которые не реализуют 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 — это функция, которая будет вызываться, если новое значение свойства отличается от текущего, а свойство должно быть задано. Это будет сделано этой функцией обратного вызова, которая получает в качестве входных данных целевую модель и новое значение свойства для задания. В этом случае мы просто присваиваем входное значение (которое мы назвали n) свойству Name (выполняя u.Name = n). Важно избегать записи значений из текущей области и взаимодействовать только с теми, которые заданы в качестве входных данных обратному вызову, так как это позволяет компилятору C# кэшировать функцию обратного вызова и выполнять ряд улучшений производительности. Именно поэтому здесь мы не обращаемся напрямую ни к полю user, ни к параметру value в сеттере, а вместо этого используем только входные параметры лямбда-выражения.

Метод SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) делает создание этих свойств оболочки чрезвычайно простым, так как он заботится о получении и настройке целевых свойств при предоставлении чрезвычайно компактного API.

Примечание.

По сравнению с реализацией этого метода с помощью выражений LINQ, в частности с помощью параметра типа Expression<Func<T>> вместо параметров состояния и обратного вызова, улучшения производительности, которые можно добиться таким образом, действительно важны. В частности, эта версия примерно в 200 раз быстрее, чем версия, использующая выражения LINQ, и вообще не выполняет выделения памяти.

Обработка свойств 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> и обеспечивает необходимую логику уведомлений для этого метода. Тип TaskNotifier также доступен для использования непосредственно, если у вас есть только общий Task тип.

Примечание.

Метод SetPropertyAndNotifyOnCompletion предназначен для замены использования типа NotifyTaskCompletion<T> из пакета Microsoft.Toolkit. Если используется этот тип, его можно заменить только внутренним Task (или Task<TResult>) свойством, а затем SetPropertyAndNotifyOnCompletion метод можно использовать для задания его значения и повышения изменений уведомлений. Все свойства, которые предоставляет тип NotifyTaskCompletion<T>, доступны непосредственно в экземплярах Task.

Примеры

  • Ознакомьтесь с примером приложения (для нескольких платформ пользовательского интерфейса), чтобы просмотреть набор средств MVVM в действии.
  • Дополнительные примеры можно найти в модульных тестах.