使用 Source Generators 快速編寫 MVVM 程式碼

微軟技術棧發表於2022-02-22

image.png
大家好,我是本期的微軟 MVP 實驗室研究員——陳錦華。
微軟 MVP ( Windows Development 方向),專注於 .NET 開發,有十多年的客戶端開發經驗。現在熱衷於撰寫部落格,分享 WPF、UWP 和 Azure DevOps 相關的經驗。

0.1. 關於 MVVM Toolkit

.NET Community Toolkit 是以用於所有 .NET 開發人員的幫助類和 API 的合集,並且與任何特定 UI 平臺無關。 最它釋出了 8.0.0 preview1 版本,它包含了從 Windows Community Toolkit 遷移過來的以下元件:
• CommunityToolkit.Common
• CommunityToolkit.Mvvm
• CommunityToolkit.Diagnostics
• CommunityToolkit.HighPerformance
其中 CommunityToolkit.Mvvm 又名 MVVM Toolkit ,它是一個現代化、快速以及模組化的 MVVM 庫。它包含一個 Source Generators 元件:MVVM Toolkit source generators。這篇文章將介紹它如何幫助開發著快速編寫 MVVM 程式碼。

0.2. MVVM Toolkit source generators

Source Generators 是一項 C# 編譯器功能,使 C# 開發人員能夠在編譯使用者程式碼時進行檢查,並動態生成新的 C# 原始檔,以新增到使用者的編譯中。 通過這種方式,你的程式碼可以在編譯過程中執行並檢查你的程式以生成與其餘程式碼一起編譯的其他原始檔。
對 MVVM 平臺的開發者來說,Source Generators 是一個期待已久的新功能,畢竟 MVVM 模式中,開發者需要寫很多命令和屬性的額外的模板程式碼。到目前為止,為了減輕這些額外程式碼的負擔,微軟和其它開發者想出過很多不同的方案,而 Source Generators 看起來會是更好的一個。MVVM Toolkit 已開始了這方面的工作,8.0 版本又得到進一步的加強,現在 MVVM Toolkit source generators 是一個增量生成器,效能將會提升很多。
下面將簡單講解如何使用 MVVM Toolkit source generators 命令和屬性的程式碼。

0.3. 如何生成命令的程式碼

在 MVVM 模式中,命令的寫法讓人有點煩惱。這是 MVVM Toolkit 中的通常寫法:

private IRelayCommand _displayCommand;

IRelayCommand DisplayCommand => _displayCommand ??= new RelayCommand(new Action(Display), () => HasName);

private void Display()
{

}

首先,程式碼就不少。另外,_displayCommand 和 DisplayCommand、Display() 是寫在一起好呢,還是按欄位、屬性、函式的排序分別放在程式碼裡的不同位置呢?又或者索性用 Partial 類分別放在不同的檔案?
用 source generators 就沒這些煩惱了,命令的定義可以簡化成這樣:

[ICommand(CanExecute = nameof(HasName))]
private void Display()
{
}

通過新增 ICommandAttribute,source generators 可以根據 Display() 這個函式名正確地生成 DisplayCommand 及對應的初始化程式碼。此外,還可以通過它的 CanExecute 屬性指定將 ICommand 的 CanExecute 關聯到對應的屬性。

0.4. 如何生成屬性的程式碼

屬性也有和命令一樣的煩惱,通常來說 MVVM 模式中的屬性的寫法如下:

private string name;

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

其實還好,不會太多。但如果是這樣呢:

 private string _surname;

 public string Surname
 {
     get
     {
         return _surname;
     }
     set
     {
         if (!EqualityComparer<string>.Default.Equals(_surnam
         {
             _surname = value;
             OnPropertyChanged();
             OnPropertyChanged(nameof(FullName));
             OnPropertyChanged(nameof(HasName));
             DisplayCommand.NotifyCanExecuteChanged();
         }
     }
 }

 public string FullName => $"{Name} {Surname}";

 public bool HasName => !string.IsNullOrWhiteSpace(FullName);

這時候 source generators 的作用就可以很明顯,因為它只需要下面的程式碼就可以自動產生與上面等價的程式碼:

[ObservableProperty]
[AlsoNotifyChangeFor(nameof(FullName), nameof(HasName))]
[AlsoNotifyCanExecuteFor(nameof(DisplayCommand))]
private string _surname;

public string FullName => $"{Name} {Surname}";

public bool HasName => !string.IsNullOrWhiteSpace(FullName);

從這段程式碼可以看到有三個 Attribute 起了作用:
ObservableProperty:自動為 _name 屬性生成對應的屬性。 AlsoNotifyChangeFor:屬性值修改時同時觸發 FullName 和 HasName 這兩個屬性的 PropertyChanged 事件。 AlsoNotifyCanExecuteFor:屬性值修改時同時通知 DisplayCommand 執行它的 NotifyCanExecuteChanged()。

0.5. 如何注入到現有類

一般來說,MVVM Toolkit source generators 需要在 ObservableObject 的派生類中使用,例如:

public partial class TestModel: ObservableObject

但如果你的類已經繼承了其它類,MVVM Toolk source generators 也允許你使用它的功能,方法是新增上 INotifyPropertyChangedAttribute,程式碼如下:

[INotifyPropertyChanged]
public partial class TestModel: Behaviour

INotifyPropertyChangedAttribute 會自動生成實現 INotifyPropertyChanged 的程式碼,而無需更改基類。不過遺憾的是,INotifyPropertyChangedAttribute 目前只能在未實現 INotifyPropertyChanged 介面的類中使用,即下面這種程式碼不能編譯通過:

[INotifyPropertyChanged]
public partial class TestModel: ObservableObject

0.6. 使用 source generators 後的成果

使用了 source generators 可以大幅減少程式碼,下面這圖直觀展示了減少的程式碼量。
a07a2e9effb6121ed26c72f73990a6bb.png
如果需要檢視自動生成的程式碼,可以在分析器的 CommunityToolkit.Mvvm.SourceGenerators 節點裡找到:
b2cea11e5018bd38cabfbb5436b68ea7.png

0.7. 一些小問題

MVVM Toolkit source generators 可以重構你的程式碼,但代價是什麼?
首先,雖然 MVVM Toolkit source generators 支援 .NET Standard 2.0,但部分功能需要 C# 8.0 以上,所以編譯時可能會看到這條錯誤:

The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 8.0 

解決方法是在專案檔案的 PropertyGroup 節點裡新增這段指明 C# 的版本:
<LangVersion>9.0</LangVersion>
另外,MVVM Toolkit source generators 還需要 Visual Studio 2022 才可以使用。
還有一點,我還沒找到為生成的屬性新增註釋的方法,這對一些難以理解的屬性來說十分致命,只好用回傳統方法來處理這種屬性。
最後,沒有 CodeLens,沒法直觀看到屬性的引用、修改等資訊,用起來不是很順手。

0.8. 總結

總的來說,MVVM Toolkit source generators 可以幫助客戶減少大量程式碼工作,而且無論從程式碼量、可維護性、可閱讀性來看,source generators 都有巨大的優勢。但在現階段,它用起來還是有不少小問題,不能完全代替原生寫法。不過這是個很符合 80/20 原則的工具:它可以讓使用者用 20% 的投入解決了 80% 的問題。所以開發者可以在新的專案中嘗試這個工具,以提高開發效率。
.NET Community Toolkit 及 MVVM Toolkit 都是開源專案,所以您也可以在它的儲存庫中提出您的反饋和程式碼。
其它更多的內容,請參考 Github 或其它文件:
https://github.com/CommunityT...
https://github.com/CommunityT...
https://docs.microsoft.com/zh...

相關文章