Поделиться через


Атрибут ObservableProperty

Тип ObservableProperty — это атрибут, позволяющий создавать наблюдаемые свойства из аннотированных полей. Его цель заключается в значительном уменьшении количества коварных элементов, необходимых для определения наблюдаемых свойств.

Примечание.

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

API платформы:ObservableProperty, NotifyPropertyChangedForNotifyPropertyChangedRecipientsICommandNotifyDataErrorInfoIRelayCommandNotifyCanExecuteChangedFor, ObservableValidator, , PropertyChangedMessage<T>IMessenger

Как это работает

Атрибут ObservableProperty можно использовать для анимации поля в частичном типе, например:

[ObservableProperty]
private string? name;

И он создаст наблюдаемое свойство, как показано ниже:

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

Это также будет делать с оптимизированной реализацией, поэтому конечный результат будет еще быстрее.

Примечание.

Имя созданного свойства будет создано на основе имени поля. Генератор предполагает, что поле называется либо lowerCamel, _lowerCamel либо m_lowerCamelпреобразуется, чтобы UpperCamel следовать соответствующим соглашениям об именовании .NET. Полученное свойство всегда будет иметь общедоступные методы доступа, но поле можно объявить с любой видимостью (private рекомендуется).

Выполнение кода при изменениях

Созданный код на самом деле немного сложнее, чем это, и причина этого заключается в том, что он также предоставляет некоторые методы, которые можно реализовать для перехвата в логику уведомлений, и выполнить дополнительную логику, когда свойство будет обновлено и сразу после обновления, при необходимости. То есть созданный код на самом деле аналогичен следующему:

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

Это позволяет реализовать любой из этих методов для внедрения дополнительного кода. Первые два полезны всякий раз, когда требуется выполнить некоторую логику, которая должна ссылаться только на новое значение, для которого было задано свойство. Другие два полезны всякий раз, когда у вас есть более сложная логика, которая также должна обновить некоторое состояние как для старого, так и нового значения.

Например, ниже приведен пример использования первых двух перегрузок:

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

Ниже приведен пример того, как можно использовать две другие перегрузки:

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

Вы можете реализовать только любое количество методов среди доступных или ни одного из них. Если они не реализованы (или только один), все вызовы будут только удалены компилятором, поэтому в случаях, когда эта дополнительная функциональность не требуется.

Примечание.

Созданные методы являются частичными методами без реализации, что означает, что если вы решили реализовать их, вы не можете указать явные специальные возможности для них. То есть реализация этих методов также должна быть объявлена как просто partial методы, и они всегда будут иметь частные специальные возможности. Попытка добавить явную доступность (например, добавление public или private) приведет к ошибке, так как это запрещено в C#.

Уведомление зависимых свойств

Представьте, что у вас было FullName свойство, которое вы хотели создать уведомление при каждом Name изменении. Это можно сделать с помощью атрибута NotifyPropertyChangedFor , например:

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

Это приведет к созданию созданного свойства, эквивалентного этому:

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

Уведомление зависимых команд

Представьте, что у вас была команда, состояние выполнения которой зависело от значения этого свойства. То есть при изменении свойства состояние выполнения команды должно быть недействительным и вычислено снова. Другими словами, ICommand.CanExecuteChanged следует снова подняться. Это можно сделать с помощью атрибута NotifyCanExecuteChangedFor :

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

Это приведет к созданию созданного свойства, эквивалентного этому:

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

Чтобы это работало, целевая команда должна быть некоторым IRelayCommand свойством.

Запрос проверки свойств

Если свойство объявляется в типе, наследуемом от ObservableValidator, то можно также запросить его с помощью любых атрибутов проверки, а затем запросить созданный набор, чтобы активировать проверку для этого свойства. Это можно сделать с помощью атрибута NotifyDataErrorInfo :

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

Это приведет к созданию следующего свойства:

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

После этого созданный ValidateProperty вызов проверит свойство и обновит состояние объекта, чтобы компоненты пользовательского ObservableValidator интерфейса могли реагировать на него и отображать все ошибки проверки соответствующим образом.

Примечание.

По проектированию только атрибуты полей, наследуемые от ValidationAttribute него, будут переадресованы в созданное свойство. Это делается специально для поддержки сценариев проверки данных. Все остальные атрибуты поля будут игнорироваться, поэтому в настоящее время невозможно добавить дополнительные настраиваемые атрибуты в поле и применить их к созданному свойству. Если это необходимо (например, для управления сериализацией), рассмотрите возможность использования традиционного ручного свойства.

Отправка сообщений уведомлений

Если свойство объявляется в типе, наследуемом от ObservableRecipient, можно использовать NotifyPropertyChangedRecipients атрибут для указания генератору также вставлять код для отправки сообщения об изменении свойства для изменения свойства. Это позволит зарегистрированным получателям динамически реагировать на изменения. То есть рассмотрим следующий код:

[ObservableProperty]
[NotifyPropertyChangedRecipients]
private string? name;

Это приведет к созданию следующего свойства:

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

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

После этого созданный Broadcast вызов отправляет новый PropertyChangedMessage<T> экземпляр, IMessenger используемый в текущем режиме представления, всем зарегистрированным подписчикам.

Добавление настраиваемых атрибутов

В некоторых случаях может быть полезно также использовать некоторые пользовательские атрибуты по созданным свойствам. Для этого можно просто использовать целевой [property: ] объект в списках атрибутов по аннотированных полях, а набор средств MVVM автоматически перенаправит эти атрибуты в созданные свойства.

Например, рассмотрим следующее поле:

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

Это приведет к созданию Username свойства с этими двумя [JsonRequired] и [JsonPropertyName("name")] атрибутами над ним. Вы можете использовать столько списков атрибутов, предназначенных для свойства, сколько требуется, и все из них будут перенаправляться в созданные свойства.

Примеры

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