我們很高興地宣布正式推出新的 .NET 社區工具包,現在已經在NuGet上發布了8.0.0版本!這是一個重要版本,包括大量新功能、改進、優化、錯誤修復,許多反映了全新項目結構和組織的重構,這篇文章將詳細描述這些內容。
與每個社區工具包版本一樣,所有的更改都受到使用該工具包的微軟團隊和社區其他開發人員反饋的影響。我們非常感謝所有做出貢獻並不斷幫助 .NET 社區工具包變得更好的人!
.NET 社區工具包是一組適用於所有 .NET 開發人員的幫助程序和 API,獨立於任何特定的 UI 平台。該工具包由 Microsoft 維護和發布,是 .NET 基金會的一部分。它也被一些內部項目和收件箱應用程序使用,例如 Microsoft Store。從新的 8.0.0 版本開始,該項目現在位於 GitHub 上的 CommunityToolkit/dotnet 存儲庫中,其中包括作為 Toolkit 一部分的所有庫。所有可用的 API 都不依賴於任何特定的運行時或框架,因此所有 .NET 開發人員都可以使用它們。這些庫是從 .NET Standard 2.0 到 .NET 6 的多目標庫,所以它們既可以支持儘可能多的平台,又可以在與較新的運行時一起使用時進行優化以獲得最佳性能。CommunityToolkit.Mvvm(又名「微軟 MVVM 工具包」)CommunityToolkit.DiagnosticsCommunityToolkit.HighPerformance
CommunityToolkit/dotnet:
https://github.com/CommunityToolkit/dotnet
您可能想知道為什麼 .NET 社區工具包的第一個版本是 8.0.0 版本。好問題!原因是 .NET 社區工具包的所有庫最初都是Windows 社區工具包的一部分,它是幫助程序、擴展和自定義控件的集合,和自定義控件,可簡化和演示為 Windows 10 和 Windows 11 構建 UWP 和 .NET 應用程序的常見開發人員任務。
隨着時間的推移,僅針對 .NET 且沒有任何 Windows 特定依賴項的 API 數量不斷增加,我們決定將它們拆分到一個單獨的項目中,以便它們可以獨立發展,並且對於不進行任何 Windows 開發的 .NET 開發人員來說也更容易找到。.NET 社區工具包就是這樣誕生的。這也使我們更容易且更好地組織文檔,現在每個特定於平台的工具包都有其單獨的文檔。
由於分支之前的 Windows 社區工具包的最後一個版本是 7.1.x,我們決定遵循該語義版本號以使現有用戶更容易理解轉換,這就是 .NET 社區工具包的第一個版本是 8.0.0 的原因。展望未來,它將與 Windows 社區工具包分開進行版本控制,因為每個項目都有自己獨立的路線圖和發布時間表。
搞清楚這些之後,現在讓我們深入了解 .NET 社區工具包庫的這個新的主要版本中的所有新功能!
Windows 社區工具包
https://github.com/CommunityToolkit/WindowsCommunityToolkit
文檔
https://docs.microsoft.com/zh-cn/dotnet/communitytoolkit/?ocid=AID3052907
正如之前在7.0 版本中宣布的那樣,.NET 社區工具包的主要組件之一是 MVVM 工具包:一個現代的、快速的、平台無關和模塊化的 MVVM 庫。這與 Microsoft Store、照片應用程序等使用的 MVVM 庫相同!MVVM 工具包受到MvvmLight的啟發,並且由於該庫已被棄用,MVVM工具包也就是MvvmLight的官方替代品。我們在開發 MVVM 工具包的同時也與Laurent Bugnion合作,他支持 MVVM 工具包作為現有 MvvmLight 用戶的升級道路(我們也有這方面的遷移文檔)。平台無關:意味着它不依賴於特定的 UI 框架。您可以使用它在 UWP、WinUI 3、MAUI、WPF、Avalonia、Uno 等之間共享代碼!運行時無關:該庫支持多目標並支持低至 .NET Standard 2.0的環境,這意味着您可以在現代運行時(例如 .NET 6)上運行時獲得性能改進,並且即使在 .NET 框架上仍然可以使用它。易於上手和使用:對使用的應用程序結構或編碼模式沒有嚴格的要求。您可以使用該庫來適應您自己的架構和風格。À la carte:所有組件都是獨立的,也可以單獨使用。沒有強迫您使用「全部」的方法:如果您只想使用整個庫中的一種類型,您可以做得很好,然後根據需要逐漸開始使用更多功能。參考實現:所有可用的 API 都是精簡和高性能的,為 .NET 基類庫中包含的接口提供「參考實現」,但缺乏直接使用它們的具體類型。例如,您將能夠找到 INotifyPropertyChanged 或 ICommand 等接口的「參考實現」。
7.0 版本:
https://blogs.windows.com/windowsdeveloper/2021/03/16/announcing-windows-community-toolkit-v7-0/
MvvmLight:
https://www.nuget.org/packages/MvvmLight
Laurent Bugnion:
https://twitter.com/LBugnion
遷移文檔:
https://docs.microsoft.com/zh-cn/dotnet/communitytoolkit/mvvm/migratingfrommvvmlight?ocid=AID3052907
MVVM Toolkit 8.0.0 版本中最大的新特性是新的 MVVM 源代碼生成器,它旨在大大減少使用 MVVM 設置應用程序所需的樣板代碼。與我們在 7.1.0 中發布的預覽生成器相比,它們也被完全重寫為增量生成器,這意味着它們的運行速度將比以前更快,並且即使在處理大型項目時也會有助於保持 IDE 的快速響應。
您可以在此處找到有關新源生成器的所有文檔,如果您更喜歡視頻版本,James Montemagno 還製作了幾個關於它們的視頻。讓我們也回顧一下由源生成器提供支持的主要功能,您可以在 MVVM 工具包中找到這些功能。MVVM 源代碼生成器:
https://docs.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview?ocid=AID3052907
https://devblogs.microsoft.com/ifdef-windows/windows-community-toolkit-7-1-preview-release/?ocid=AID3052907https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.mdhttps://www.youtube.com/watch?v=AXpTeiWtbC8&t=600s
創建命令可能是非常重複的,在為每個方法設置一個屬性的需求下,我們希望以抽象的方式向應用程序中用於調用它們的各種UI組件(如按鈕)公開這些方法。
這就是新的 [RelayCommand] 屬性發揮作用的地方:這將使 MVVM 工具包自動生成具有正確簽名的命令(使用庫中包含的 RelayCommand 類型),具體取決於帶注釋的方法。
作為比較,以下是從前人們通常會如何設置命令的代碼:
private IRelayCommand<User> greetUserCommand;public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);private void GreetUser(User user){ Console.WriteLine($"Hello {user.Name}!");}現在可以簡化為:
[RelayCommand]private void GreetUser(User user){ Console.WriteLine($"Hello {user.Name}!");}源生成器將負責根據帶注釋的方法創建正確的 GreetUserCommand 屬性。此外,您可以指定 CanExecute 方法,還可以控制異步命令的並發級別。還有其他選項可以微調生成的命令的行為,您可以在我們的文檔中了解更多信息。https://docs.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/?ocid=AID3052907
編寫可觀察屬性可能非常冗長,尤其是當您還必須添加額外的邏輯來處理被通知的依賴屬性時。現在,通過使用 MVVM 工具包中的新屬性,並讓源生成器在幕後創建可觀察的屬性,所有這些都可以大大簡化。這些新屬性是[ObservableProperty]、[NotifyPropertyChangedFor]和[NotifyCanExecuteChangedFor]、[NotifyDataErrorInfo]和[NotifyPropertyChangedRecipients]。接下來讓我們快速回顧一下所有這些新屬性可以做什麼。考慮一個場景,其中有兩個可觀察屬性,一個依賴屬性和上面定義的命令,當兩個可觀察屬性中的任何一個發生變化時,都需要通知依賴屬性和命令。也就是說,每當FirstName或LastName更改時,也會通知FullName以及GreetUserCommand。這就是過去的做法:
private string? firstName;public string? FirstName{ get => firstName; set { if (SetProperty(ref firstName, value)) { OnPropertyChanged(nameof(FullName)); GreetUserCommand.NotifyCanExecuteChanged(); } }}private string? lastName;public string? LastName{ get => lastName; set { if (SetProperty(ref lastName, value)) { OnPropertyChanged(nameof(FullName)); GreetUserCommand.NotifyCanExecuteChanged(); } }}public string? FullName => $"{FirstName} {LastName}";現在可以全部改寫如下:
[ObservableProperty][NotifyPropertyChangedFor(nameof(FullName))][NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]private string? firstName;[ObservableProperty][NotifyPropertyChangedFor(nameof(FullName))][NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]private string? lastName;public string? FullName => $"{FirstName} {LastName}";MVVM 工具包將處理這些屬性的代碼生成,包括插入所有邏輯以引發指定的屬性更改或執行更改事件。
但是等等,還有更多的特性!當使用[ObservableProperty]生成可觀察屬性時,MVVM 工具包 現在還將生成兩個沒有實現的部分方法:On<PROPERTY_NAME>Changing和On<PROPERTY_NAME>Changed。這些方法可用於在更改屬性時注入額外的邏輯,而無需回退到使用手動屬性。請注意,由於這兩個方法是部分的、返回 void 且沒有定義,如果未實現它們,C# 編譯器將完全刪除它們,這意味着它們在不使用時會消失並且不會添加到應用程序中間。這是如何使用它們的示例:
[ObservableProperty]private string name;partial void OnNameChanging(string name){ Console.WriteLine($"The name is about to change to {name}!");}partial void OnNameChanged(string name){ Console.WriteLine($"The name just changed to {name}!");}當然,您也可以只使用這兩種方法中的一種,或者不使用任何一種。從上面的代碼片段中,源生成器將生成類似於以下的代碼:public string Name{ get => name; set { if (!EqualityComparer<string>.Default.Equals(name, value)) { OnNameChanging(value); OnPropertyChanging(); name = value; OnNameChanged(); OnPropertyChanged(); } }}partial void OnNameChanging(string name);partial void OnNameChanged(string name);[ObservableProperty]屬性還支持驗證:如果表示屬性的任何字段具有一個或多個繼承自ValidationAttribute的屬性,這些屬性將自動複製到生成的屬性中,因此在使用ObservableValidator創建可驗證的屬性時也完全支持這種方法。如果您還希望在設置其值時驗證該屬性,您還可以添加[NotifyDataErrorInfo]以在屬性設置器中生成驗證代碼。
[ObservableProperty]還有更多可用的功能,就像命令一樣,您可以閱讀更多關於它們的信息並在我們的文檔中查看更多示例。
https://docs.microsoft.com/zh-cn/dotnet/communitytoolkit/mvvm/generators/overview?ocid=AID3052907
[RelayCommand]屬性中添加了一個新屬性,可用於指示源生成器在原始命令旁邊生成取消命令。此取消命令可用於取消異步命令的執行。
這也展示了[RelayCommand]如何自動適應異步方法和接受參數的方法,並在後台創建異步命令的實現。這還啟用了其他功能,例如易於設置綁定以顯示進度指示器等等。
這是如何使用它們的示例:
[RelayCommand(IncludeCancelCommand = true)]private async Task DoWorkAsync(CancellationToken token){ // 使用取消支持做一些長期運行的工作}private AsyncRelayCommand? doWorkCommand;public IAsyncRelayCommand DoWorkCommand => doWorkCommand ??= new AsyncRelayCommand(DoWorkAsync);ICommand? doWorkCancelCommand;public ICommand DoWorkCancelCommand => doWorkCancelCommand ??= IAsyncRelayCommandExtensions.CreateCancelCommand(UpdateSomethingCommand);生成的代碼與IAsyncRelayCommandExtensions.CreateCancelCommand API中的邏輯相結合,讓您只需一行代碼即可生成命令,在工作開始或運行時通知 UI,並具有自動並發控制(命令是 當它已經運行時命令是默認禁用的)。每當主命令開始或結束運行時,將通知單獨的取消命令,並在執行時向傳遞給主命令包裝的方法的令牌發出取消信號。所有這些,完全抽象出來,只需一個屬性即可輕鬆訪問。
我們還添加了一個新的[NotifyPropertyChangedRecipients]屬性,該屬性可用於從繼承自ObservableRecipient(或使用 [ObservableRecipient] 注釋的類型)生成的可觀察屬性。使用它將生成對 Broadcast 方法的調用,以向所有其他訂閱組件發送有關剛剛發生的屬性更改的消息。這在視圖模型的屬性更改還需要通知應用程序中的其他組件的情況下很有用(假設有一個 IsLoggedIn 布爾屬性,當用戶登錄時更新;這可以通知並觸發應用程序來刷新 Broadcast 消息)。它可以按如下方式使用:
[ObservableProperty][NotifyPropertyChangedRecipients]private string name;
public string Name{ get => name; set { if (!EqualityComparer<string>.Default.Equals(name, value)) { OnNameChanging(value); OnPropertyChanging(); string oldValue = name; name = value; Broadcast(oldValue, value, nameof(Name)); OnNameChanged(); OnPropertyChanged(); } }}
這是另一個增強生成的屬性並確保它們可以在幾乎所有場景中使用而不會被迫回退到手動屬性的功能。
如果有一個必須從特定類型繼承的視圖模型,但您還想向其中注入 INotifyPropertyChanged 支持,或者讓它也從 ObservableRecipient 繼承以訪問其 API,該怎麼辦?MVVM 工具包現在通過引入代碼生成屬性來解決這個問題,這些屬性允許將這些類型的邏輯注入到任意類中。它們是[INotifyPropertyChanged]、[ObservableObject]和[ObservableRecipient]。將它們添加到一個類將導致 MVVM 工具包源代碼生成器將該類型的所有邏輯包含到該類中,就好像該類也繼承自該類型一樣。例如:
[INotifyPropertyChanged]partial class MyObservableViewModel : DatabaseItem{}此MyObservableViewModel將像您所期望的那樣繼承自DatabaseItem,但使用[INotifyPropertyChanged]將使其也支持INotifyPropertyChanged,以及ObservableObject自身包含的所有幫助 API。
我們仍然建議在需要時從基本類型(例如ObservableObject)繼承,因為這也有助於減少二進制大小,但是在需要的時候以這種方式注入代碼的能力可以幫助在無法改變視圖模型的基本類型的情況下解決c#的限制,就像上面的例子一樣。MVVM 工具包 中另一個常用的特性是IMessenger接口,它是一種類型合約,可用於在不同對象之間交換消息。
這對於解耦應用程序的不同模塊而不必保持對引用類型的強引用很有用。還可以將消息發送到特定通道,由令牌唯一標識,並在應用程序的不同部分具有不同的信使。
WeakReferenceMessenger:它不會固定收件人並允許收集他們。這是通過依賴句柄實現的,這是一種特殊類型的 GC 引用,它允許此信使確保始終允許收集已註冊的接收者,即使已註冊的處理程序將它們引用回來,但不存在對它們的其他未完成的強引用。StrongReferenceMessenger:這是一個信使實現,它對已註冊的接收者進行根權限化,以確保它們保持活躍狀態,即使信使是唯一引用它們的對象。下面是一個如何使用這個接口的小例子:
// 聲明消息public sealed record LoggedInUserChangedMessage(User user);// 明確註冊收件人...messenger.Register<MyViewModel, LoggedInUserChangedMessage>(this, static (r, m) =>{// 在這裡處理消息,r 是接收者,m 是接收者// 輸入消息。使用作為輸入傳遞的接收者使得 // ambda 表達式不捕獲「this」,從而提高性能。});// ... 或者讓視圖模型實現 IRecipient<TMessage>......class MyViewModel : IRecipient<LoggedInUserChangedMessage>{ public void Receive(LoggedInUserChangedMessage message) { // 在這裡處理消息 }}// ... 然後通過接口註冊(其他API也可用)messenger.Register<LoggedInuserChangedMessage>(this);// 從其他模塊發送消息messenger.Send(new LoggedInUserChangedMessage(user));由於新提供的公共 DependentHandle API,這個新版本的 MVVM 工具包中的信使實現在 .NET 6 中得到了高度優化,它允許信使類型變得比以前更快,並提供完全零分配的消息廣播。以下是一些基準,展示了 MVVM 工具包中的信使與其他廣泛使用的 MVVM 庫中的其他幾種等效類型的比較:
方法中位數錯誤標準差比率比率標準差第0代第一代已分配MVVMToolkitStrong4.025 ms0.0177 ms0.0147 ms10–––MVVMToolkitWeak7.549 ms0.0815 ms0.0762 ms1.870.02–––MvvmCrossStrong11.483 ms0.0226 ms0.0177 ms2.850.019687.5–41,824,022 BMvvmCrossWeak13.941 ms0.1865 ms0.1744 ms3.470.049687.5–41,824,007 BMVVMLight52.929 ms0.1295 ms0.1011 ms13.140.067600–33,120,010 BStylet91.540 ms0.6362 ms0.4967 ms22.730.1735500–153,152,352 BMvvmGen141.743 ms2.7249 ms2.7983 ms35.310.719250–83,328,348 BCatel148.867 ms2.6825 ms2.5093 ms36.940.645250–22,736,316 BPrism150.077 ms0.5359 ms0.4184 ms37.260.131750025076,096,900 BCaliburnMicro280.740 ms3.7625 ms3.1418 ms69.740.82880002000381,859,608 BMauiMessagingCenter673.656 ms1.7619 ms1.3755 ms167.260.638000–35,588,776 B每個基準測試運行涉及向 100 個收件人發送 4 條不同的消息 1000 次。如您所見,WeakReferenceMessenger和StrongReferenceMessenger都是迄今為止最快的,也是唯一一個在廣播消息時甚至不分配一個字節的 。
https://github.com/dotnet/runtime/pull/54246這個新版本的 MVVM 工具包 還將所有可觀察的分組集合類型從CommunityToolkit.Common包移動到CommunityToolkit.Mvvm,同時還進行了一些重大更改以改進 API 表面並使其在更多場景中有用。這些 API 在處理分組項目時特別有用(例如,顯示聯繫人列表),它們現在還包括擴展以極大地促進常見操作,例如在組內的正確位置插入項目(使用默認比較器 或輸入一個,並在需要時創建一個新組)。這個視頻,展示了來自 MVVM Toolkit 示例應用程序的簡單聯繫人視圖: