ObservableObject

ObservableObject 是供實作 INotifyPropertyChangedINotifyPropertyChanging 介面而成為可觀察之物件所使用的基底類別。 它可作為所有需要支援屬性變更通知的各種物件的起始點。

平臺 API:ObservableObject、、 TaskNotifierTaskNotifier<T>

運作方式

ObservableObject 具有下列主要功能:

  • 它為 INotifyPropertyChangedINotifyPropertyChanging 提供基底實作,並公開 PropertyChangedPropertyChanging 事件。
  • 它提供一系列 SetProperty 方法,可用來輕鬆地設定繼承自 ObservableObject的類型屬性值,並自動引發適當的事件。
  • 它提供 SetPropertyAndNotifyOnCompletion 方法,這類似於 SetProperty ,但能夠設定 Task 屬性,並在指派的工作完成時自動引發通知事件。
  • 它會提供 OnPropertyChangedOnPropertyChanging 方法,這些方法可在衍生型別中覆寫,以自訂引發通知事件的方式。

簡單屬性

以下範例說明如何實作自定義屬性的通知支援:

public class User : ObservableObject
{
    private string name;

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

提供的 SetProperty<T>(ref T, T, string) 方法會檢查屬性的目前值,若不同則加以更新,並自動引發相關事件。 屬性名稱會透過屬性的使用 [CallerMemberName] 自動擷取,因此不需要手動指定要更新的屬性。

封裝非可觀察模型

例如,在處理資料庫項目時,常見的情況是建立一個包裝用的「可繫結」模型,傳遞資料庫模型的屬性,並在需要時引發屬性變更通知。 當需要為未實作 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,這是屬性 setter 內的輸入值。
  • TModel model 是我們包裝的目標模型,在此案例中,我們會傳遞儲存在欄位中的 user 實例。
  • Action<TModel, T> callback 是一個函式,如果屬性的新值與目前值不同,而且必須設定 屬性,就會叫用此函式。 這會由這個回呼函式來完成,此函式會接收作為目標模型的輸入,以及要設定的新屬性值。 在此情況下,我們只是將輸入值(我們呼叫 n的)指派給 Name 屬性(藉由執行 u.Name = n)。 請務必避免從目前範圍擷取值,並且只與指定做為回呼輸入的值互動,因為這可讓 C# 編譯程式快取回呼函式並執行一些效能改善。 這就是為什麼我們在這裡不是直接存取 user 欄位或 setter 中的 value 參數,而是只使用 Lambda 運算式的輸入參數。

方法 SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) 會讓建立這些包裝屬性非常簡單,因為它會同時處理擷取和設定目標屬性,同時提供非常精簡的 API。

注意

相較於使用 LINQ 運算式來實作此方法,特別是透過 Expression<Func<T>> 型別的參數而非 state 和 callback 參數來實作時,以這種方式可獲得的效能提升確實相當顯著。 特別是,此版本比使用 LINQ 運算式快約 200 倍,而且完全不會進行任何記憶體配置。

處理 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> 執行個體,並為這個方法啟用必要的通知邏輯。 如果您只有一般的 Task,也可以直接使用 TaskNotifier 類型。

注意

SetPropertyAndNotifyOnCompletion 方法旨在取代對來自 Microsoft.Toolkit 套件之 NotifyTaskCompletion<T> 型別的使用。 如果使用的是這種類型,則可改為只使用內部的 Task(或 Task<TResult>)屬性,然後使用 SetPropertyAndNotifyOnCompletion 方法來設定其值並引發變更通知。 由 NotifyTaskCompletion<T> 類型公開的所有屬性都可直接在 Task 實例上使用。

範例

  • 查看 範例應用程式 (適用於多個 UI 架構),以查看 MVVM 工具組的運作情形。
  • 您也可以在單元測試中找到更多範例。