本文翻译于Klaus Loeffelmann的这篇英文文章:Updated Modern Code Generation for WinForm’s InitializeComponent – .NET Blog (microsoft.com)
当你使用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 Sub
End 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 库中提交新的问题。
祝你设计和编码愉快!
然而 winform的设计器提升并不明显。甚至感觉现在打开设计器的速度还变慢了。