更新 WinForms | InitializeComponent 的現代程式碼生成

微軟技術棧發表於2023-04-25

當你使用 Visual Studio 中的 WinForms Designer 來建立一個 WinForms 表單或使用者控制元件時,它並沒有像 XML 或 HTML 那樣的特殊定義或檔案格式來表示使用者介面。從一開始,WinForms 使用的唯一格式就是程式程式碼。在 WinForms Visual Basic 專案中定義的表單或使用者控制元件會被儲存到 VB 程式碼中。在 C# 專案中,這就是 C# 程式碼。這些程式碼將被放置在一個專用的 Designer 檔案中,該檔案位於實際表單程式碼檔案後面,也包含控制 UI 的程式碼。

圖片

當你的表單或使用者控制元件需要在 WinForms Designer 中再次開啟時,該程式碼將被解釋並根據結果物件圖在 Designer 中重新建立表單/使用者控制元件。這就是我們把儲存表單的過程稱為 CodeDOM 序列化的原因。這裡的 CodeDOM 指的是一種物件模型(Code Document object model,程式碼文件物件模型),它允許開發人員透過特定型別的物件來定義程式的各個方面或程式的一部分。

圖片

雖然 CodeDOM 很靈活,可以比較容易地進行擴充套件,並且支援比 Visual Basic 或 C# 更多的語言,但是從現有的程式碼檔案生成 CodeDOM 圖是一件完全不同的事情。雖然 CodeDOM 可以透過現有的編譯器實現為特定的語言編寫程式碼檔案,但生成的程式碼風格仍然是 .NET 框架剛開始時的風格,在許多情況下已經不再符合當前的編碼標準。

在 WinForms 中,當你設計一個表單的時候,所有相關的內容都是在每個表單或使用者控制元件的一個方法中生成的。這個方法(還有一些基礎結構和初始化程式碼)叫做 InitializeComponent。

這個方法會被表單的建構函式無條件地呼叫。在 C# 中,這是非常明顯的,你新增到專案中的新表單總是具有建構函式和所需的呼叫:

public partial class Form1 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }
    }

在 Visual Basic 中,如果你不顯式地新增建構函式 Sub New,Visual Basic 編譯器會在後臺自動插入對 InitializeComponent 的呼叫。但如果你在程式碼檔案中新增了一個建構函式,編輯器也會在 VB 程式碼中插入對 InitializeComponent 的呼叫:

Public Class Form1

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

    End SubEnd Class

注意,在 Visual Basic 中,Inherits 語句允許新表單類繼承 System.Windows.Forms.Form 基類,與 C# 不同的是,它只是 Designer 程式碼隱藏檔案的一部分。在 VB 中,分部類只需在其中一個分部類的程式碼檔案中宣告 partial 關鍵字即可。這就是為什麼 Visual Basic WinForms 表單程式碼檔案預設不包含任何東西,只包含表單的類定義的原因。

直到最近,WinForms Designer 使用 CodeModel 介面來解釋不同編碼語言的原始碼,以構建所需的內部 CodeDOM 圖,以便 Designer 儲存表單或使用者控制元件的定義。但是我們改變了這一點。

Enter Roslyn

WinForms 在 Visual Studio 2022 17.5版本中引入了一種現代化的方式,來讀取和生成 WinForms 程式外 Designer 的 InitializeComponent 程式碼。它透過使用 .NET 編譯器平臺的 API(通常稱為 Roslyn SDK)來完成所有相關任務。Roslyn 編譯器是一套針對 .NET 語言的開源編譯器和程式碼分析 API。它允許開發人員使用現代語言特性在 C# 和 Visual basic .NET 中編寫、分析和操作程式碼。它還提供了一套豐富的診斷和程式碼重構功能,以提高程式碼質量和開發人員的工作效率。它是 C# 和 VB 程式碼生成的黃金標準和當前的最佳實踐。而且,由於它與 Visual Studio 中用於編譯和構建任何 C# 或 Visual Basic 專案的工具相同,因此其程式碼生成結果完全符合當前的編碼標準。

此外,由於 Roslyn 編譯器提供了某些  API,不僅知道特定語句、命令或方法的正確語法,而且還知道在 WinForms 設計時程式碼塊的語義,WinForms Designer 可以比以前更早、更精確地指出 InitializeComponent 內部程式碼潛在的問題。因此,它不僅知道你什麼時候拼錯了“Buttne”——它還知道在 InitializeComponent 內部定義的拼寫錯誤的變數將是一個未知的符號,並且能夠指出這一點。

但是還有一系列額外的好處:

  • 以前,基於 CodeModel 構建 CodeDOM 只能在 UI Thread 上執行。這不僅是一種阻塞操作,而且無法充分發揮現代多核處理器的潛力。使用 Roslyn 編譯器,我們將可以透過使用並行化來最佳化構建的過程。
  • 舊的系統沒有一種簡單的方法來解釋最近引入的語言特性。而使用 Roslyn,我們可以選擇引入像 NameOf 這樣的語言特性來生成更優的程式碼,特別是用於資料繫結的時候。此外,它為未來 InitializeComponent 內部更復雜的程式碼生成開闢了新的道路,這將有助於最佳化和均衡在具有不同 HighDPI 設定的機器上的 HighDPI 場景的程式碼生成。
  • Roslyn 編譯器支援許多方面的 .editorconfig 配置,因此在 InitializeComponent 中生成的程式碼接近於您和您的團隊透過自定義 .editorconfig 定義強制執行的編碼標準。

總而言之,有一些編碼元素與之前的有本質上的不同。C# 中 this 或 Visual Basic 中 Me 的省略就是一個例子。下面的截圖顯示了 Roslyn 在 InitializeComponent 中 Button 的程式碼生成的區別:

圖片

如果你對將 WinForms Designer 中的程式碼生成轉移到 Roslyn 或如何使用 .editorconfig 配置 InitializeComponent 程式碼生成的更多技術背景感興趣,請檢視 WinForms 庫中的這篇技術文章,它更詳細地解釋了所有這些內容。

關於這個主題的反饋對我們來說真的很重要,請在文章下方留言告訴我們你在 WinForms 程式碼生成方面的想法或意見。如果你對 WinForms Designer 有任何建議,或者發現了一個 bug,請隨時在 WinForms Github 庫中提交新的 issue。編碼愉快!

相關文章