ObservableObject
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# 編譯程式快取回呼函式並執行一些效能改善。 這是因為我們不只是直接存取user
setter 中的欄位或value
setter 中的 參數,而是只使用 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
。