ObservableObject是實作和 INotifyPropertyChanging 介面可INotifyPropertyChanged觀察之物件的基類。 它可以做為支援屬性變更通知之各種物件的起點。
運作方式
ObservableObject 具有下列主要功能:
- 它提供 和
INotifyPropertyChanging的基底實作INotifyPropertyChanged,公開PropertyChanged和PropertyChanging事件。 - 它提供一系列
SetProperty方法,可用來輕鬆地設定繼承自ObservableObject的類型屬性值,並自動引發適當的事件。 - 它提供
SetPropertyAndNotifyOnCompletion方法,這類似於SetProperty,但能夠設定Task屬性,並在指派的工作完成時自動引發通知事件。 - 它會公開
OnPropertyChanged和OnPropertyChanging方法,這些方法可以在衍生型別中覆寫,以自定義如何引發通知事件。
Simple 屬性
以下範例說明如何實作自定義屬性的通知支援:
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# 編譯程式快取回呼函式並執行一些效能改善。 這是因為我們不只是直接存取usersetter 中的欄位或valuesetter 中的 參數,而是只使用 Lambda 表達式的輸入參數。
方法 SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string) 會讓建立這些包裝屬性非常簡單,因為它會同時處理擷取和設定目標屬性,同時提供非常精簡的 API。
注意
相較於使用 LINQ 表達式的這個方法實作,特別是透過型 Expression<Func<T>> 別的參數,而不是狀態和回呼參數,可達成這種方式的效能改善確實相當重要。 特別是,此版本比使用 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>實例,並啟用這個方法的必要通知邏輯。 TaskNotifier如果您只有一般Task類型,也可以直接使用類型。
注意
方法SetPropertyAndNotifyOnCompletion的目的是要取代封裝中Microsoft.Toolkit型別的使用NotifyTaskCompletion<T>方式。 如果使用這個類型,它只能取代為 inner Task (或 Task<TResult>) 屬性,然後使用 SetPropertyAndNotifyOnCompletion 方法來設定其值並引發通知變更。 類型公開 NotifyTaskCompletion<T> 的所有屬性都可直接在 實例上使用 Task 。