.NET 编译器平台:使用 Roslyn 体验 MVVM

模型-视图-视图模型 (MVVM) 是一个非常受欢迎的结构模式,与 XAML 应用程序平台(如 Windows Presentation Foundation (WPF) 和通用 Windows 平台 (UWP))配合使用效果绝佳。首先,使用 MVVM 构建应用程序能够在数据、应用程序逻辑和 UI 之间实现清晰分离。这使应用程序更易于维护和测试,提高了代码的重复使用,使设计人员能够对 UI 进行操作,而无需与逻辑或数据进行交互。

多年来,已构建了许多库、项目模板和框架(如 Prism 和 MVVM Light Toolkit)用于帮助开发人员更轻松有效地实现 MVVM。然而,在某些情况下,你不能依赖于外部库,或者你可能只是想要在专注于你的代码的同时能够快速实现此模式。虽然 MVVM 有多种实现方式,但大多数都共享一些可通过 Roslyn API 自动生成的公用对象。

在本文中,我将解释如何创建自定义 Roslyn 重构,从而轻松地生成可通用于每个 MVVM 实现的元素。因为此处不可能为你提供有关 MVVM 的完整摘要,所以我假设你已经对 MVVM 模式、相关术语和 Roslyn 代码分析 API 有了基本的了解。如果你需要复习,可以阅读以下文章: “模式 – 使用‘模型-视图-视图模型’设计模式构建的 WPF 应用”(msdn.com/magazine/dd419663)、“C# 和 Visual Basic: 使用 Roslyn 编写 API 的实时代码分析器”(msdn.com/magazine/dn879356) 和“C# – 将代码修补程序添加到 Roslyn 分析器”(msdn.com/magazine/dn904670)。

随附的代码可用于 C# 和 Visual Basic 版本。文章中的该版本包括 C# 和 Visual Basic 列表。

通用 MVVM 类

任何典型的 MVVM 实现都需至少具备以下类(在一些情况下名称会稍有不同,具体取决于你所应用的 MVVM 风格):

ViewModelBase – 一个基本的抽象类,反映通用于应用程序中每个 ViewModel 的成员。通用成员可以根据应用程序的体系结构发生相应的改变,但其最基本的实现是为任何派生 ViewModel 提供更改通知。

RelayCommand – 一个表示命令的类,通过它,ViewModels 可以调用方法。RelayCommand 通常有两种风格,分别为:通用和非通用。本文将使用通用风格 (RelayCommand<T>)。

我假设你已经熟悉了这两种风格,所以本文不再赘述。图 1a 表示 ViewModelBase 的相关 C# 代码,图 1b 显示 Visual Basic 代码。

图 1a ViewModelBase 类 (C#)

图 1b ViewModelBase 类 (Visual Basic)

这是 ViewModelBase 的最基本的实现;它只提供基于 INotifyPropertyChanged 接口的属性更改通知。当然,你可能会根据自己的具体需求添加更多的成员。图 2a 显示 RelayCommand<T> 的相关 C# 代码,图 2b 显示 Visual Basic 代码。

图 2a RelayCommand<T> 类 (C#)

图 2b RelayCommand(Of T) 类 (Visual Basic)

这是 RelayCommand<T> 最常见的实现,且适用于大多数 MVVM 方案。值得一提的是,这个类实现了 System.Windows.Input.ICommand 接口,该接口需要实现一个名为 CanExecute 的方法,目标是告诉调用者某个命令是否可执行。

Roslyn 如何使你的生活简单化

如果你不使用外部框架,Roslyn 可以说是一个真正的生活助手: 你可以创建自定义代码重构,用于替换类定义并自动实现所需的对象,还可以根据模型属性轻松地自动实现 ViewModel 类的生成。图 3 举例说明了在文章的最后你将有何收获。
通过自定义 Roslyn 重构实现 MVVM 对象
图 3 通过自定义 Roslyn 重构实现 MVVM 对象

这种方法的好处是,你可以始终将注意力放在代码编辑器上,并且非常快速地实现所需的对象。此外,如文章后面提供的演示,你可以根据模型类生成自定义 ViewModel。让我们从创建重构项目开始。

创建适用于 Roslyn 重构的项目

第一步是创建一个新的 Roslyn 重构。为此,你可以使用代码重构 (VSIX) 项目模板,它位于你在“新建项目”对话框中所选语言下的扩展节点中。调用新项目 MVVM_Refactoring,如图 4 中所示。
创建 Roslyn 重构项目
图 4 创建 Roslyn 重构项目

准备好之后,单击“确定”。当 Visual Studio 2015 生成该项目时,会自动添加一个在 CodeRefactoringProvider.cs(或 Visual Basic 的 .vb)文件中定义的名为 MVVMRefactoringCodeRefactoringProvider 的类。

分别将该类和文件重命名为 MakeViewModelBaseRefactoring 和 MakeViewModelBaseRefactoring.cs。为了清楚起见,同时删除自动生成的 ComputeRefactoringsAsync 和 ReverseTypeNameAsync 方法(后者是为了演示而自动生成的)。

研究语法节点

正如你可能知道的,代码重构的主入口点是 ComputeRefactoringsAsync 方法,如果语法节点的代码分析满足所需的规则,则该方法负责创建一个插入到代码编辑器灯泡中的所谓的快速操作。在这种特殊情况下,ComputeRefactoringsAsync 方法必须检测开发人员是否正在通过类声明调用灯泡。

在语法可视化工具窗口的帮助下,你可以很容易地了解你需要使用的语法元素。更具体地说,在 C# 中,你必须检测语法节点是否是 Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax 类型对象所表示的 ClassDeclaration(见图 5),而在 Visual Basic 中,你要确定语法节点是否是Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassStatementSyntax 类型对象所表示的 ClassStatement。

实际上,在 Visual Basic 中,ClassStatement 是 ClassBlock 的子节点,它表示某一类的整个代码。C# 和 Visual Basic 会有不同的对象是因为它们表示类定义的方式有所不同: C# 使用“class”关键字并将大括号作为分隔符,而 Visual Basic 使用“Class”关键字并将 End Class 语句作为分隔符。

理解类声明
图 5 理解类声明

创建操作

我将讨论的第一个代码重构涉及 ViewModelBase 类。第一步是在 MakeViewModelBaseRefactoring 类中编写 ComputeRefactoringsAsync 方法。使用此方法,你可以检查语法节点是否表示类声明;如果是的话,你可以创建并注册可在灯泡中使用的操作。图 6a 演示如何在 C# 中完成此操作,图 6b 显示 Visual Basic 代码(请参阅内联注释)。
图 6a 主入口点: ComputeRefactoringsAsync 方法 (C#)

图 6b 主入口点: ComputeRefactoringsAsync 方法 (Visual Basic)

如果这是一个类声明,通过此代码,你已经注册了可以在语法节点上调用的操作。该操作由 MakeViewModelBaseAsync 方法执行,可实现重构逻辑,并提供一种全新的类。

代码生成

Roslyn 不仅提供了一个面向对象的结构化的方式来表示源代码,还允许分析源文本和生成具有全保真度的语法树。为了从纯文本生成新语法树,你需要调用 SyntaxFactory.ParseSyntaxTree 方法。它使用一个包含源代码(你要在其中生成 SyntaxTree)的 System.String 类型参数。

Roslyn 还提供 VisualBasicSyntaxTree.ParseText 和 CSharpSyntaxTree.ParseText 方法来实现相同的结果;然而,在这种情况下,使用 SyntaxFactory.ParseSyntaxTree 是有意义的,因为代码从 SyntaxFactory 调用其他分析方法,这一点你很快就会看到。

在你拥有新的 SyntaxTree 实例后,可以对它执行代码分析以及其他与代码相关的操作。例如,你可以分析整个类的源代码,从中生成语法树,替换类中的语法节点,并返回一个新的类。在使用 MVVM 模式的情况下,由于公共类具有固定的结构,所以分析源文本并用新的类定义去替换某个类定义的过程会非常快捷和容易。

通过利用所谓的多行字符串文本,你可以将整个类定义粘贴到 System.String 类型对象中,然后从中获取 SyntaxTree,检索对应于类定义的 SyntaxNode 并使用新类替换树中原来的类。我将首先演示如何对 ViewModelBase 类完成此操作。更具体地说,图 7a 显示 C# 的代码,图 7b 显示 Visual Basic 的代码。
图 7a MakeViewModelBaseAsync: 从源文本 (C#) 生成新的语法树

图 7b MakeViewModelBaseAsync: 从源文本 (Visual Basic) 生成新的语法树

由于 SyntaxFactory 类型可多次使用,所以你可以考虑执行静态导入,这样,通过在 Visual Basic 中添加 Imports Microsoft.CodeAnalisys.VisualBasic.SyntaxFactory 指令并在 C# 中使用静态 Microsoft.CodeAnalysis.CSharp.SyntaxFactory 指令即可简化代码。此处没有任何静态导入能够更容易地发现 SyntaxFactory 提供的方法。

请注意,MakeViewModelBaseAsync 方法有三个参数:

  • Document,它表示当前的源代码文件
  • ClassDeclarationSyntax(在 Visual Basic 中,则为 ClassStatementSyntax),它表示执行代码分析所采用的类声明
  • CancellationToken,它在必须取消操作的情况下使用

代码首先根据表示 ViewModelBase 类的源文本,调用 SyntaxFactory.ParseSyntaxTree 来获取一个新的 SyntaxTree 实例。需要调用 GetRoot 来获取语法树的根 SyntaxNode 实例。在这种特殊情况下,你事先知道已分析的源文本只有一个类定义,所以代码会通过 OfType 调用 FirstOrDefault 来检索所需类型的后代节点,即在 C# 中为 ClassDeclarationSyntax,在 Visual Basic 中则为 ClassBlockSyntax。

此时,你需要用 ViewModelBase 类来替换原来的类定义。为此,代码将首先调用 Document.GetSyntaxRootAsync 来异步检索文档语法树的根节点,然后调用 ReplaceNode 将旧的类定义替换为新的 ViewModelBase 类。

注意代码如何通过分别研究 CompilationUnitSyntax.Usings 和 CompilationUnitSyntax.Imports 集合检测System.ComponentModel 命名空间是否存在 using (C#) 或 Imports (Visual Basic) 指令。如果不存在,则要添加适当的指令。如果尚不可用,那么在代码文件级添加指令的做法很有用。

请记住,在 Roslyn 中,对象是不可改变的。同样的概念也适用于 String 类: 事实上,你永远无法修改字符串,因此当你编辑字符串或调用诸如 Replace、Trim 或 Substring 之类的方法时,会得到一个包含特定更改的新的字符串。出于这个原因,每次你需要编辑语法节点时,实际上将创建带有更新属性的新的语法节点。

在 Visual Basic 中,代码也需要检索当前语法节点的可替代 ClassStatementSyntax 类型的父 ClassBlockSyntax。这是检索将被替换的 SyntaxNode 实例的必要步骤。提供 RelayCommand 类的普通实现原理是一样的,但你需要添加一个新的代码重构。为此,在解决方案资源管理器中右键单击该项目名称,然后选择“添加 | 新项目”。在“添加新项目”对话框中,选择重构模板,并将新文件命名为 MakeRelayCommandRefactoring.cs(对于 Visual Basic 则为 .vb)。重构逻辑与 ViewModelBase 类是相同的(当然,源文本有所不同)。

图 8a 显示新重构的全部 C# 代码,包括 ComputeRefactoringsAsync 和 MakeRelayCommandAsync 方法,图 8b 显示 Visual Basic 代码。
图 8a 实现 RelayCommand 类的代码重构 (C#)

图 8b 实现 RelayCommand(Of T) 类的代码重构 (Visual Basic)

你已经成功完成了两个自定义重构操作,现在你已经掌握了实现其他重构的基础知识,具体取决于你的 MVVM 模式的实现方式(如消息代理、服务定位器和服务类)。

作为替代方案,你还可以使用 SyntaxGenerator 类。这可以提供与语言无关的 API,意味着你编写的代码会针对 Visual Basic 和 C# 实现重构。然而,这种方法需要生成每一个对应源文本的语法要素。通过使用 SyntaxFactory.ParseSyntaxTree,你可以分析任何源文本。如果你编写了需要处理你事先不知道的源文本的开发者工具,那么这种做法就特别有用。

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部