ObservableProperty 特性

ObservableProperty 类型是一种允许从带批注字段生成可观察属性的特性。 其用途是显著减少定义可观察属性所需的样本量。

注意

带批注字段需要位于具有必要 INotifyPropertyChanged 基础结构的分部类中才能发挥作用。 如果对类型进行嵌套,则必须也将声明语法树中的所有类型批注为分部。 否则,将导致编译错误,因为生成器将无法使用所请求的可观察属性生成该类型的不同分部声明。

平台 API:ObservablePropertyNotifyPropertyChangedForNotifyCanExecuteChangedForNotifyDataErrorInfoNotifyPropertyChangedRecipientsICommandIRelayCommandObservableValidatorPropertyChangedMessage<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 方法,并且它们总是隐式具有专用可访问性。 尝试添加显式可访问性(例如添加 publicprivate)将导致错误,因为 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 对象的状态,以便 UI 组件可以对其做出反应,并相应地显示任何验证错误。

注意

根据设计,只有继承自 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 调用将使用当前 viewmodel 中正在使用的 IMessenger 实例向所有注册订阅者发送新的 PropertyChangedMessage<T>

添加自定义属性

在某些情况下,在生成的属性上添加一些自定义属性可能会很有用。 为此,你只需在带注释的字段上使用属性列表中的 [property: ] 目标,MVVM Toolkit 就会自动将这些属性转发到生成的属性。

例如,请考虑如下所示的字段:

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

这将生成一个 Username 属性,其中包含 [JsonRequired][JsonPropertyName("name")] 这两个属性。 可根据需要使用任意多个针对属性的属性列表,所有这些属性列表都将转发到生成的属性。

示例

  • 查看示例应用(适用于多个 UI 框架),以了解 MVVM 工具包的实际运行情况。
  • 还可以在单元测试中查找更多示例。