我們很高興地宣布正式推出新的 .NET 社區(qū)工具包,現(xiàn)在已經(jīng)在NuGet上發(fā)布了8.0.0版本!這是一個重要版本,包括大量新功能、改進、優(yōu)化、錯誤修復(fù)和許多反映了全新項目結(jié)構(gòu)和組織的重構(gòu),這篇博文將詳細(xì)描述這些內(nèi)容。
與每個社區(qū)工具包版本一樣,所有的更改都受到使用該工具包的微軟團隊和社區(qū)其他開發(fā)人員反饋的影響。我們非常感謝所有做出貢獻并不斷幫助 .NET 社區(qū)工具包變得更好的人!
.NET 社區(qū)工具包中有什么?
.NET 社區(qū)工具包是一組適用于所有 .NET 開發(fā)人員的幫助程序和 API,獨立于任何特定的 UI 平臺。該工具包由 Microsoft 維護和發(fā)布,是 .NET 基金會的一部分。它也被一些內(nèi)部項目和收件箱應(yīng)用程序使用,例如 Microsoft Store。從新的 8.0.0 版本開始,該項目現(xiàn)在位于 GitHub 上的 CommunityToolkit/dotnet存儲庫中,其中包括作為 Toolkit 一部分的所有庫。
所有可用的 API 都不依賴于任何特定的運行時或框架,因此所有 .NET 開發(fā)人員都可以使用它們。這些庫是從 .NET Standard 2.0 到 .NET 6 的多目標(biāo)庫,所以它們既可以支持盡可能多的平臺,又可以在與較新的運行時一起使用時進行優(yōu)化以獲得最佳性能。
.NET 社區(qū)工具包中的庫包括:
-
CommunityToolkit.Common
-
CommunityToolkit.Mvvm(又名“微軟 MVVM 工具包”)
-
CommunityToolkit.Diagnostics
-
CommunityToolkit.HighPerformance
社區(qū)工具包歷史一覽?
您可能想知道為什么 .NET 社區(qū)工具包的第一個版本是 8.0.0 版本。好問題!原因是 .NET 社區(qū)工具包的所有庫最初都是Windows 社區(qū)工具包的一部分,它是幫助程序、擴展和自定義控件的集合,和自定義控件,可簡化和演示為 Windows 10 和 Windows 11 構(gòu)建 UWP 和 .NET 應(yīng)用程序的常見開發(fā)人員任務(wù)。
隨著時間的推移,僅針對 .NET 且沒有任何 Windows 特定依賴項的 API 數(shù)量不斷增加,我們決定將它們拆分到一個單獨的項目中,以便它們可以獨立發(fā)展,并且對于不進行任何 Windows 開發(fā)的 .NET 開發(fā)人員來說也更容易找到。.NET 社區(qū)工具包就是這樣誕生的。這也使我們更容易且更好地組織文檔,現(xiàn)在每個特定于平臺的工具包都有其單獨的文檔。
由于分支之前的 Windows 社區(qū)工具包的最后一個版本是 7.1.x,我們決定遵循該語義版本號以使現(xiàn)有用戶更容易理解轉(zhuǎn)換,這就是 .NET 社區(qū)工具包的第一個版本是 8.0.0 的原因. 展望未來,它將與 Windows 社區(qū)工具包分開進行版本控制,因為每個項目都有自己獨立的路線圖和發(fā)布時間表。
搞清楚這些之后,現(xiàn)在讓我們深入了解.NET 社區(qū)工具包庫的這個新的主要版本中的所有新功能!
MVVM 工具包
正如之前在7.0 版本中宣布的那樣,.NET 社區(qū)工具包的主要組件之一是 MVVM 工具包:一個現(xiàn)代的、快速的、平臺無關(guān)和模塊化的 MVVM 庫。這與 Microsoft Store、照片應(yīng)用程序等使用的 MVVM 庫相同!
MVVM 工具包受到MvvmLight的啟發(fā),并且由于該庫已被棄用,MVVM工具包也就是MvvmLight的官方替代品。我們在開發(fā) MVVM 工具包的同時也與Laurent Bugnion合作,他支持 MVVM 工具包作為現(xiàn)有MvvmLight 用戶的升級道路(我們也有這方面的遷移文檔)。
MVVM工具包是基于以下幾個關(guān)鍵原則構(gòu)建的:
-
平臺無關(guān):意味著它不依賴于特定的 UI 框架。您可以使用它在 UWP、WinUI 3、MAUI、WPF、Avalonia、Uno 等之間共享代碼!
-
運行時無關(guān):該庫支持多目標(biāo)并支持低至 .NET Standard 2.0的環(huán)境,這意味著您可以在現(xiàn)代運行時(例如 .NET 6)上運行時獲得性能改進,并且即使在 .NET 框架上仍然可以使用它。
-
易于上手和使用:對使用的應(yīng)用程序結(jié)構(gòu)或編碼模式?jīng)]有嚴(yán)格的要求。您可以使用該庫來適應(yīng)您自己的架構(gòu)和風(fēng)格。
-
à la carte:所有組件都是獨立的,也可以單獨使用。沒有強迫您使用“全部”的方法:如果您只想使用整個庫中的一種類型,您可以做得很好,然后根據(jù)需要逐漸開始使用更多功能。
-
參考實現(xiàn):所有可用的 API 都是精簡和高性能的,為 .NET 基類庫中包含的接口提供“參考實現(xiàn)”,但缺乏直接使用它們的具體類型。例如,您將能夠找到?INotifyPropertyChanged?或?ICommand?等接口的“參考實現(xiàn)”。
MVVM 工具包源生成器
MVVM Toolkit 8.0.0 版本中最大的新特性是新的 MVVM 源代碼生成器,它旨在大大減少使用 MVVM 設(shè)置應(yīng)用程序所需的樣板代碼。與我們在 7.1.0 中發(fā)布的預(yù)覽生成器相比,它們也被完全重寫為增量生成器,這意味著它們的運行速度將比以前更快,并且即使在處理大型項目時也會有助于保持 IDE 的快速響應(yīng)。
您可以在此處找到有關(guān)新源生成器的所有文檔,如果您更喜歡視頻版本,James Montemagno 還制作了幾個關(guān)于它們的視頻。讓我們也回顧一下由源生成器提供支持的主要功能,您可以在 MVVM 工具包中找到這些功能。
命令
創(chuàng)建命令可能是非常重復(fù)的,在為每個方法設(shè)置一個屬性的需求下,我們希望以抽象的方式向應(yīng)用程序中用于調(diào)用它們的各種UI組件(如按鈕)公開這些方法。
這就是新的?[RelayCommand]?屬性發(fā)揮作用的地方:這將使 MVVM 工具包自動生成具有正確簽名的命令(使用庫中包含的?RelayCommand?類型),具體取決于帶注釋的方法。
作為比較,以下是從前人們通常會如何設(shè)置命令的代碼:
private IRelayCommand<User> greetUserCommand;
public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
現(xiàn)在可以簡化為:
[RelayCommand]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
源生成器將負(fù)責(zé)根據(jù)帶注釋的方法創(chuàng)建正確的?GreetUserCommand?屬性。此外,您可以指定?CanExecute?方法,還可以控制異步命令的并發(fā)級別。還有其他選項可以微調(diào)生成的命令的行為,您可以在我們的文檔中了解更多信息。
可觀察的屬性
編寫可觀察屬性可能非常冗長,尤其是當(dāng)您還必須添加額外的邏輯來處理被通知的依賴屬性時?,F(xiàn)在,通過使用 MVVM 工具包中的新屬性,并讓源生成器在幕后創(chuàng)建可觀察的屬性,所有這些都可以大大簡化。
這些新屬性是?[ObservableProperty]、[NotifyPropertyChangedFor]?和?[NotifyCanExecuteChangedFor]、[NotifyDataErrorInfo]?和?[NotifyPropertyChangedRecipients]。接下來讓我們快速回顧一下所有這些新屬性可以做什么。
考慮一個場景,其中有兩個可觀察屬性,一個依賴屬性和上面定義的命令,當(dāng)兩個可觀察屬性中的任何一個發(fā)生變化時,都需要通知依賴屬性和命令。也就是說,每當(dāng)?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}";
現(xiàn)在可以全部改寫如下:
[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 工具包將處理這些屬性的代碼生成,包括插入所有邏輯以引發(fā)指定的屬性更改或執(zhí)行更改事件。
但是等等,還有更多的特性!當(dāng)使用?[ObservableProperty]?生成可觀察屬性時,MVVM 工具包 現(xiàn)在還將生成兩個沒有實現(xiàn)的部分方法:On<PROPERTY_NAME>Changing?和?On<PROPERTY_NAME>Changed?。這些方法可用于在更改屬性時注入額外的邏輯,而無需回退到使用手動屬性。請注意,由于這兩個方法是部分的、返回 void 且沒有定義,如果未實現(xiàn)它們,C# 編譯器將完全刪除它們,這意味著它們在不使用時會消失并且不會添加到應(yīng)用程序中間。
這是如何使用它們的示例:
[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}!");
}
當(dāng)然,您也可以只使用這兩種方法中的一種,或者不使用任何一種。
從上面的代碼片段中,源生成器將生成類似于以下的代碼:???????
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?的屬性,這些屬性將自動復(fù)制到生成的屬性中,因此在使用?ObservableValidator?創(chuàng)建可驗證的屬性時也完全支持這種方法。如果您還希望在設(shè)置其值時驗證該屬性,您還可以添加?[NotifyDataErrorInfo]?以在屬性設(shè)置器中生成驗證代碼。
[ObservableProperty]還有更多可用的功能,就像命令一樣,您可以閱讀更多關(guān)于它們的信息并在我們的文檔中查看更多示例。
取消對命令的支持
[RelayCommand]?屬性中添加了一個新屬性,可用于指示源生成器在原始命令旁邊生成取消命令。此取消命令可用于取消異步命令的執(zhí)行。
這也展示了?[RelayCommand]?如何自動適應(yīng)異步方法和接受參數(shù)的方法,并在后臺創(chuàng)建異步命令的實現(xiàn)。這還啟用了其他功能,例如易于設(shè)置綁定以顯示進度指示器等等!
這是如何使用它們的示例:???????
[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?中的邏輯相結(jié)合,讓您只需一行代碼即可生成命令,在工作開始或運行時通知 UI,并具有自動并發(fā)控制(命令是 當(dāng)它已經(jīng)運行時命令是默認(rèn)禁用的)。每當(dāng)主命令開始或結(jié)束運行時,將通知單獨的取消命令,并在執(zhí)行時向傳遞給主命令包裝的方法的令牌發(fā)出取消信號。所有這些,完全抽象出來,只需一個屬性即可輕松訪問。
對生成屬性的Broadcast的更改支持
我們還添加了一個新的?[NotifyPropertyChangedRecipients]?屬性,該屬性可用于從繼承自?ObservableRecipient(或使用?[ObservableRecipient]?注釋的類型)生成的可觀察屬性。使用它將生成對 Broadcast 方法的調(diào)用,以向所有其他訂閱組件發(fā)送有關(guān)剛剛發(fā)生的屬性更改的消息。這在視圖模型的屬性更改還需要通知應(yīng)用程序中的其他組件的情況下很有用(假設(shè)有一個 IsLoggedIn 布爾屬性,當(dāng)用戶登錄時更新;這可以通知并觸發(fā)應(yīng)用程序來刷新Broadcast消息)。
它可以按如下方式使用:???????
[ObservableProperty]
[NotifyPropertyChangedRecipients]
private string name;
這將產(chǎn)生與此類似的代碼:
???????
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();
}
}
}
這是另一個增強生成的屬性并確保它們可以在幾乎所有場景中使用而不會被迫回退到手動屬性的功能。
ViewModel 組成
C# 沒有多重繼承,這有時會成為障礙。
如果有一個必須從特定類型繼承的視圖模型,但您還想向其中注入 INotifyPropertyChanged 支持,或者讓它也從 ObservableRecipient 繼承以訪問其 API,該怎么辦?
MVVM 工具包現(xiàn)在通過引入代碼生成屬性來解決這個問題,這些屬性允許將這些類型的邏輯注入到任意類中。它們是?[INotifyPropertyChanged]、[ObservableObject]?和?[ObservableRecipient]。
將它們添加到一個類將導(dǎo)致 MVVM 工具包源代碼生成器將該類型的所有邏輯包含到該類中,就好像該類也繼承自該類型一樣。例如:???????
[INotifyPropertyChanged]
partial class MyObservableViewModel : DatabaseItem
{
}
此?MyObservableViewModel?將像您所期望的那樣繼承自?DatabaseItem,但使用?[INotifyPropertyChanged]?將使其也支持?INotifyPropertyChanged,以及?ObservableObject?自身包含的所有幫助 API。
我們?nèi)匀唤ㄗh在需要時從基本類型(例如?ObservableObject)繼承,因為這也有助于減少二進制大小,但是在需要的時候以這種方式注入代碼的能力可以幫助在無法改變視圖模型的基本類型的情況下解決c#的限制,就像上面的例子一樣。
改進的 Messenger API?
MVVM 工具包 中另一個常用的特性是?IMessenger?接口,它是一種類型合約,可用于在不同對象之間交換消息。
這對于解耦應(yīng)用程序的不同模塊而不必保持對引用類型的強引用很有用。還可以將消息發(fā)送到特定通道,由令牌唯一標(biāo)識,并在應(yīng)用程序的不同部分具有不同的信使。
MVVM 工具包 提供了這個接口的兩種實現(xiàn):
-
WeakReferenceMessenger:它不會固定收件人并允許收集他們。這是通過依賴句柄實現(xiàn)的,這是一種特殊類型的 GC 引用,它允許此信使確保始終允許收集已注冊的接收者,即使已注冊的處理程序?qū)⑺鼈円没貋恚淮嬖趯λ鼈兊钠渌赐瓿傻膹娨谩?/p>
-
StrongReferenceMessenger:這是一個信使實現(xiàn),它對已注冊的接收者進行根權(quán)限化,以確保它們保持活躍狀態(tài),即使信使是唯一引用它們的對象。
下面是一個如何使用這個接口的小例子:
// 聲明消息
public sealed record LoggedInUserChangedMessage(User user);
// 明確注冊收件人...
messenger.Register<MyViewModel, LoggedInUserChangedMessage>(this, static (r, m) =>
{
// 在這里處理消息,r 是接收者,m 是接收者
// 輸入消息。使用作為輸入傳遞的接收者使得
// ambda 表達(dá)式不捕獲“this”,從而提高性能。
});
// ... 或者讓視圖模型實現(xiàn) IRecipient<TMessage>......
class MyViewModel : IRecipient<LoggedInUserChangedMessage>
{
public void Receive(LoggedInUserChangedMessage message)
{
// 在這里處理消息
}
}
// ... 然后通過接口注冊(其他API也可用)messenger.Register<LoggedInuserChangedMessage>(this);
// 從其他模塊發(fā)送消息
messenger.Send(new LoggedInUserChangedMessage(user));
由于新提供的公共 DependentHandle API,這個新版本的 MVVM 工具包中的信使實現(xiàn)在 .NET 6 中得到了高度優(yōu)化,它允許信使類型變得比以前更快,并提供完全零分配的消息廣播。以下是一些基準(zhǔn),展示了 MVVM 工具包中的信使與其他廣泛使用的 MVVM 庫中的其他幾種等效類型的比較:
方法 | 中位數(shù) | 錯誤 | 標(biāo)準(zhǔn)差 | 比率 | 比率標(biāo)準(zhǔn)差 | 第0代 | 第一代 | 已分配 |
MVVMToolkitStrong | 4.025 ms | 0.0177 ms | 0.0147 ms | 1 | 0 | – | – | – |
MVVMToolkitWeak | 7.549 ms | 0.0815 ms | 0.0762 ms | 1.87 | 0.02 | – | – | – |
MvvmCrossStrong | 11.483 ms | 0.0226 ms | 0.0177 ms | 2.85 | 0.01 | 9687.5 | – | 41,824,022 B |
MvvmCrossWeak | 13.941 ms | 0.1865 ms | 0.1744 ms | 3.47 | 0.04 | 9687.5 | – | 41,824,007 B |
MVVMLight | 52.929 ms | 0.1295 ms | 0.1011 ms | 13.14 | 0.06 | 7600 | – | 33,120,010 B |
Stylet | 91.540 ms | 0.6362 ms | 0.4967 ms | 22.73 | 0.17 | 35500 | – | 153,152,352 B |
MvvmGen | 141.743 ms | 2.7249 ms | 2.7983 ms | 35.31 | 0.7 | 19250 | – | 83,328,348 B |
Catel | 148.867 ms | 2.6825 ms | 2.5093 ms | 36.94 | 0.64 | 5250 | – | 22,736,316 B |
Prism | 150.077 ms | 0.5359 ms | 0.4184 ms | 37.26 | 0.13 | 17500 | 250 | 76,096,900 B |
CaliburnMicro | 280.740 ms | 3.7625 ms | 3.1418 ms | 69.74 | 0.82 | 88000 | 2000 | 381,859,608 B |
MauiMessagingCenter | 673.656 ms | 1.7619 ms | 1.3755 ms | 167.26 | 0.63 | 8000 | – | 35,588,776 B |
每個基準(zhǔn)測試運行涉及向 100 個收件人發(fā)送 4 條不同的消息 1000 次。如您所見,WeakReferenceMessenger?和?StrongReferenceMessenger?都是迄今為止最快的,也是唯一一個在廣播消息時甚至不分配一個字節(jié)的 。
改進的集合 API
這個新版本的 MVVM 工具包 還將所有可觀察的分組集合類型從?CommunityToolkit.Common?包移動到?CommunityToolkit.Mvvm,同時還進行了一些重大更改以改進 API 表面并使其在更多場景中有用。這些 API 在處理分組項目時特別有用(例如,顯示聯(lián)系人列表),它們現(xiàn)在還包括擴展以極大地促進常見操作,例如在組內(nèi)的正確位置插入項目(使用默認(rèn)比較器 或輸入一個,并在需要時創(chuàng)建一個新組)。
這是一個 GIF,展示了來自 MVVM Toolkit 示例應(yīng)用程序的簡單聯(lián)系人視圖:
宣布 MVVM 工具包示例應(yīng)用程序
為了配合新版本,我們還在 Microsoft Store 中發(fā)布了示例應(yīng)用程序!它包括 MS Docs 上可以找到的所有文檔,以及許多可用 API 的交互式示例。它旨在成為 MVVM 工具包的伴侶,我們希望它能幫助人們開始使用這個庫,從而更加熟悉它!
從 Microsoft Store 下載并試用!
改進的診斷 API
CommunityToolkit.Diagnostics?包也獲得了一些新的改進,利用了新的C# 10 內(nèi)插字符串處理程序和調(diào)用者參數(shù)表達(dá)式功能。一些以前接受字符串的?Guard?API?現(xiàn)在也接受自定義處理程序,允許調(diào)用站點在沒有拋出異常的情況下完全跳過插值步驟,而且也不再需要手動指示參數(shù)名稱。
這是一個快速的前后比較:
// 診斷 7.1
public static void SampleMethod(int[] array, int index, Span<int> span, string text)
{
Guard.IsNotNull(array, nameof(array));
Guard.HasSizeGreaterThanOrEqualTo(array, 10, nameof(array));
Guard.IsInRangeFor(index, array, nameof(index));
Guard.HasSizeLessThanOrEqualTo(array, span, nameof(span));
Guard.IsNotNullOrEmpty(text, nameof(text));
}
// 診斷 8.0
public static void SampleMethod(int[] array, int index, Span<int> span, string text)
{
Guard.IsNotNull(array);
Guard.HasSizeGreaterThanOrEqualTo(array, 10);
Guard.IsInRangeFor(index, array);
Guard.HasSizeLessThanOrEqualTo(array, span);
Guard.IsNotNullOrEmpty(text);
}
???????
.NET 6 支持
這個新版本的 .NET 社區(qū)工具包還增加了對 .NET 6 的支持,將其作為所有可用庫的新目標(biāo)。在最新的 .NET 運行時上運行時,帶來了一些改進:
-
現(xiàn)在為所有庫啟用了修剪支持。為了支持這一點,所有包還為所有 API 提供了完整的修整注釋,以確保所有內(nèi)容要么對鏈接器友好,要么在編譯時顯式顯示正確的警告(例如,MVVM 工具包中的某些驗證 API 就是這種情況 ,它們使用 BCL 中的一些 API,這些 API 本質(zhì)上需要一些反射才能工作)。
-
High Performance 包中的?Count<T>()?擴展現(xiàn)在也支持?mint?和?nunit。
-
為 .NET 6 上的所有包引入了其他幾項優(yōu)化。
當(dāng)然,所有庫都將繼續(xù)支持 .NET Standard 2.0,因此您也可以繼續(xù)從具有不同目標(biāo)框架的項目中引用它們。由于 NuGet 包解析的工作原理,如果您使用這些包和較低目標(biāo)框架(例如 .NET Standard 2.0)編寫庫,并且消費者從針對較新 .NET 版本的項目中引用它(例如.NET 6),他們?nèi)匀粫詣荧@得可用的 .NET 社區(qū)工具包 程序集的最優(yōu)化版本!
?
在這個新版本中還有更多內(nèi)容!
您可以在 GitHub 發(fā)布頁面上查看完整的變更日志。
您可以在我們的 GitHub 存儲庫中找到所有源代碼,在MS Docs 網(wǎng)站上找到一些手寫文檔,在 .NET API 瀏覽器網(wǎng)站上找到完整的 API 參考。如果您想做出貢獻,請隨時提出問題或聯(lián)系我們,讓我們了解您的體驗!要關(guān)注 Twitter 上的對話,請使用 #CommunityToolkit 標(biāo)簽。您的所有反饋都極大地幫助了這些庫的發(fā)展方向,因此請務(wù)必分享它們!
???????
關(guān)注微軟開發(fā)者MSDN了解更多文章來源:http://www.zghlxwxcb.cn/news/detail-441406.html
點擊了解.NET社區(qū)工具包~???????文章來源地址http://www.zghlxwxcb.cn/news/detail-441406.html
到了這里,關(guān)于最新 .NET 社區(qū)工具包, 推出MVVM 源代碼生成器!的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!