ObservableProperty 屬性

這個 ObservableProperty 型別是一個屬性,允許從註解欄位產生可觀察的屬性。 其目的是大幅減少定義可觀察性質所需的標準模板數量。

備註

為了運作,註解欄位必須屬於具有必要基礎設施的INotifyPropertyChanged。 若型別是巢狀,宣告語法樹中的所有類型也必須標註為部分型。 若不這麼做,將會導致編譯錯誤,因為產生器將無法為該型別產生另一個包含所要求之可觀察屬性的 partial 宣告。

平台 API:ObservableProperty, NotifyPropertyChangedFor, NotifyCanExecuteChangedFor, NotifyDataErrorInfo, NotifyPropertyChangedRecipients, ICommand, IRelayCommand, 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 = false;
    }

    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 的型別中宣告,也可以使用任何驗證屬性加以註記,然後要求產生的 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")] 屬性。 你可以使用任意多的屬性清單來針對該屬性,這些屬性都會被轉發到產生的屬性中。

Examples

  • 可以看看範例 應用程式 (針對多個 UI 框架),看看 MVVM 工具包的運作。
  • 你也可以在 單元測驗中找到更多範例。