ObservableProperty 属性

ObservableProperty 类型是一个属性,允许从带批注的字段生成可观察属性。 其目的是大幅减少定义可观察属性所需的样板代码。

注释

若要正常工作,带批注的字段需要位于具有必要基础结构的INotifyPropertyChanged中。 如果该类型是嵌套的,则声明语法树中的所有类型也必须注释为部分。 如果不这样做,将导致编译错误,因为生成器将无法为该类型生成另一个包含所请求的可观测属性的分部声明。

平台 API:ObservableProperty、、、NotifyPropertyChangedForNotifyCanExecuteChangedForNotifyDataErrorInfoNotifyPropertyChangedRecipientsICommandIRelayCommandObservableValidatorPropertyChangedMessage<T>IMessenger

工作原理

ObservableProperty 特性可用于标注分部类型中的字段,如下所示:

[ObservableProperty]
private string? name;

它将生成如下所示的可观察属性:

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

它还会在优化实现中执行此操作,因此最终结果会更快。

注释

将基于字段名称创建生成的属性的名称。 生成器假定字段命名为 lowerCamel_lowerCamelm_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 的类型中声明该属性,则也可以使用任何验证属性对它进行批注,然后请求生成的 setter 来触发该属性的验证。 这可以通过属性实现 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 工具包会自动将这些属性转发到生成的属性。

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

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

这将生成一个Username属性,其上带有这两个[JsonRequired][JsonPropertyName("name")]属性。 可以使用任意数量的面向属性的属性列表,所有这些属性都将转发到生成的属性。

示例

  • 查看 示例应用 (适用于多个 UI 框架),以查看 MVVM 工具包的操作。
  • 还可以在 单元测试中找到更多示例。