VS2019 基础知识(一)

原文:Essential Visual Studio 2019

协议:CC BY-NC-SA 4.0

一、安装和 IDE 差异

Visual Studio 有一个有趣的传统。它已经以不同的形式存在了超过 15 年。如果你追溯到 Visual Basic,微软已经提供了近 30 年的开发环境。这种经历使得 Visual Studio 成为开发人员最常用(也是最受欢迎)的工具之一变得可以理解,不管他们试图创建什么样的应用。

Visual Studio 的优势之一是高度的一致性,特别是在最近的五个版本中。如果你熟悉 Visual Studio 的早期版本,你可以轻松地浏览 Visual Studio 2019。这就是稳定和熟悉的力量。

但话虽如此,仅仅知道 Visual Studio 2019 与早期版本相似并不能说明全部情况。每个版本都增加了新功能,增强了现有功能。这并不奇怪,因为微软的生态系统还在继续发展——而且发展得相当快:AzureDevOps 码头工人;Xamarin 像 Angular、React、Vue 这样的 web 框架;和大数据。

并非所有这些你都感兴趣。但是他们中的一些人很有可能是。但是,这意味着,如果您所做的只是继续像以前那样使用 Visual Studio,那么您就错过了机会。这就是本书的要点——让你不要错过,确保你知道 Visual Studio 2019 是如何发展的,以及在哪里发展的。通过识别可能对你有用的新领域,你可以在日常工作中变得更有效率。这也是本书的最终目标。

首先,在了解一些高级特性之前,让我们先来谈谈基础知识。毫无疑问,界面没有发生重大变化。但仍有一些东西被添加或调整。而有时候小时代是最有用的。

正在安装 Visual Studio 2019

本章的起点是安装过程。这也是过去几个版本中比较不稳定的函数之一。似乎每个新版本都有一个新的安装界面。而 Visual Studio 2019 并没有打破这一趋势。

Visual Studio 2019 可以与早期版本的 Visual Studio 并行安装。事实上,从 Visual Studio 2012 到 Visual Studio 2017,你可以独立于每个版本运行 Visual Studio 2019。这使您能够使用 Visual Studio 2019 中可能不可用的组件(如 Crystal Reports)来支持较旧的应用。

这实际上也意味着,你没有从 Visual Studio 2017 升级到 Visual Studio 2019。您正在安装 Visual Studio 2019 的全新实例。本质上没有升级过程。只是安装。

这并不是说,在 Visual Studio 2017 中创建和打开的项目在 Visual Studio 2019 中不起作用。确实如此。更重要的是,在 Visual Studio 2019 中打开一个项目,并不意味着不能在更早的版本中打开。项目和解决方案的格式在 Visual Studio 2019 和 Visual Studio 2013 之间向后兼容。当然,例外情况是,如果您的 Visual Studio 2019 解决方案包含一个具有早期版本中不存在的功能的项目模板。例如,您将无法在 Visual Studio 2015 中打开 Docker 项目。但是在 Visual Studio 2013 中创建的 Windows 窗体或 ASP.NET 项目将在 Visual Studio 2019 中正常打开,反之亦然。

Visual Studio 2019 的安装是通过 Visual Studio 安装程序驱动的。您可以从 https://visualstudio.microsoft.com/downloads/ 下载最新的安装程序。在此页面上,您有三个 Visual Studio 版本可供选择:社区版、专业版和企业版。这些版本之间的差异在某些方面是微小的,而在其他方面是重要的。

每个版本都使您能够用 Visual Studio 支持的每种语言开发应用。您可以安装必要的工作负载来开发 web 应用、通用 Windows 平台(UWP)应用、跨平台应用和数据科学功能,在这些应用和功能中,您会发现一些生产力和测试工具存在显著差异。社区版和专业版都不包括 IntelliTest、代码克隆、实时依赖项验证、实时单元测试、快照调试或时间旅行调试。这些都是将在本书中讨论的特性,这样您就可以更好地选择 Enterprise 是否更适合您的开发习惯。

一旦你下载并执行了安装程序,你将看到如图 1-1 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-1

Visual Studio 安装程序中的“已安装”选项卡

这是从头安装 Visual Studio 2019 或在开始使用后添加和删除已安装的工作负载和组件的起点。对话框中有两个可见的选项卡。“已安装”选项卡显示计算机上当前安装的所有 Visual Studio 版本。“可用”选项卡显示可以安装的 Visual Studio 版本。可以看到,Installed 选项卡显示有一个版本的 Visual Studio 2019 准备更新。图 1-2 显示了可用的选项卡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-2

Visual Studio 安装程序中的可用选项卡

这里可以看到有两个版本的 Visual Studio 2019 可以安装。虽然计算机上安装的所有 Visual Studio 版本都出现在“已安装”选项卡上,但“可用”选项卡仅显示最新版本。请注意,有两组可用版本。Visual Studio Enterprise 2019 在发布部分。这意味着您已经安装了当前发布的版本。预览部分包含两个仍处于预览模式的版本。这意味着他们拥有正在测试过程中的特性和工具。不要认为这意味着预览版本的质量不高…它是。但是,在预览模式和发布版本之间,支持的功能或配置可能会发生变化。

要启动安装过程,请找到所需的版本,然后单击“安装”按钮。同样,要更新现有实例,请在“已安装”选项卡上找到它,然后单击“更新”按钮。这两个动作启动同一个对话框,如图 1-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-3

Visual Studio 安装对话框

Visual Studio 安装过程中的最新创新之一是工作负荷的概念。微软意识到,并不是每个开发人员都需要在 Visual Studio 中安装相同的组件。允许开发人员选择他们需要的东西,然后只安装那些部分,可以提高安装速度。但是有许多可用的组件,开发人员怎么知道哪些是必需的呢?当然,组件的命名没有提供任何有用的指示。因此引入了工作负载的概念。

工作负载是一组预定义的组件,这些组件被确定为一种常见开发类型的一部分。图 1-3 包含了可用的工作负载。例如,有一个 web 开发人员的工作量。它包含使用 ASP.NET 或 ASP.NET 核心来编码、调试和部署 web 应用所需的所有组件。如果这是您所做的那种开发,您将选择工作量并开始这个过程。其他可用的工作负载也是如此。确定您计划进行的开发类型,选择适当的工作负载,并开始安装。

可用的工作负载根据常见的使用模式分为不同的类别。类别列表以及每个类别中的工作负载可以在接下来的章节中找到。

网络和云

  • ASP.NET 和 web 开发–包括使用 ASP.NET 核心、ASP.NET 和 HTML/JavaScript 构建 web 应用的组件。为了顺应当今的一个主要趋势,它还包括了使用 Docker 组件所需的工具。

  • Azure——包括 Azure SDK、项目模板和其他工具,帮助您创建和操作不同的 Azure 资源。您可以创建基于 Azure 的 web 应用、虚拟机,并且像前面的 ASP.NET 和 web 开发工作负载一样,它还包括 Docker 组件工具。

  • Node.js–包括项目模板、分析工具和 REPL(读取-评估-打印循环)交互环境,以便您可以有效地使用 node . js

  • Python——支持使用 Flask 和 Django 等框架构建基于 Python 的 web 应用。该工作负载对于创建基于 Python 的数据科学应用也很有用,因为它内置了对 Conda 和 IPython 的支持。

Windows 操作系统

  • 使用 C++进行桌面开发–用于使用 C++构建传统的 Windows 应用。一些受支持的工具包括 CMake、Clang 和 MSBuild。

  • 。NET 桌面开发–也用于构建传统的 Windows 应用,但现在选择的技术包括 Windows 窗体和 WPF(Windows Presentation Foundation),而支持的语言是 Visual Basic、C#和 F#。

  • UWP 开发——通用 Windows 平台(UWP)工作负载允许您以各种平台为目标,包括 Xbox、HoloLens、Surface Hub 和典型的桌面。这也是一个工作负载,如果你在 Windows 10 IoT(物联网)中工作,你可能会感兴趣。

移动和游戏

  • 用 C++开发游戏——这个工作量包括用 C++创建游戏的组件。包括支持 DirectX 和 Unreal 等引擎的能力。

  • 使用 Unity 进行游戏开发——Unity 是一个跨平台的游戏开发环境,可用于创建 2D 和 3D 游戏。该工作负载支持 Unity 游戏开发框架,能够发布到移动平台、Mac、桌面、web 应用和游戏控制台。

  • 移动开发。NET——虽然名称包括。NET 中,这个工作负载很容易被称为 Xamarin。通过 Xamarin,您可以使用 C#和 XAML 以及底层技术为 iOS、Android 和 UWP 创建本地应用。

  • 使用 C++进行移动开发——该工作负载还允许您使用 C++作为首选语言,为 iOS 和 Android 开发应用。

其他工具集

  • 。NET Core 跨平台开发-。NET Core 是一个开发平台,微软已经将它放入开源中,让所有人都可以看到(甚至为之做出贡献)。此工作负载添加了创建所需的组件。NET 核心应用,包括 ASP.NET 核心。

  • 数据科学和分析——能够对数据仓库执行复杂的查询是一个引人注目的应用,在过去十年中已经走到了最前沿。该工作负载包括工具和对 Python、F#和 R 等语言的支持,允许您构建提取、清理和查询数据的应用。

  • 数据存储和处理——在过去的几年中,Azure 增加了一些功能(如 Azure Data Lake 和对 Hadoop 的支持), SQL Server 也增加了一些功能来支持大量数据。从存储和查询的角度来看,此工作负载包括用于处理大数据的组件。

  • 使用 C++进行 Linux 开发——这个工作负载包括用于为 Linux 环境创建应用的组件。虽然这对于长期 Windows 开发人员来说可能令人惊讶,但并没有您想象的那么不寻常。Windows 10 包括安装基于 Ubuntu 的 Bash shell 的选项,并包括一个用于 Linux 的 Windows 子系统,允许 Linux 应用在 Windows 中运行。

  • Office/SharePoint 开发–近年来,Office 和 SharePoint 的开发方法都发生了很大的变化。此工作负载添加了用于为最新版本的 Office 和 SharePoint 创建应用的组件。这包括 Word、Excel 和 Outlook 的加载项,以及可用的不同 SharePoint 解决方案。

  • Visual Studio 扩展开发——Visual Studio 拥有一组令人难以置信的扩展点。您可以相对容易地创建与 Visual Studio 深度集成的代码分析器或工具窗口。这个工作负载添加了组件和项目模板来帮助您入门。

每个定义背后都有一组随工作负载一起安装的组件。选择一个工作负载相当于说您想要安装相应的组件,但是不需要知道您需要哪些组件。选择工作负载并不是确定要安装的组件的唯一方法。您可以自己选择单个组件。

在对话框顶部附近,选择“单个组件”链接。出现如图 1-4 所示的组件列表。在这里,您可以从列表中选择任何单个组件。并且,正如您所预料的,所选择的组件将被安装到您的机器上。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-4

单个组件

自然,工作负载和单个组件之间存在关系。毕竟,工作负载的全部目的是根据您正在进行的开发类型为您提供预打包的组件集。如果从安装页面中选择一个工作负荷,则可以在对话框右侧的窗格中看到包含的组件列表。并且它们在左侧显示的所有组件列表中被选中。如果要在安装中添加或删除组件,请选中或取消选中相应的项目。

还有第三种方式影响 Visual Studio 2019 的安装。点击对话框顶部的语言包链接,显示可用语言包的列表(如图 1-5 所示)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-5

语言包

语言包的安装独立于组件的安装,这意味着您可以选择一组组件,然后转到语言包列表,并选择其中的一些组件。工作负载和语言包之间没有关联,单个组件和语言包之间也没有关联。无论何种组合,所有选定的项目都将被安装。

一旦选择了组件和语言包,安装就可以开始了。有一个默认的安装位置,如果需要,您也可以选择不同的位置。点击顶部的安装位置链接,显示如图 1-6 所示的屏幕,即可确定备用位置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-6

安装位置

在这个屏幕上有三个可用的位置,可能比您预期的多两个。在顶部,Visual Studio IDE 字段定义了安装的主要位置。这对于安装任何产品都是典型的。但是第二个领域是偏离“正常”的地方

“下载缓存”字段让您知道作为安装一部分下载的文件将放置在哪里。还有一个复选框允许在安装完成后保留这些文件(因为它们通常会在安装完成后被删除)。

共享组件、工具和 SDK 字段也是信息性的。这是放置组件文件的目录。该位置由您计算机上安装的不同版本的 Visual Studio 使用,这就是该位置不能更改的原因。但是,如果您安装了 Visual Studio 的早期版本,请确保该路径与这些版本使用的路径相匹配。否则,您将向系统添加两次共享组件文件,一次用于早期版本,一次用于 Visual Studio 2019。

你可能想知道为什么微软觉得有必要让你为下载缓存指定一个不同的位置。基本原理与微软关于安装位置的建议有关。

Visual Studio 有许多与之相关的文件——不仅仅是核心开发环境,还包括安装的不同组件。当您运行应用时,会有大量的磁盘 I/O 活动在进行。为了获得 Visual Studio 的最佳性能,微软建议,如果您有一个可用的固态硬盘(SSD ),那么您可以使用该驱动器作为安装位置。然而,下载缓存可能会占用大量空间,而 SSD 上的空间可能非常宝贵。下载缓存字段允许您将安装文件放在不同的位置。将下载缓存放在不同的驱动器上还有一个好处,就是当您选择边下载边安装时,可以提高安装速度,因为现在下载和安装不会争用同一个硬盘驱动器。

当您根据需要定制安装后,点击对话框右下角的安装按钮(如图 1-3 )。这将启动该过程。默认情况下,安装在 Visual Studio 上开始,同时下载安装所需的其他文件。这缩短了完成安装的总时间。但是,安装按钮左侧的下拉菜单允许您在开始安装之前选择下载所有文件。官方的选择是全部下载然后安装。

你为什么会选择这个选项?嗯,如果你边下载边安装,那么你需要在整个安装过程中保持与互联网的连接。如果你正在运行一个缓慢或有限的互联网连接,这可能是恼人的。但是,如果您在开始安装之前下载了所有文件,则可以在安装准备就绪后立即断开连接。

Note

如果出于某种原因,安装 Visual Studio 不是你的选择,微软已经将包含 Visual Studio 的虚拟机放入 Azure Marketplace。你可以进入你的 Microsoft Azure 帐户,创建包含 Visual Studio 2015 至 2019 企业版或社区版的虚拟机。所有工作负载都已安装,映像每月更新一次,因此它们始终是最新的。

更新 Visual Studio 2019

微软发布 Visual Studio 更新有一个固定的节奏。希望你有机会定期安装更新。Visual Studio 安装程序最终决定了更新的安装方式,但是当更新不仅适用于 Visual Studio,还适用于您已安装的各种组件时,Visual Studio 会通知您。

起点是出现在状态栏右侧的通知程序,如图 1-7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-7

状态栏通知程序

此通知程序有一个包含可用通知数量的标记。并非所有通知都与更新相关。例如,如果您的许可证密钥即将到期,或者连接到源代码管理提供程序时出现问题,您可能会收到通知。和其他组件可以生成通知,包括微软提供的和第三方开发的。当您单击通知程序时,通知消息会出现在它们自己的窗格中(图 1-8 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-8

通知窗格

你可以看到两个当前可见的通知,其中一个是针对 Visual Studio 2019 的更新。这是您有更新可用的提示。因为,好吧,通知信息差不多就是这么说的。此时,有两种方法可以安装更新。首先,消息左下角的 Show Details 链接用于打开有关更新的信息。图 1-9 显示了点击链接时出现的典型对话框。如果单击“更新”按钮,Visual Studio 安装程序将启动。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-9

通知详细信息

这很好地引出了你可以选择的第二条路。Visual Studio 安装程序是 Windows 中的一个独立应用。这意味着您可以像在您的计算机上执行任何其他应用一样执行它。您可以在已安装程序列表中找到它,或者从任务栏的搜索区域搜索它。无论是从通知窗格启动还是直接启动,结果都是一样的。

Note

启动器程序在运行时需要自我更新是很常见的。虽然情况并不总是如此,但新的 dot 版本(16.1、16.2 等)之间似乎有很高的相关性。)和新版本的安装程序。

安装程序用于管理计算机上的所有 Visual Studio 实例。在图 1-1 中可以看到已经安装了 Visual Studio 2019 Professional。如果您安装了 Visual Studio 2017,该实例也会出现。每个实例描述的右侧是一些按钮,它们的标签取决于实例的状态。不支持的早期版本的预览版将顶部按钮标记为卸载。

对于 Visual Studio 2019,顶部按钮显示“更新”或“修改”,具体取决于 Visual Studio 更新是否可用。单击“更新”将启动更新到 Visual Studio 当前版本的过程。单击修改允许您在现有实例中添加或删除工作负荷。

还有另外两个按钮可用。中间的按钮标记为 Launch,用于启动实例。下面是一个显示附加功能的“更多”按钮。此功能包括修复或卸载 Visual Studio 的选项。如果有可用的更新,您可以使用下载然后安装按钮运行更新。最后,还有导入或导出配置的能力。在这种情况下,正在导入或导出的配置是工作负载、组件和语言包的集合。如果选择导出选项,则当前安装的集合将被放入配置文件中。然后,其他人可以导入该文件,这样就可以在你们两个人之间同步该组功能。这使得在同一个项目上工作变得更加容易,而不会在以后发现一些关键的组件没有被安装。

启动您的代码

另一个高波动性的领域,至少在用户体验方面,是 Visual Studio 的启动流程。当您启动 Visual Studio 时,会出现如图 1-10 所示的屏幕。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-10

Visual Studio 2019 启动屏幕

启动屏幕有两个主要部分。左侧是最近打开的项目、解决方案或文件的列表。这使您可以快速访问最近使用的工件。单击项目/解决方案/文件会打开它们。右边是一组按钮,让您执行一些与创建新项目相关的常见活动。

随着您打开不同的项目,左侧的列表将会增长。它确实记住了大量的项目,也就是说,你不可能超过任何存在的最大值。为了帮助您浏览列表,有几个选项可供您选择。图 1-11 显示了一个典型的条目,以及一个上下文菜单。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-11

最近打开的项目项

您可以在上下文菜单中看到选项,既可以将项目固定到最近的项目列表,也可以将其从列表中删除。通过这种方式,您可以确保定期打开的工件保持在列表的顶部,而不管打开的任何中间项目,并且可以将不再需要的项目从列表中清除。虽然当上下文菜单处于活动状态时它不可见,但在项目的右侧有一个图钉图标,以便您可以轻松地固定和取消固定项目。

如果您不打算使用以前打开的项目,右侧界面上的按钮是最常见的选项。每个按钮的描述如下。

克隆或签出代码——单击时,您会看到几个如何继续的选项。对话框如图 1-12 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-12

克隆或签出代码对话框

第一个选项是输入您想要克隆的 Git 存储库的 URL。在这种情况下,您将在“存储库位置”字段中提供 URL,并在“本地路径”字段中提供存储库在您计算机上的位置。

第二种选择是浏览存储库,比如 Azure DevOps 或本地 Team Foundation System (TFS)服务器。可以在浏览存储库标签下看到已配置存储库的列表。当您单击所需的存储库时,接下来会发生什么取决于存储库的主机。例如,如果你配置了一个 Azure DevOps 帐户,你会看到一个如图 1-13 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-13

用于连接到 Azure DevOps 项目的对话框

对于 TFS 服务器,出现的对话框略有不同。但结果是一样的。找到您想要克隆的项目并连接到它。一旦连接完毕,Visual Studio 中的团队资源管理器窗格就会出现,如图 1-14 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-14

映射和获取项目

在此对话框中,您可以指定放置项目代码的本地目录。完成后,单击“地图和获取”按钮。这会将代码下载到目录中,并在文件和 TFS 的项目之间建立映射。

打开一个项目或解决方案–此选项允许您浏览本地机器上可用的驱动器,以找到本地项目或解决方案。流程很简单。将打开一个标准的打开文件对话框。您可以像平常一样导航,寻找您想要的项目或解决方案文件。找到它后,单击“打开”, Visual Studio 将打开它。这个对话框唯一真正不同的地方是支持不同类型的项目文件的数量。如果你点击下拉列表,结果看起来如图 1-15 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-15

支持的项目类型列表

打开本地文件夹–概念上类似于“上一步”按钮,除了不是搜索项目文件,而是搜索文件夹。哦,扩展名列表不在那里,因为你在找一个文件夹,而不是一个文件。

此选项用于没有描述项目内容的单独清单的项目类型(如。例如,C#中的 csproj 文件),而是假设特定文件夹的内容(至少是不会被使用。忽略通常可用的文件)是项目的一部分。

创建新项目–如果您没有现有的项目或文件夹,此选项将显示已安装在您系统上的项目模板列表,并允许您基于它创建新项目。可用模板的实际列表取决于已安装的工作负载。这是 Visual Studio 的一部分,在过去的几个版本中没有显著变化。新建项目对话框如图 1-16 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-16

新建项目对话框

与早期版本的 Visual Studio 相比,项目模板的组织方式发生了显著变化。现在,焦点集中在模板类型上,与早期版本相反,在早期版本中,在看到可用模板之前,您首先要选择项目类型和语言。

在对话框的右侧,您可以看到可用模板的列表。对于每个模板,都有标签指示模板支持的语言和平台。您可以使用列表顶部的搜索框来搜索模板。或者您可以按语言、平台或项目类型过滤模板列表。项目类型列表如图 1-16 所示。

找到模板后,点击下一步,显示如图 1-17 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-17

配置新项目对话框

在此对话框中,您可以提供新项目的位置和名称,以及解决方案的名称。准备就绪后,单击创建。此时,接下来发生的细节取决于模板。有些模板会引导您完成向导,让您能够选择不同的定制。其他的模板会根据模板的需求创建一个新的项目。无论如何,一旦该过程完成,Visual Studio 将打开新项目,您就可以开始编写代码了。

搜索 Visual Studio

现在我们已经看了 Visual Studio 2019 的基本安装,以及打开或创建项目的新方式,如果你一直在使用 Visual Studio 2017 或 2015,你会发现没有太多变化。菜单、工具栏和各种窗口几乎与您预期的一模一样。改进的地方之一是搜索功能。

有一个搜索来检查代码库中的匹配项。还有一个在当前执行的调试上下文中查找的搜索(参见第七章,“调试和分析”)。但是在这种情况下,我们讨论的是搜索构成 Visual Studio 本身的命令和设置的能力。

图 1-18 举例说明了出现在 Visual Studio 2019 顶部的菜单和图标。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-18

Visual Studio 2019 菜单栏和图标

通过带有 Search Visual Studio 内部标签的文本框可以访问该搜索。也可以使用 Ctrl+Q 键激活它。

在文本框中,您可以键入要查找的关键字。匹配列表出现在文本框下方的下拉列表中。搜索是渐进的,这意味着你一输入就能看到结果。这些结果会随着你继续输入而改变。图 1-19 显示了在文本框中输入space时的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-19

搜索结果

结果分为三类:菜单、组件和模板。默认情况下,所有结果都是可见的,但是通过单击列表顶部的标题,只会显示该类别的结果。

单击搜索结果时会发生什么取决于结果的类型。菜单结果触发要执行的命令(如果结果是菜单命令)或要显示的选项对话框,包含选项的屏幕可见。例如,点击打开或关闭虚拟空间结果显示选项对话框,如图 1-20 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-20

显示启用虚拟空间的选项对话框

在这里,所需的选项 Enable virtual space 位于对话框中间的 Settings 标题下。

组件结果包含已知可用但尚未安装在您的计算机上的组件。如果您返回到安装过程,可以选择要安装的单个组件(图 1-4 )。选择组件结果会触发该组件的安装,也就是说,它会启动 Visual Studio 安装程序并选择相应的组件作为要安装的组件。

模板结果用于为当前项目或全新项目创建新元素(如类或网页)。在这两种情况下,都会出现一个对话框,引导您完成命名元素或项目的步骤,并根据模板的要求进行选择。

在图 1-19 所示对话框的右下角,有一个链接允许您在线搜索您在文本框中输入的术语。这允许您查找不属于 Visual Studio 安装的组件和模板。这包括已经提供给 Visual Studio 市场的项目。

共享和同步设置

配置 Visual Studio 可能是个人的事情。和任何 IDE(集成开发环境)一样,人们喜欢自己喜欢的东西。他们习惯了某些组合键来启动某些功能。同样,让团队的所有成员使用同一套快捷键有时也很有用。这有助于成员之间的交流,很可能对一个团队成员有效的方法对所有成员都有效。

有两种设置可以帮助驱动这种功能。首先,可以定义设置文件的位置。这可通过“选项”对话框(可通过“工具”“➤选项”菜单项访问)中的“环境➤导入和导出设置”选项卡来实现。图 1-21 显示了选项对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-21

选项对话框中的导入和导出设置

这是在您修改设置时用来存储设置的位置。并且在启动 Visual Studio 时会读取该文件的内容。这对于团队来说非常有用,因为文件可以在网络共享上。这样,团队中的每个人都可以使用相同的设置文件。唯一的警告是,如果人们对他们的设置进行了更改,那么团队中的每个人都会接受这些更改。不太危险,但是如果一些预期的功能一夜之间消失了,就要注意了。

在更个人化的层面上,您可以将当前设置导出到一个单独的文件中进行备份,然后在以后导入它们。这是通过导入和导出设置向导完成的,该向导可通过工具➤导入和导出设置菜单选项启动。向导的初始屏幕如图 1-22 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-22

导入和导出设置向导

选择导出选定的环境设置,然后单击下一步开始导出过程。如图 1-23 所示,向导的下一步让您选择要导出的设置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-23

选择要导出的设置

有大量的设置可供选择。默认情况下,除了可能包含敏感信息的设置之外,所有设置都会被选中。这些设置标有黄色警告三角形,如图 1-23 中环境旁边的那个。默认情况下,这些设置不会被导出,因此如果您想要包括它们,您需要手动添加它们。

该向导中的最后一个对话框允许您指定保存文件的名称以及保存文件的目录。当您在最后一个对话框中单击“完成”时,设置将被保存。

设置的导入过程稍微简单一些。如果选择图 1-22 所示的导入所选环境设置,则出现图 1-24 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-24

导入设置时保存当前设置

在导入新设置之前,您可以选择保存当前设置。或者您可以覆盖您当前的设置。点击“下一步”会出现一个对话框(图 1-25 ,您可以在其中选择要导入的设置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-25

选择要导入的设置

此时,您可以选择要导入的设置。有两类供你选择。您可以选择一个默认设置。有些设置是由微软定义的,针对特定的开发人员子集。例如,前提是 Visual Basic 开发人员使用的设置不同于 Visual C++开发人员使用的设置。这些差异是基于不同类别的开发人员历史上所处的环境,或者他们更经常使用的 Visual Studio 的不同部分。

除了默认设置之外,还有您以前明确或自动保存的设置。这些是默认目录中的设置文件。还有一个浏览选项,可让您识别存储在其他地方的设置文件。选择所需的设置集合,单击“完成”按钮,这些设置将被导入。

图 1-22 中有一个额外的选项,用于重置您的所有设置。如果您选择该选项,您将转到图 1-24 中所示的屏幕,该屏幕允许您在继续之前备份当前设置。下一个屏幕如图 1-25 所示,不同之处在于没有你的个人设置文件——只有 Visual Studio 提供的默认设置。

除了创建包含您的 Visual Studio 设置的物理文件,还可以自动将您的设置同步到云,因为在这个时代,所有东西都是支持云的。云同步的好处是,当您在不同计算机上的不同 Visual Studio 实例之间移动时,您的设置会跟随您。只要您使用相同的云凭据登录 Visual Studio,您的设置就会存在。

您可以通过工具➤选项对话框中的环境➤帐户屏幕启用和禁用此同步(图 1-26 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-26

个性化帐户设置

有一个复选框支持跨设备同步。此外,还有一个云帐户(注册的 Azure 云)列表,这些帐户被识别为共享设置。对于大多数人来说,列表中只有一个项目,那就是他们的 Azure 帐户。但是,如果您有多个可用的 Azure 帐户,您可以使用“添加”按钮将它们添加到此列表中。

摘要

在本章中,我们介绍了安装和更新 Visual Studio 2019 的过程,以及启动应用时的外观。这包括访问以前打开的项目和解决方案,以及从头创建应用或从存储库中克隆应用的过程。此外,我们还研究了 IDE 的一些对团队开发有用的特性,或者如果您只是想找到隐藏在选项对话框中的设置。

在下一章,我们开始看看 Visual Studio 2019 如何帮助你更有效地编写代码。因为当你真正开始工作时,帮助你写代码就是 Visual Studio 的全部。

二、辅助编码

作为一个集成开发环境,Visual Studio 的优势之一是帮助您更有效和高效地编写代码。它是微软 Visual Studio 团队的一个焦点,每个版本都有几个或大或小的特性,旨在改进编程过程。在本章中,我们将介绍 Visual Studio 2019 中添加的功能,以及对早期版本中引入的功能进行的改进。在每种情况下,目标都是减少编码工作。

本章将重点讨论三个方面:

  • 查找代码能够快速搜索您想要查看的下一段代码,以及识别历史上谁曾处理过该代码。

  • 写代码写代码过程中的辅助。

  • 保持代码干净也称为静态分析,我们将介绍识别和清理不符合既定标准的代码的步骤。代码分析器用于帮助识别有问题的代码。代码清理有助于“纠正”代码。

查找代码

在 Visual Studio 2019 中,在代码库中搜索字符串或符号的功能绝对不是新功能。基本搜索功能与 Visual Studio 2017 中的相同,包括在当前文档内或跨解决方案中的所有文件进行搜索。唯一值得一提的改进是在文件中查找对话框,如图 2-1 所示,以及搜索结果窗格,如图 2-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1

在文件对话框中查找

“在文件中查找”对话框的附加功能可以在下拉列表中找到。以前,不可能确定要执行搜索的一组文件。但是,使用此下拉列表,您可以限制要搜索的文件。

通过从下拉列表中选择一个选项,将只搜索带有列表中扩展名的文件。这可以大大减少找到的结果的数量。但是功能更进一步。您可以手动修改扩展名列表,甚至输入您自己的列表。此外,当您更改或创建新列表时,它会被记住并作为一个选项出现在下拉列表中。

搜索结果窗格(图 2-2 )在 Visual Studio 2019 中有了重大改变。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2

搜索结果窗格

从选项卡上的名称开始,该窗格经历了重大的重新设计。选项卡上的名称现在包括作为搜索目标的字符串,而不是查找结果 1。当您开始同时激活多个搜索结果窗格时,这就容易多了。

默认情况下,结果先按路径分组,然后按文件分组。在图 2-2 中,这意味着日志目录路径有 128 个匹配项。在该目录中,有一个只有一个匹配项的js文件夹。在site.js文件中找到了这个匹配。当您向下移动结果时也是如此,在识别文件和实际匹配之前,匹配按文件夹分组。

至于匹配本身,将显示找到匹配的行,以及文件、行号和列号。如果您单击匹配项,文件将在编辑器中打开,光标被设置到该匹配项的行和列。

窗格顶部的工具栏包含操作和使用结果的方法。第一个下拉菜单允许您修改搜索范围。选项如下:

  • 所有文件搜索所有文件,任何文件类型都受本节前面描述的下拉列表的约束。

  • 打开文档搜索仅限于编辑器中当前打开的所有文件。

  • 当前文档搜索仅限于当前文档。

  • 已更改的文档搜索仅限于那些被源代码管理标记为已更改的文件。

紧邻下拉列表右侧的是一个复制图标。一旦您选择了其中一个结果,此功能就会启用。列名以及所选结果中的信息被复制到剪贴板。您可以使用标准的 Ctrl+(右键单击)和 Ctrl+Shift+(右键单击)击键或 Ctrl+A 选择整个结果集来选择多个结果。复制功能会将所有选定的项目复制到剪贴板。

接下来的两个图标是相关的。它们用于移动到下一场和上一场比赛。当您使用图标导航到匹配项时,会显示该文件,并且光标的位置就像您单击了该匹配项一样。

导航图标右侧的图标在图 2-2 中被禁用。它用于清除为搜索结果设置的任何过滤器,并且在定义了至少一个过滤器后启用。例如,如果您从第一个下拉列表中选择了打开的文档,则清除过滤器图标将变为启用状态。然后单击它会将下拉列表的值恢复到默认设置“所有文件”。

点击过滤器右侧的下拉菜单用于控制结果的分组。正如刚才提到的,默认情况下是先按路径再按文件对结果进行排序。但是,该列表中还有两个附加的分组选项。如果选择“仅路径”,则匹配项仅按找到它们的文件夹分组,而不是按文件分组。文件的名称出现在文件列中(在图 2-2 中可见),它们按文件排序。只是文件名不是结果层次结构的一部分。

另一个分组选项是不分组。在这种情况下,结果只是以列表的形式出现。和以前一样,文件名出现在列表中,但是没有对路径的引用,至少在默认视图中没有。通过右键单击结果列表并从上下文菜单中选择列选项➤路径项,可以添加路径列。此视图是最接近 Visual Studio 2017 中默认设置的结果。

下一个图标,保留结果图标,和标签上说的差不多。它将这些结果保存在一个单独的选项卡中。如果图标没有被选中,那么一个新的在文件中查找功能将重新使用同一个标签,也就是说你以前的结果将会丢失。如果您单击“保留结果”,则下一次“在文件中查找”将为这些结果创建一个新选项卡,允许您使用多个搜索结果集。

最后,右边的文本框用于进一步细化搜索结果。此处输入的任何关键字都用于减少结果集。只有那些也包含关键字的匹配才会继续显示。

除了在文件中查找之外,在最近几个 Visual Studio 版本中引入的用于在代码中查找符号的其他机制仍然可用。例如,如果你右击一个类名或变量名,上下文菜单包括如图 2-3 所示的选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3

编辑器上下文菜单的一部分

这些功能对 Visual Studio 2019 来说并不陌生,但从个人经验来看,它们的使用频率并没有达到应有的水平。尤其是 Peek 定义、查找所有引用、查看调用层次,似乎缺乏开发者的喜爱和关注。

峰值定义

Peek 定义背后的想法非常简单。当您从上下文菜单中选择“查看定义”时,符号的定义将显示在编辑器中,与代码的其余部分对齐。图 2-4 提供了图示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-4

峰值定义

现在您可以看到定义,而不需要切换到不同的编辑器窗格。这非常方便,因为它允许你停留在当前的环境中。而且有可能嵌套偷窥。因此,在查看显示中,您可以右键单击某个符号,然后从上下文菜单中选择查看定义。这将在同一个内嵌窗口中显示定义。选项卡指示器中出现两个点,允许您在两个(或多个)Peek 显示之间导航。

转至定义/转至实施

“转到定义”和“转到实现”选项在功能上非常相似,因此有理由对它们进行单独描述。

如果从上下文菜单中选择“转到定义”,将会转到该符号的定义。如果符号是变量,光标定位在声明上。如果符号是一个方法,光标定位在方法声明处。这相当简单。

如果方法或属性是接口的一部分,则会发生 catch。在这种情况下,声明被认为是接口中元素的定义。但在很多情况下,这并不是你真正想要的。您真的想找到该方法的实现。这就是使用“转到实现”选项的地方。

如果选择“转到实现”,并且符号是接口的一部分,光标将定位在相应的方法、属性上,甚至定位在实现该接口的类中。如果实现接口的解决方案中有多个可用的类,您可以选择要导航到的实现。

事实证明,如果您使用“转到实现”,并且符号不是接口的一部分,那么它的工作方式与“转到定义”完全相同。出于这个原因,我通常使用 Go To Implementation 作为两者之间的默认选择。

查找所有引用

“查找所有引用”选项的用途可以从其名称中找到。它在整个代码库中查找对当前符号的所有引用。如果您试图识别引用符号的位置,同时试图找出某个更改可能产生的影响,这是一个非常有用的函数。

虽然在 Visual Studio 2019 中查找所有引用提供的功能没有改变,但用于查看和操作结果的用户界面已经到了几乎与在文件中查找结果窗格相同的程度。图 2-5 包含一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-5

查找所有引用结果窗格

通过将此窗格与图 2-2 进行比较,相似之处非常明显。在这两种情况下,顶部的下拉菜单和图标执行相同的功能。不同之处在于,在第一个下拉列表中有一个额外的选项(当前项目)。并且可用于分组的选项完全不同。它们是项目然后定义(默认)、仅定义、定义然后项目、定义然后路径、定义、项目然后路径和无分组。

除了工具栏的变化,还有一个额外的列 Kind,它不存在于搜索结果中。种类值表示使用的类型。在图 2-5 中,值被读取和写入,因为目标符号是一个属性。方法调用也有一个 Read 类值。另一种可能是构造函数,当方法的构造函数被调用时。

查看呼叫层次结构

调用层次结构是对一个方法的所有调用的集合。请注意 Visual Studio 的早期版本,甚至一些在线文档也说明了从该方法到其他方法的所有调用也会显示出来。并非所有语言都是如此。为了减少结果页面的混乱,Visual Studio 2015 for C#中删除了查看呼出呼叫的选项。更不用说,在大多数情况下,通过查看代码,您应该能够很容易地判断哪些方法正在被调用。如果方法太大而不能快速看到信息,这可能是您需要做一些重构的信号。但这是第四章“重构代码”的主题

图 2-6 显示了从上下文菜单中点击查看调用层次选项的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-6

查看呼叫层次结构结果

结果显示在级联树中。有一个包含目标方法名的根节点。接下来是对 node 的一次调用,然后是调用该方法的每个实例的一个子实例。如果单击一个实例,有关调用站点的信息将出现在详细信息窗格中。具体来说,您可以看到进行调用的代码行和包含该调用的文件的名称,以及行和列。双击详细信息行将在编辑器中打开文件,光标位于代码行上。

每个实例还包含自己的对的调用。这允许您找出调用调用站点的位置。并且您可以继续将调用级联到节点,直到到达没有调用的点。这可以通过图 2-6 底部的消息来可视化。

您可以使用对话框左上角的下拉列表来限制呼叫层次结构的范围。在这里,您可以选择当前项目、当前文档或整个解决方案。

代码镜头

虽然这里提到的选项非常有用,但许多开发人员发现,当涉及到浏览代码库的现在和历史时,CodeLens 提供的功能是一个重要的性能增强器。CodeLens 背后的设计理念是让你不用离开编辑器就能确定你的代码发生了什么。换句话说,您可以在继续编码的同时找到关于代码的有用信息。这些有用的信息包括已经做出的更改、已经链接的 bug、代码审查和单元测试。

Note

Visual Studio 2019 社区版中并非所有 CodeLens 功能都可用。具体来说,与源代码管理相关的信息是不可见的。

为了了解 CodeLens 的样子,考虑图 2-7 中的例子,它显示了平视显示器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-7

带代码透镜平视显示器的代码

这是一个类中的一个属性,在一段时间内发生了许多变化。正如不到 5 分钟前所说,时间不多了。但是改变已经足够了。

CodeLens 平视显示器中有四条信息:

  • References 表示代码中引用该属性的位置。这类似于查找所有引用功能。

  • 最后一次修改最后一次接触这一行代码的人的名字以及它发生的相对时间。

  • 对该属性或方法进行了更改的人数,以及总共进行了更改的人数。

  • 工作项与属性或方法相关的工作项。

抬头显示器中的每个项目都是一个链接。让我们更详细地检查可用的信息。

参考

“引用”链接显示有关属性或方法使用位置的信息。图 2-8 包含一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-8

参考显示

从显示的内容来看,所有引用都在一个文件 HomeController.cs 中,每个引用的行号以及代码行都是可见的。如果您双击一个引用,那么该文件的编辑器将打开,并且光标将定位在该变量上。如果代码在多个文件中被引用,你会看到多个节点,如图 2-8 所示。默认情况下,窗格打开时引用是可见的,但是如果有很多引用,这看起来会很混乱。单击左下方的全部折叠链接关闭引用,这样只显示文件名。

由于 CodeLens 的抬头特性,“引用”窗格(事实上,所有 CodeLens 窗格)都是暂时的。当您单击该链接时,它会出现。当你点击其他地方,窗格消失。如果您想要将窗格保留更长时间,请点按窗格右上角的 Dock 弹出式图标。这将参考信息移动到它自己的可浮动和可停靠的窗格中。所有的信息和功能都保留了下来,只是增加了一个刷新链接来更新参考列表。

上次修改

最后的修改信息指示最近对属性或方法进行修改的人,以及他们何时进行的,而不是他们进行修改的具体时间,而是相对时间。相对时间是描述性的,基于最合适的单位。它可以显示“5 分钟前”,如图 2-7 所示。如果该行被修改已经有一段时间了,那么它可能显示为“3 个月前”。目的是让您了解变化发生的时间。如果您想找出它发生的确切时间,右键单击该方法并选择源代码管理➤责备(注释)。该选项的输出包括最近更改的确切日期、时间和提交 ID。

点击链接会显示变更的彩色时间线,如图 2-9 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-9

上次修改时间表

时间轴使用相对时间作为刻度。每个点代表对方法或属性所做的更改。确切地说,是将变更提交到源代码控制的时候了。右边是参与修改的作者列表,用颜色代码将作者与时间轴上的特定修改联系起来。

将鼠标悬停在一个点上可使关于变更的详细信息显示为工具提示,如图 2-10 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-10

上次修改时间表详细信息

在详细信息中,可以看到更改的确切日期、进行更改的人员、更改的类型(编辑或添加)以及与更改相关的提交信息。具体来说,包括提交 ID 和描述。

作者

虽然最后的修改信息包含了一些关于修改者的细节,但是作者的信息更深入一些。在平视显示中,仅显示作者和更改的数量。当你点击链接时,大量的信息就会出现。图 2-11 包含一个示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-11

作者详细信息

从平视显示中,您可以看到一个作者做了两处修改。通过这些细节,您可以看到导致这两个更改的实际提交。对于每次提交,提交 ID、描述、作者和日期都很容易获得。如果您将鼠标悬停在提交上,工具提示中会显示更多信息。

在详细信息窗格的底部,有几个控件会影响可见的更改数量。默认情况下,它只显示过去 12 个月的提交。在右边,该值在一个文本框中,可以根据需要进行更改。如果您想要查看所有的提交,那么单击窗格左下角的显示所有文件更改链接。这将打开历史窗格,如图 2-12 所示。现在,文件的所有提交都显示出来了,不管它们是否包含有问题的属性或方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-12

历史面板

您会注意到,对于第一次提交,您可以展开节点并查看与提交相关联的工作项。而且,与提交本身一样,将鼠标悬停在工作项上会显示更多的细节。

Tip

您会注意到在图 2-11 所示的提交细节中,部分描述包括文本“相关工作项目:#158。”如果您使用的是 Git 存储库,这是包含在描述中的重要文本。它用于将提交与特定的工作项相关联。在这种情况下,它将 ID 为 158 的工作项与提交相关联。如果没有它,工作项就不会作为提交的子级包含在显示中。并且,当涉及到平视显示器中的下一个组件时,它不作为工作项目包括在内。如果一个特定的提交包含不止一个工作项,那么您需要在每个 ID 前面包含 hashtag(数字符号)。换句话说,描述将包括一个类似“相关工作项:#158,#159”的链接

在详细信息窗格中,作者也是一个链接。一般来说,单击链接的目的是让您与提交的作者取得联系。这个想法是,你有一个关于提交的问题,你希望作者解决。单击链接时实际发生的情况取决于您工作的环境。

至少,单击该链接会为您的默认邮件程序打开一个电子邮件表单。作者的电子邮件地址包含在“收件人”字段中。电子邮件的主题和正文包括关于存储库和提交问题的信息,允许收件人快速建立问题的上下文。剩下的问题就看你自己了。

然而,如果你和作者都用 Skype 做生意,那么你就有了各种各样的额外魔力。作者列中包括作者的在场指示符。点击链接,你可以选择发送电子邮件,甚至直接与作者进行 Skyping 通话。

工作项目

工作项详细信息窗格中的可用信息类似于作者详细信息窗格中的可用信息。不同的是侧重点略有不同。图 2-13 包含了一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-13

工作项详细信息窗格

该视图显示与方法或属性相关联的工作项,并在下面嵌套提交,而不是从提交开始并显示子工作项。当您将鼠标悬停在提交上时,信息与图 2-11 中看到的信息相同。当您将鼠标悬停在某个工作项上时,您会看到关于该工作项的详细信息(作者、状态、描述和日期/时间),就像您将鼠标悬停在 Authors detail 窗格中的工作项上一样。当您单击作者链接时,可以在作者详细信息窗格中找到相同的功能。根据您的工作环境,您可能会收到一封电子邮件,也可能有权使用 Skype for Business 功能。

当编写了覆盖目标属性或方法的单元测试时,在抬头显示中还有一条额外的信息。这将在第三章“单元测试”中讨论

编写代码

帮助加速代码编写过程一直是 Visual Studio 的宗旨。举个例子,考虑一下像 IntelliSense 这样的特性对你来说有多重要。一旦你习惯了它,没有它提供的帮助就很难编写代码。有些人可能会认为它减少了开发人员的知识,因为它没有强迫他们学习如何用正确的语法和参数手动编写代码。这感觉就像 30 年前反对计算器和今天反对智能手机的理由一样。在所有情况下,争论都假定有效地使用工具并不是执行预期任务的更好方式。我认为确实如此。对开发者来说幸运的是,微软同意这一点。

对于 Visual Studio 2019 来说,greatest note 的代码编写功能是 IntelliCode,虽然平心而论,IntelliCode 实际上是 Visual Studio 的扩展。它不仅支持 Visual Studio 2019 的所有版本,还支持 Visual Studio 2017 和 Visual Studio 代码的最新版本。

当谈到 IntelliCode 到底是什么的问题时,一行字的答案感觉有点模糊:IntelliCode 使用人工智能技术来增强您的编码体验。让我们把这句话拆开,看看它实际上是如何应用到你身上的。但首先,让我们确保您已经安装了 IntelliCode 并准备就绪。

安装 IntelliCode

有两种方法可以将智能代码安装到您的系统中。第一种,也可能是最简单的一种,是选择一个默认包含它的工作负载。这将是支持 C#、C++、TypeScript/JavaScript 或 XAML 的任何工作负载。此外,您需要运行 Visual Studio 2019 的 16.1 版本或更高版本。

或者,您可以从 Visual Studio 市场下载它。链接是 https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.VSIntelliCode 。这将下载一个 VSIX (Visual Studio 扩展安装程序)文件,该文件在执行时会将 IntelliCode 安装到支持它的计算机上的所有 Visual Studio 实例中。

安装 IntelliCode 后,需要先启用这些功能,然后才能使用。这是通过“工具”“➤选项”菜单项完成的,然后导航到“智能代码”部分。对话框如图 2-14 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-14

配置智能代码功能

默认情况下,您看到的标记为“Enabled”的属性被设置为“Default”,这是意料之中的,因为 IntelliCode 已经在预览中。在撰写本文时,一些特性即将作为 Live 发布,但是可以肯定的是,只需启用您想要使用的特性,对于本书来说,这些特性是 C#、自定义培训和 EditorConfig 特性。

现在您已经有了可以使用的 IntelliCode,让我们来分析一下这一行的描述。第一,人工智能部分。是的,这确实是新功能和产品的一个时髦术语。然而,在这种特殊情况下,微软已经利用机器学习来生成用于从 IntelliSense 内部做出“智能”建议的启发。作为其最初的知识主体,使用了数千个备受推崇(超过 100 颗星)的开源 GitHub 项目。基于此,IntelliCode 建立了一个通用实践和可能建议的基础。然而,IntelliCode 还使用您现有的代码来调整建议,使之更有利于您。为此,您必须在您的代码基础上对其进行训练,以创建一个定制模型。

要启动此过程,请使用查看➤其他窗口➤ IntelliCode 模型管理器菜单选项。出现一个类似于图 2-15 的屏幕。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-15

智能代码模型管理

此页面允许您管理整个环境中的各种 IntelliCode 模型。可以使用多个模型(不是同时使用,而是针对不同的解决方案)。但是如果你仔细阅读页面上的描述,在这个过程中有一些东西可能会让你暂停。

首先,IntelliCode 分析您的项目,寻找使用模式。这些信息被打包到一个摘要文件中。该文件包含关于所声明的类型以及如何使用这些类型的元数据。然后,该摘要文件被上载到 Visual Studio IntelliCode 服务,并在那里与当前的 IntelliCode 模型相结合。这样,您就可以下载并激活一个特定于您的解决方案的定制模型供您使用。下载模式实际上可以与你选择的任何人分享,例如你团队中的其他人。

这个过程中可能会暂停的部分与将摘要文件上传到云有关。微软明确声明你的源代码不会被上传。这只是摘要信息。但是,虽然这对许多人来说不是问题,但有些组织会对将看似知识产权的一部分迁移到云中持怀疑态度。不幸的是,这样做是能够使用 IntelliCode 的一个要求。

如果您有兴趣查看正在共享的内容,这些文件位于%TEMP%\Visual Studio IntelliCode 文件夹中。文件夹的名称是随机的,因此,请通过按日期降序排列文件夹来打开您最近的培训课程。

文件夹中是发送给 Microsoft 的整套文件。UsageOutput 子文件夹包含一个 JSON 文件,该文件包含由 IntelliCode 提取的信息。这是用于使用您的自定义数据训练模型的信息。UsageOutput _ ErrorStats 文件包含尝试构建提取文件时发现的任何错误。如果需要调试摘要文件生成过程中的任何问题,Microsoft 会使用它。

要开始培训过程,请单击“启动新模型”按钮。几乎立即创建、上传和处理摘要文件。完成后,结果如图 2-16 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-16

IntelliCode 模型的定型结果

此时你有几个选择。首先,要明白你的模型已经可以使用了。此时,您不需要再做任何事情来利用 IntelliCode。此页面上的选项是关于管理您拥有的模型的。

首先,在活动模型标签下,有两个按钮。第二种最容易理解。删除按钮用于删除当前模型。这意味着 IntelliCode 功能将不可用于该解决方案。第一个按钮 Share model 用于与其他人共享这个模型。将生成一个 URL 并添加到您的剪贴板中。这样,您可以将 URL 发送给任何您想与之共享模型的人。他们使用页面左侧的添加模型链接。点击该链接将启动如图 2-17 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-17

添加共享 IntelliCode 模型

粘贴与您共享的链接,单击 Add,该模型将成为解决方案的活动模型。

Note

是的,其他人可以使用 URL 访问您的模型这一事实意味着模型的副本保存在云中。这也意味着您应该将 URL 视为源代码,不要与您不信任的人共享。

图 2-16 中的另一个按钮用于重新运行模型训练过程。这不是你需要经常做的事情。只有在添加了代码之后,您才不会从 IntelliCode 获得您所期望的结果类型(就建议的 completed 或参数重载而言)。但是当你点击重新培训,整个过程重做。生成新的摘要文件,并创建和下载新的模型。如果你想与人分享,需要一个新的网址。

智能代码功能

现在 IntelliCode 已经准备好了,它做什么呢?嗯,有几个特性是智能代码的一部分,但更多的是定期添加。它与 Visual Studio 2019 分开安装的原因之一是,它可以有一个不与 Visual Studio 本身挂钩的升级周期。因此可以预览或发布新功能,而不需要对 Visual Studio 进行相应的更新。

上下文感知代码完成

首先,让我们考虑一下 IntelliCode 中最明显的特性之一。到目前为止,大多数开发人员都知道智能感知。事实上,很大一部分人的生死取决于它有多好。上下文感知代码完成功能利用机器学习来提供更好的建议。考虑下面的数字。图 2-18 是添加 IntelliCode 之前的 IntelliSense。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-18

预智能代码智能感知

属性和方法按字母顺序排列。选择正确的属性很容易,但通常需要键入额外的字符或使用鼠标来完成。图 2-19 位于代码中的相同位置,但启用了 IntelliCode。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-19

后智能代码智能感知

虽然大多数方法仍按字母顺序排列,但有五种方法已被移到列表的顶部。名称左边带星号的项目是 IntelliCode 为您建议的项目。虽然它没有涵盖所有可能的情况,但是仔细想想,这五个函数很可能涵盖了字符串函数的很大一部分。他们现在只差一两个光标了。这就是上下文感知代码完成特性的目标。

生成 EditorConfig 文件

本章稍后将详细讨论 EditorConfig,但简言之,EditorConfig 文件用于帮助确保编写的代码符合某些标准标准,如变量和方法名的大小写、最小变量长度,以及其他有助于在大型代码库中保持代码一致的规则。

虽然可以手动构建 EditorConfig 文件,但 IntelliCode 会查看您的代码库并为您生成一个。在解决方案资源管理器中,右击解决方案或项目,然后从上下文菜单中选择“添加➤新编辑器配置(IntelliCode)”。这将检查解决方案或项目中的代码,创建一个.editorconfig文件,并将其添加到解决方案或项目中。

关于如何使用 EditorConfig 文件的信息可以在本章后面的“代码清理”部分找到。

保持代码整洁

很长一段时间,FxCop 是确保这一点的标准工具。NET 代码符合特定的风格标准和规范。它对项目或解决方案中的文件执行静态代码分析,并报告结果,通常是错误或警告。然而,在过去的几年里。NET 编译器平台(也称为 Roslyn)已经取代了对静态分析的需求。相反,当您的代码不符合定义的标准时,您会立即面对熟悉的绿色和红色曲线。在本节中,我们将了解 Visual Studio 2019 如何帮助确保您的代码符合通用标准,确保您的代码没有绿色和红色的波浪线。

Visual Studio 2019 包括一组内置的代码分析器。这些分析器在您键入时评估您的代码。如果分析器检测到违规,就会在“错误列表”窗口和代码编辑器中报告。代码编辑器中的报告由所有开发人员都熟悉的弯曲下划线组成。

动态分析过程的一个强大特性是许多规则都定义了一个或多个代码修正。这个想法是,如果你应用了代码修复,问题就被纠正了,曲线就消失了。代码修复作为快速操作之一显示在灯泡图标菜单中。

配置分析仪

大多数项目都有自己的一套内置分析器。您可以在解决方案资源管理器的"依赖项"节点下找到它们。图 2-20 展示了 ASP.NET 核心项目的分析仪。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-20

解决方案资源管理器中的内置分析器

这些只是内置的分析器。可以将分析器添加到您的项目中,通常是通过 NuGet 包或作为 Visual Studio 扩展。还要注意,根据分析器的不同,有时列表可能出现在 References 节点下,而不是图 2-20 中的 Dependencies 节点下。

对于任何已安装的分析仪,您可以查看属性并修改其中一项设置。通过属性表可以看到这些属性。这可以通过右键单击分析器并从上下文菜单中选择属性来访问。图 2-21 中显示了一个分析仪的属性示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-21

分析器属性表

最有可能感兴趣的属性是消息,如果分析器所涵盖的情况存在,它就是出现在错误列表中的内容。在消息、标题和帮助链接之间,可以发现关于情况和可能的解决方案的信息。此外,默认严重性指示错误列表中是否出现错误或警告。

虽然从图 2-21 中可能看不出来,但是属性表中的所有值都被禁用,这意味着您不能修改分析仪的任何值。仔细想想,这是有道理的。但是,有时您可能希望修改默认严重性。这实际上不是通过属性表完成的,而是通过解决方案资源管理器完成的。

在解决方案资源管理器中右击分析器,然后从上下文菜单中选择 Set Rule Set Severity 选项。这将显示一组选项,如图 2-22 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-22

为分析仪设置规则集严重性

可用的选项如下:

  • 默认使用分析仪指定的默认严重性。

  • 错误将消息显示为错误。通常,错误和其他严重性之间的区别在于其他工具如何看待它。首先,曲线是红色的。第二,其他工具经常将错误消息视为进一步管道处理的终止。例如,它会阻止编译成功。

  • 警告将消息显示为错误。波浪线是绿色的,只有当管道被配置为在出现警告时停止时,它才会影响管道进程。

  • 信息该信息显示为信息性的,并且曲线为灰色。对任何管道处理都没有影响。

  • 开发者看不到任何显示。违规情况会报告给 IDE 诊断引擎。因此,它可能会出现在某些日志中,或者被计为违规。

  • 所有与规则集相关的消息或指示都被抑制。

出现在解决方案资源管理器中分析器名称左侧的图标提供了当前规则集严重性的直观指示。

  • 错误圆圈内有一个“x”

  • 警告一个“!”在一个圆圈里

  • 圆圈内的“我”

  • 隐藏在一个圆圈里的也是一个“我”,但是图标的背景是浅色的

  • 圆圈内的向下箭头

您可能要考虑的另一个因素是默认严重性和有效严重性属性之间的差异。默认严重性是分析仪安装时定义的严重性。它不会改变,当您将规则集严重性设置为默认值时,将使用该值。

然而,还有其他因素决定了分析仪的实际或有效的严重程度。例如,更改规则集的严重性会影响有效的安全流程。此外,项目级设置(如将警告视为错误)也在确定有效安全性时发挥作用。

自定义规则集

到目前为止,我们一直在抽象地使用术语规则集。但是更深入地研究细节是值得的,特别是因为可以定制内置分析器中包含的规则集,而且还可以从头开始创建规则集。

本质上,规则集只是一个 XML 文件。XML 文件中包括分析器标识符和规则标识符的集合。对于每个规则,如果它不同于缺省值,则包括严重性。这种极简方法的原因是每个分析器都有自己的一组规则和每个规则的默认严重性。XML 文件只包含对这些默认值的更改。

因为编辑 XML 并不有趣,所以有一个用于规则集文件的编辑器。要启动编辑器,请在解决方案资源管理器中右键单击 Analyzer 节点,然后选择“打开活动规则集”选项。这将打开当前规则集的规则集编辑器,如图 2-23 所示。这与手动创建规则集文件时出现的编辑器相同。

Note

上下文菜单上的此选项不适用于。净核心项目。对于这些类型的项目,无法通过解决方案资源管理器打开活动规则集。相反,将手动创建一个规则集文件,然后将其添加到项目中。但是随之而来的是,项目文件需要手动编辑。更具体地说,需要添加如下项目组:

<PropertyGroup>

<CodeAnalysisRuleSet>

$(MSBuildThisFileDirectory)MyCustom.ruleset

</CodeAnalysisRuleSet>

</PropertyGroup>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-23

规则集编辑器

每个相关的分析器都有自己的节点。在分析器中,有一个规则集合。然后,对于每个规则,都有一个 ID、一个名称和一个操作,这实际上就是严重性。您可以使用规则 ID 前面的复选框来指示该规则是否应该处于活动状态。然后,通过修改操作来指定规则的严重性。

对于规则集,有一个额外的选项可供您使用。在底部,有一个单选按钮,用于确定是否使用作为单个规则一部分的帮助链接。第三种选择可能看起来有点奇怪。毕竟,为什么你想使用在线帮助一次。但是它与帮助的显示方式有关。在这种情况下,它会提示您是否要使用在线帮助,然后记住您的选择。

由于该规则集文件取自活动规则集,因此可以直接保存。如果您进行任何更改并保存它们,系统会自动提示您提供新的文件名。该文件将成为项目的一部分,并将成为以后的活动规则集。

代码清理

Visual Studio 2019 新增了一个名为代码清理的功能。前提取自格式文档函数。通过几次击键或鼠标点击,您可以运行一个进程,对您的代码执行一组定义的清理例程。首先,为了帮助理解,让我们定义您想要在清理时执行的例程。

代码清理的配置可以通过几种方法来实现。首先,在解决方案浏览器中,右键单击一个项目,然后选择分析和代码清理➤配置代码清理选项稍微可爱一点,但不如代码编辑器底部的扫帚图标容易访问(如图 2-24 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-24

从编辑器中配置代码清理

单击图标右侧的下拉菜单会显示一个菜单,其中包含“配置代码清理”选项。这种方法的缺点是图标只对代码清理支持的编辑器可用。不管你如何到达那里,图 2-25 显示了用于配置代码清理的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-25

代码清理配置对话框

从概念上讲,配置非常简单。有两种配置文件可供您使用。对于每个配置文件,您可以选择要应用的过滤器。默认情况下,概要文件已经添加了几个过滤器(删除不必要的 using 和排序 using)。右侧底部是其他过滤器的列表。要将它们添加到配置文件中,请从底部列表中选择它们,然后单击向上箭头。要从配置文件中删除过滤器,请在顶部列表中选择过滤器,然后单击向下箭头。一旦您按照自己的意愿配置了配置文件,单击 OK 保存它们。

若要为配置文件运行代码清理,请使用解决方案资源管理器选项(右键单击某个项目,然后选择“分析和代码清理”选项)或扫帚图标。在这两种情况下,选择名称中带有所需概要文件的运行代码清理项。

这似乎很简单,没有痛苦。而且,在很大程度上,的确如此。大多数过滤器都很容易理解。但是,有许多过滤器中包含单词“preference ”,例如 Apply“this”限定首选项和 Apply implicit/explicit 类型首选项,您可以在其中指定您的首选项。

答案在选项对话框中。这也引出了下一个主题,EditorConfig 文件。

编辑器配置文件

过去,当您使用“设置文档格式”命令时,总会有一组对文档执行的过滤器,以便对文档进行格式化。同样的方法也适用于上一节中描述的代码清理功能。Visual Studio 2019 的一个新增功能是能够直接生成 EditorConfig 文件,允许在不同的队友之间共享。虽然您可以手动生成该文件,但仍然存在如何指定组成该文件的不同样式设置和首选项的问题。要定义这些,使用工具➤选项对话框。图 2-26 显示了选项对话框的 C# ➤代码风格部分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-26

选项对话框中的 C#代码样式屏幕

你会注意到有相当多的样式设置可以调整。如果你回到上一节末尾的问题,这里是你可以指定你的偏好的地方。在图 2-26 中,优先选择隐式声明用于内置类型,否则使用显式声明。运行代码清理时,将“应用隐式/显式类型”首选项设置为过滤器之一,然后将这些选择应用于代码。

为了在团队成员之间共享风格偏好,需要将它们导出到一个.editorconfig文件中。在图 2-26 中设置部分的顶部,有一个根据当前设置生成文件的按钮。当您单击该按钮时,系统会提示您为文件命名。当您保存文件时,它也将被添加到项目中。将文件保存在您的项目结构中是一个好主意,这样它将成为您的存储库的一部分。

或者,您可以通过任何其他方法发送.editorconfig文件。它是一个物理文件。一旦将该文件添加到项目中,其中包含的设置将成为用于执行代码清理的设置。

摘要

在本章中,您已经了解了 Visual Studio 2019 能够以不同的方式帮助您实现日常开发流程中固有的功能。查找代码和编写代码是非常基本的开发任务。保持代码的整洁和一致会有回报,即使你不得不看着你两年前或两个月前写的代码,这种可能性不大。拥有一个工具来确保您的代码(以及其他人的代码)遵循一个通用的标准,这使得阅读整个代码库变得容易得多。

下一章继续让你的代码更好的主题。它包含对单元测试的讨论,以及 Visual Studio 2019 对帮助您编写和运行单元测试的支持。

三、单元测试

对于现代应用开发人员来说,典型的工作流包括一系列反复重复的步骤。这些步骤俗称红/绿/重构。您为开发中失败的功能创建一个单元测试。这导致单元测试结果中出现红色。接下来,编写足够的代码来通过单元测试,然后重新运行所有测试。如果一切顺利,你应该有一个绿色(所有测试通过)。最后一步是重构你写的代码。整理一下。找到重复的代码并将其转换为单独的方法。确保遵守编码标准。完成后,再次运行测试以确保它们仍然是绿色的。至此,循环完成…红色,然后绿色,然后重构。并且重复这个循环,直到当前任务的开发完成。

Visual Studio 2019 通过许多相关功能帮助您度过这个周期。它能够使用许多不同的框架来创建和运行单元测试。还有一些工具可以帮助你完成重构步骤。但除了帮助完成基础工作,Visual Studio 2019 还提供了自动生成单元测试的能力,并确定测试了多少代码。如果你有 Visual Studio 2019 的企业版,实时单元测试,它允许你在打字时跟踪单元测试的状态。不过还是从基础开始吧……在 Visual Studio 2019 中创建单元测试。

编写和运行单元测试

Visual Studio 2019 包括一个内置的单元测试框架。它叫做 MSTest。然而,并不是每个团队都在使用 MSTest。像 NUnit 和 xUnit 这样的框架在专业开发团队中很常见。Visual Studio 也支持它们。令人高兴的是,尽管一些细节发生了变化,但是不管测试框架如何,编写单元测试的基本结构和流程是相同的。对于本节中的示例,我们将使用 MSTest,因为它包含在 Visual Studio 中。但是对于其他框架,几乎只有语法和属性名发生了变化。流量接近相同。

该示例的起点是一个 web 应用。虽然该应用是一个简单的销售订单系统,但是您将为其创建测试用例的功能在 order 类中,特别是一个将 OrderLine 对象添加到 Order 类的行集合中的方法。

首先,使用 Visual Studio 打开章节 3 。开始文件夹中的 sln 文件。该解决方案中有许多项目(欢迎您运行它来查看实际情况),但我们关心的一个项目名为 OrderWebApp.Library,它是包含要测试的类的项目。

测试项目模板是 Visual Studio 可用的大部分工作负载的一部分。对于 ASP.NET 核心 Web 应用(示例应用是 ASP.NET 核心),有三种不同的类型。如果在解决方案资源管理器中右击该解决方案,然后选择“添加➤新项目”,将出现“添加新项目”对话框。在顶部的搜索框中键入 test,只显示不同类型测试的模板。图 3-1 就是你会看到的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-1

为测试模板添加新项目对话框

前四个模板中的三个是用于 MSTest、NUnit 和 xUnit 测试的项目。NET 核心应用。选择 MSTest 测试项目(。NET Core)并单击下一步。输入 OrderWebApp 的项目名称。测试并单击 Create 来创建项目。

新创建的项目没什么看头。图 3-2 显示了项目的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2

单元测试项目结构

一点也不多。只有一个包含测试方法和测试设置方法占位符的文件。但是在做任何事情之前,需要添加的是对被测试库的引用。否则,就不可能调用将要测试的方法。右击该项目,并从上下文菜单中选择“添加➤引用”。在出现的对话框中,选择 OrderWebApp。库项目,因为我们要测试的类就是在那里实现的。

在进入代码之前,简单地偏离一下单元测试类和测试方法的命名。首先,更普遍接受的方法名称之一是使用以下格式:unit of work _ state beingtested _ expected result。UnitOfWork 可以很小,就像单个方法一样,尽管更常见的是在类级别将测试组合在一起。StateBeingTested 表示作为测试主题的工作单元中的状态。ExpectedResult 表示运行测试的预期结果。可能的值将指示成功的类型或遇到的特定异常。

出于组织的目的,测试方法被放入不同的类中。这些类的命名应该完全基于您希望如何在测试资源管理器中显示测试,因为类名是用于显示测试结果的层次结构的一部分。一般来说,每个类都有自己的单元测试类,这将在本例中使用。还要考虑到班级名称和工作单位确实不需要重复。创建一个 OrderTest 类,然后让工作单元为 Order,这是复制信息。使用工作单元来表示正在测试的 Order 类中的属性或方法。

将所有这些放在一起,让我们看看一个简单单元测试的代码:

[TestClass]
public class OrderTest
{
    [TestMethod]
    public void TestMethod1()
    {
    }
}

这是生成的代码,除了类名。包含单元测试的文件名从 UnitTest1.cs 更改为 OrderTest.cs,导致类名更改。有两个元素表明这个类将在单元测试中使用。该类的 TestClass 属性指示该类将包含测试。修饰方法声明的 TestMethod 属性将该方法标记为测试。TestMethod 属性是必需的,因为并非类中的每个方法都是测试方法。在测试类中有不同的助手方法在测试方法中使用是可能的,也是经常需要的。当您选择运行单元测试时,测试运行器(执行测试的进程)会查看测试项目中的测试类,并找到所有具有 TestMethod 属性的方法。这些是将要执行的测试。

对于这个例子,我们将为 Order 类中的 AddOrderLine 方法编写一个测试。测试的成功标准是,当添加订单行时,订单对象的 Lines 属性的计数为 1。若要运行此测试,以下方法应替换 OrderTest 类中的 TestMethod1 方法:

[TestMethod]
public void Lines_AddOrderLine_LinesCountIsCorrect()
{
   Order target = new Order();

   target.AddOrderLine();

   Assert.AreEqual(1, target.Lines.Count,
      "The order line was not added");
}

按照现在的情况,这个应用应该不会成功编译。虽然不看 Order 类的代码看不出来,但是 AddOrderLine 方法还没有定义。这是单元测试的“红色”过程的一部分。但是不能编译是没有帮助的。需要将 AddOrderLine 方法添加到 Order 类中。您可以通过编辑 Order 类并添加一个名为 AddOrderLine 的方法来手动完成此操作。或者,您可以使用快速动作来完成相同的目标,而无需从当前编辑上下文切换。使用 Ctrl+。击键打开快速动作菜单(也可以通过点击代码行target.AddOrderLine()左侧的灯泡打开),并选择生成方法“订单”。AddOrderLine”选项。如果使用快速操作,该方法如下所示:

public void AddOrderLine()
{
   throw new NotImplementedException();
}

这个方法不做任何事情,除了在被调用时抛出一个异常,这正是我们想要开始的地方。这有助于测试变红。但是为了确认它是红色的,我们需要运行测试。

运行测试有几个选项。从单元测试类本身的代码中,最简单的方法是右键单击编辑器中的任意位置,然后选择 Run Tests。这将生成项目并运行测试,在测试资源管理器中显示结果。第二种方法是使用同一个测试资源管理器对话框。若要访问测试资源管理器,请使用“测试➤测试资源管理器”菜单选项。测试浏览器将如图 3-3 所示。至少在您展开树中的所有节点后会这样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-3

测试浏览器

测试资源管理器为您提供了许多运行测试的方法。但是对于第一次执行,单击 Run All Test 工具栏按钮(左边的第一个)。在构建和运行之后,结果如图 3-4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-4

测试失败的测试资源管理器

鉴于图像是黑白的,这并不明显,但整棵树都是红色的。树中的所有图标都表示测试失败。同样,在工具栏中,有一个带有 x 的红色图标。该图标旁边是失败测试的数量。拥有所有这些不同的指标是一个想法。红/绿/重构的“红”已经实现。

该过程的下一步是通过编写通过测试所需的最少代码来修复测试。将 AddOrderLine 方法更改为如图 3-5 所示即可实现这一点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-5

使该方法通过测试

首先,请注意,为了通过测试,只添加了一行。这是这个过程的最小方面。其次,第 2 “辅助编码”一章中描述的方法增加了抬头显示显示中的第二个元素表明该方法被 1 个单元测试覆盖,并且 0/1 的测试通过。平视显示器中测试元件的功能将在本章后面详细介绍。

回到绿色测试。更改代码后,在“测试资源管理器”窗格中,再次单击“全部运行”链接。项目构建,测试运行,结果如图 3-6 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-6

通过测试的测试资源管理器

过去是红色的现在是绿色的。工具栏中的绿色图标(带有复选标记)旁边现在有一个 1(表示一次成功的测试),而红色图标旁边有一个 0。红/绿/重构过程的第二步已经完成。第三步包含在第四章“重构代码”中。

测试浏览器

测试资源管理器是 Visual Studio 2019 单元测试的核心。它包含了几个旨在简化单元测试运行的特性。

您已经使用了顶部的 Run All Test 工具栏按钮来执行解决方案中的所有测试,这在只有少量测试时(或者当您确实需要运行所有测试时)是很好的。但是紧挨着右边的是一个运行工具栏按钮,点击它会打开一个下拉菜单。下拉菜单的内容如图 3-7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-7

运行…下拉菜单

第一个选项 Run 运行树中所选节点内包含的所有测试。如果选择了一个类,那么这个类中的所有测试都会被执行。如果选择了单个测试,则只运行该测试。第二个选项,运行所有测试,就是这样做的。执行当前解决方案中的所有测试。

上下文菜单中接下来的三个选项根据测试的状态运行不同的测试集。测试可以处于三种状态之一:通过、失败和尚未运行。通过的测试将被认为是“绿色”,失败的测试将被认为是“红色”,而尚未运行的测试是指尚未在当前工作会话中运行的测试。可用的选项允许您执行这些测试子集之一。“重复最后一个测试”选项用于重新执行先前的测试运行,而不考虑包含的测试集。

图 3-7 下拉菜单第二部分的三个选项用于调试不同组的测试。在执行的测试集中,“调试”、“调试所有测试”和“调试上次运行”与“运行”、“运行所有测试”和“重复上次测试”相同。不同之处在于,如果在测试或应用中设置了断点,那么当遇到断点时,执行就会暂停。如果您试图找出测试失败的原因,这个功能非常有用。

Tip

还可以在调试模式下执行测试,方法是在编辑器中右键单击并从上下文菜单中选择 Debug Tests 选项。

下拉菜单中的最后一个选项是分析所有测试的代码覆盖率。此选项运行所有测试,但是如果测试成功,它还会为运行生成代码覆盖率分析。关于代码覆盖率的更多细节可以在本章后面的“代码覆盖率”部分找到。

在运行测试时,您可以从测试资源管理器中选择另一个选项。如果右键单击层次结构中的一个节点,会出现一个上下文菜单,如图 3-8 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-8

测试资源管理器上下文菜单

上下文菜单中最上面的两个选项运行测试。所包含的测试取决于所选的节点,但最终取决于所选节点或其下的所有测试。换句话说,如果您右键单击 OrderTest,并选择 Run,那么 OrderTest 中的所有测试都会被执行。这同样适用于调试,除了测试是在调试模式下执行的。

在上下文菜单的底部,有一个“转到测试”选项。仅在选择单个测试时启用,此选项打开所选测试所在的文件,并将光标置于相应的方法上。

上下文菜单中还有四个其他选项:关联到测试用例、分析代码覆盖率、配置文件和添加到播放列表。“关联到测试用例”和“添加到播放列表”选项将在本章后面的章节中介绍。Analyze Code Coverage 选项为选定的节点运行测试,在成功的测试运行(即没有失败)之后,执行代码覆盖分析。代码覆盖率的细节将在本章后面的单独章节中介绍。Profile 选项用于为测试所使用的代码启动性能分析。有关分析的详细信息可以在第七章“调试和分析”中找到

为了查看测试资源管理器中可用的一些附加功能,让我们向 OrderTest 类添加另一个单元测试。特别是,添加以下方法:

[TestMethod]
public void Lines_AddOrderLineWithProduct_ProductIsCorrect()
{
    Order target = new Order();
    Product product = new Product()
    {
        Id = 1,
        Number = "A-123"
    };

    target.AddOrderLine();

    Assert.AreEqual(1, target.Lines.Count,
        "The order line was not added");
    Assert.IsNotNull(target.Lines[0].Product ,
        "No product was added to the order line");
    Assert.AreEqual(product.Id,
        target.Lines[0].Product.Id,
        "The product was not added to the order line");
}

这个单元测试背后的想法是将一个产品对象传递到 AddOrderLine 方法中,然后应该可以在添加的订单行上找到该产品。此时,单元测试应该会失败,这正是我们想要的。当所有测试运行时,测试浏览器看起来如下(图 3-9 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-9

具有多个失败测试的测试资源管理器

您会注意到其中一个测试通过了,另一个测试失败了。如果您选择了一个失败的测试,那么关于失败的详细信息将出现在右侧。有用的部分是消息,它包含在对 Assert 方法和堆栈跟踪的调用中。堆栈跟踪包含对异常进行的调用列表。虽然示例中只有一个,但是堆栈跟踪中可以有更多项。每一项都是一个链接,单击该链接将打开相应文件的编辑器,并将光标置于异常发生时正在执行的方法上。当试图确定 bug 的来源时,所有这些都非常有用。

本章前面提到了代码中的平视显示。图 3-10 包括 AddOrderLine 方法的当前视图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-10

AddOrderLine 方法平视显示器

与测试相关的抬头显示元素显示 1/2 的测试通过。如果点击该链接,将显示如图 3-11 所示的信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-11

测试抬头显示信息

平视显示器提到有两项测试,其中一项是通过。信息显示包括测试列表,左侧的图标指示测试的状态(通过或失败)。在信息显示中,您可以通过双击某个测试来导航至该测试。此外,在左下方,有运行所有测试或者只是运行所选测试的链接。

与测试用例相关联

“与测试用例关联”选项用于将单元测试链接到 TFS (Team Foundation Server)或 Team Services 中的测试用例。图 3-12 显示了选择该选项时出现的对话框。

Note

如果“与测试用例关联”选项不可见,则很可能是因为您登录 Visual Studio 所使用的帐户未与 TFS 或 Team Services 关联。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-12

与测试用例对话框相关联

虽然只有在选择了单个测试时才启用这个选项,但是理解这个关联的上下文是很重要的。通过将单元测试与测试用例联系起来,将有助于理解一些限制。

团队服务/TFS 中的测试用例是为了测试一项功能而需要执行的一组步骤的描述。例如,一个测试用例可能是为一个客户创建一个包含产品“A-123”的订单它包括一个用户添加订单所需步骤的详细列表,包括选择的菜单选项、输入的数据和单击的按钮。在过去,这可能大致相当于手动测试。

当您将一个测试与一个测试用例相关联时,您就说这个测试覆盖了测试用例中描述的功能。这意味着测试的成功完成具有相同的权重,就好像用户已经手动完成了测试用例中的所有步骤,并且它们都通过了。

当自动化执行测试时,将测试与测试用例相关联的真正好处就来了。如果项目和测试是构建管道的一部分,那么可以将测试配置为自动运行。当这种情况发生时,与测试用例相关联的测试作为构建的一部分被执行,来自该测试运行的结果也与测试用例相关联。如果测试通过,测试用例被标记为完成。如果测试失败,测试用例也被认为是失败的。对于在开发中有许多移动部分的复杂系统,测试与测试用例的关联以及构建管道中的后续包含可以是对开发工作流的强大补充。

然而,作为与测试用例相关的上下文的结果,对于哪些类型的测试可以与一个测试用例相关联有一些限制。毕竟,将本章中构建的简单单元测试与多步骤、手动执行的测试用例联系起来是没有意义的。以下类型的测试可以与一个测试用例相关联:

  • 编码 UI 测试(尽管需要注意的是,在 Visual Studio 2019 之后,对编码 UI 的支持已被否决)

  • 硒测试

  • 使用 MSTest 框架版本 1 编写的单元测试

  • MSTest 版本 2、NUnit 和 xUnit 测试(但是,如果它们是使用 TFS 管道构建的,则不能是较旧的 XAML 版本之一)

  • 。NET 核心框架测试(但是,它们不能使用旧的 XAML 版本)

使用 JavaScript 测试框架的测试,比如 Chutzpah 或 Jest,不能与测试用例相关联。

Note

虽然同一个测试可以与多个测试用例相关联,但是每个测试用例只能与一个测试相关联。

如果您确实有一个合格的测试,那么将一个测试与一个测试用例相关联的流程是简单的。例如,使用 TFS 门户获取测试用例的标识符。在文本框中输入标识符,然后单击添加关联。对话框底部的列表显示了所选测试的所有现有关联。

播放列表

在图 3-8 的上下文菜单中还可以看到一个添加到播放列表选项。播放列表是一种机制,用于创建可以一起执行的测试组。到目前为止,测试的唯一分类是基于状态(通过、失败、未运行)或代码的物理位置(类或项目)。播放列表提供了一种可自定义的测试分组方式。

要创建播放列表,首先在测试资源管理器中选择一个或多个节点。然后右键单击并从上下文菜单中选择添加到播放列表➤新播放列表选项。测试资源管理器的一个新实例出现了,测试树以您最初选择的节点为根。两个测试浏览器之间唯一的区别是新创建的浏览器在消息类型图标的右边有一个保存图标。如果希望在 Visual Studio 会话之间保留播放列表,单击保存图标将提示您输入文件名(带有。播放列表扩展)。一旦创建了播放列表,就可以在整个测试资源管理器的不同地方使用它。例如,您可以使用顶部的播放列表图标打开播放列表来查看测试(参见图 3-13 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-13

打开现有播放列表

或者您可以将测试添加到打开的播放列表中。通过从上下文菜单中选择添加到播放列表,完成与创建播放列表时相同的过程。然后选择要添加测试的播放列表的名称。如果你特别喜欢冒险,你甚至可以编辑。播放列表文件。播放列表信息存储为 XML 文档,其中播放列表中的每个测试都是一个节点。节点的属性之一是测试方法的完全限定名。

每次打开或创建播放列表时,都会打开一个新的测试资源管理器实例,只显示播放列表中包含的测试。新实例中的功能与原始测试资源管理器中的功能相同,也就是说,您可以运行测试、调试测试以及查看任何测试的结果。

智能测试

编写单元测试是一项有趣的脑力劳动。遵循红/绿/重构模式会产生一些单元测试,但是这些单元测试是为覆盖功能而设计的。它们可能不一定涵盖所有的边缘情况,特别是就传入的参数值的范围而言。开发人员需要考虑参数的所有不同可能性,然后为每个场景编写单元测试。技术上不具有挑战性,但很有必要。而且乏味。

IntelliTest 正好填补了这一空白。它始于微软的一个研究项目(代号为 Pex ),目标是通过对代码执行动态分析来生成单元类型,这意味着它检查被测试的代码并识别应该创建的单元测试。它试图确保代码中的所有路径都被测试覆盖。它确保参数的极端值(比如为一个对象传递一个空值)包含在测试中。

随着时间的推移,来自该项目的功能以 IntelliTest 的形式发布到产品中。IntelliTest 检查您的代码,查找以下项目:

  • 条件分支对于每个 switch 或 if 语句,参数值被标识为通过每个分支执行。

  • 断言确定断言通过和失败所需的参数值。

  • 超出范围对于某些类型的参数,发现包含空值的测试。

这些信息用于创建一套单元测试。而且,如果您愿意,可以将这些单元测试添加到您的项目中,无论是在现有的项目中还是在单独的项目中。

启动 IntelliTest 的过程很简单。右键单击要处理的方法中的任意位置,并从上下文菜单中选择 IntelliTest ➤运行 IntelliTest(图 3-14 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-14

IntelliTest 上下文菜单

Note

在 IntelliTest 菜单选项可见之前,需要满足几个条件。首先,IntelliTest 是 Visual Studio 2019 企业版的一项功能。专业版和社区版不会显示菜单项。此外,根据版本号,IntelliTest 不支持。NET 核心应用。微软最新说法是支持 IntelliTest 和。从 16.3 版本开始,NET Core 将被零星地包含在内,但目前还没有完整支持的时间表。

运行 IntelliTest 时,会生成项目,然后对方法进行动态分析。结果显示在它自己的窗格中,如图 3-15 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-15

IntelliTest 探索结果窗格

从 IntelliTest 运行的结果来看,生成了两个单元测试。输出中的第一列包括被测试对象的构造函数(在本例中,是一个 Order 对象)。正在测试的方法(AddOrderLine)有两个参数,一个产品和一个数量。对于这些参数中的每一个(标记为 product 和 quantity ),在显示屏上都有一列,您可以看到作为测试的一部分传入的值。接下来的三列处理运行测试的结果。在第一行中,测试运行时生成了一个异常(NullReferenceException)。还有一条错误消息,包含与异常相关的消息。在第二行中,测试通过,标有 result(target)的列显示成功执行后目标对象的状态。

以这种方式运行 IntelliTest 不会生成单元测试的代码并将其添加到项目中。相反,测试完全在内存中生成和执行。如果您选择了一行,图 3-15 中窗格右侧的部分显示了生成的单元测试的代码。也可以这样发现:

[TestMethod]
[PexGeneratedBy(typeof(OrderTest))]
[PexRaisedException(typeof(NullReferenceException))]
public void AddOrderLineThrowsNullReferenceException886()
{
    Order order;
    order = new Order((Customer)null);
    order.Id = 0;
    this.AddOrderLine(order, (Product)null, 0);
}

您看到的一些内容对于单元测试来说是完全正常的。但是该方法是如何调用的有点不寻常,值得进一步研究。

TestMethod 属性是如何将一个方法指示为单元测试的。但是另外两个属性 PexGeneratedBy 和 PexRaisedException 是非典型的。如果您将这个测试转移到您的项目中,它们是不必要的。PexGeneratedBy 属性用于跟踪哪些测试属于哪个类。PexRaisedException 属性指示运行测试导致引发 NullReferenceException。IntelliTest 使用它们来帮助确定在 IntelliTest 浏览结果窗格中显示哪些方法以及何时显示。

在方法本身内部,创建目标对象并将属性设置为适当的值。然后调用一个名为 AddOrderLine 的私有方法。注意,传递了目标对象以及 AddOrderLine 的参数。

AddOrderLine 方法是内部生成的方法。代码如下:

[PexMethod]
public void AddOrderLine(
   [PexAssumeUnderTest]Order target,
   Product product,
   int quantity
)
{
   target.AddOrderLine(product, quantity);
}

这种方法从表面上看似乎没有增加多少价值。在这种情况下,这可能是不必要的。除了用于帮助 IntelliTest 浏览结果窗格的属性(PexMethod 和 PexAssumeUnderTest)之外,还显示相应的信息。但是,作为一种良好的实践,将对测试方法的调用放在一个单独的方法中会很有用。例如,很可能会有许多对 AddOrderLine 的调用。其中的每一个都将涉及检查调用是否导致 OrderLine 被添加到 Order 对象(由目标变量表示)。可以将断言添加到这个方法中对 AddOrderLine 的调用下面,现在对 AddOrderLine 的所有调用都包含该断言。减少了每个单元测试中需要出现的重复断言的数量。当测试是手工编写的时候,这种类型的单元测试重构也会被执行。IntelliTest 只是从最初的代码生成开始。

图 3-15 顶部是一个工具栏,帮助您在结果窗格中执行常见功能。左侧的下拉列表包含智能测试运行中包含的所有方法的列表。虽然在前面描述的示例中,IntelliTest 是通过在方法内右击来执行的,但是如果您改为在类中右击,那么 IntelliTest 将会为该类中的所有公共方法(包括构造函数)生成测试。从下拉列表中选择一种方法,会显示为该方法生成的测试。

紧挨着下拉列表的右边是一个图标(一个带箭头的正方形),它将您带到被测方法的定义。接下来是一个运行探索的图标(绿色三角形)和一个停止探索的图标(红色正方形)。

接下来的四个图标用于执行从生成的结果中选择的测试的功能。如果您选择一个或多个测试,则至少会启用第一个图标。这个图标(看起来像软盘)用于将测试保存到项目中。该项目的命名细节基于下面描述的 Create IntelliTest 设置。

其他三个图标是有选择地启用的。对于图 3-15 中的视图,只有一种可能性,那就是允许图标(四个中的第三个)。如果您选择了由于空引用异常而失败的单元测试,则“允许”图标将被启用。当您单击该图标时,表示让该方法引发异常是可接受的结果。这会将 PexExceptedException 添加到本节前面描述的 AddOrderLines 方法中(该方法是测试的一部分,而不是测试中的方法)。现在,当出现异常时,它被视为通过测试。

要访问其他两个图标,需要稍微绕一下工具栏。在右边,有一个警告图标。它直观地描述了在运行浏览过程中发现了多少个警告。但是当您单击它时,您会将 IntelliTest 窗格的内容更改为类似图 3-16 的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-16

IntelliTest 的警告

作为探索过程的一部分,IntelliTest 对每个方法进行了详尽的遍历。但是这种遍历不仅仅是沿着可能存在的任何分支或循环。它查看不同变量的数据类型来确定边界情况。它还考虑如何创建对象的实例,如果合适的话,将不同范围的参数传递给构造函数。如果该方法具有将接口作为数据类型的参数,则它会查看项目中的任何具体实现。IntelliTest 可能会考虑各种各样的事情,如果有问题,甚至有机会改进测试过程,就会生成警告。

在图 3-16 中,有三类警告可见。如果您单击第一个,您可以看到该特定警告的详细信息。在此实例中,IntelliTest 找不到订单对象的工厂。在细节部分的右边,是 IntelliTest 提出的工厂。在这一点上,其他两个图标开始发挥作用。

选择警告后,之前禁用的两个图标将被启用。第二个图标用于修复警告。在这种情况下,它将为已经发现的单元测试创建一个测试项目,并将提议的工厂添加到执行流中。第二个图标用于抑制警告。在这种情况下,不会添加任何修复,警告也不会在以后的探索中出现。

使用 Fix 选项要考虑的一件事是,一旦工厂被添加到测试项目中,您就能够调整它以适应您的类的具体情况。这里的“调整”意味着改变实现。向工厂添加参数。将属性初始化为不同的值。所有选项都是开放的,因为工厂只是测试类中的一个方法,您所做的任何更改都将被集成到单元测试执行流程中。

测试项目设置

到目前为止,在关于 IntelliTest 的讨论中,已经有许多函数接受了生成的单元测试,并在实际项目中展现出来。例如,修复上一节末尾的警告将创建一个单元测试项目(如果它还不存在)并添加一个包含工厂方法的适当命名的类。因为代码是生成的,所以您可以定制以满足您的需求。

然而,生成代码的诀窍是用最少的努力让它很好地适应您的项目。让您的测试项目名称符合您的项目名称标准。创建与红/绿/重构过程中使用的单元测试类相同的类。幸运的是,Visual Studio 2019 提供了一种机制,为生成的测试的各个部分定义命名模式。

用于定制命名的屏幕通过图 3-14 所示上下文菜单中的智能测试➤创建智能测试菜单项访问。结果是如图 3-17 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-17

创建智能测试对话框

起点是选择您正在使用的测试框架。Visual Studio 2019 提供的唯一选项是两个版本的 MSTest。如果您正在使用另一个测试框架,您可以从 Visual Studio Marketplace 中找到支持 xUnit 和 NUnit 的扩展。如果您安装了一个(或多个)扩展,您可以从下拉列表中选择您想要的框架。

下一个字段,Test Project,用于指定您是否想要创建一个新的项目,或者将测试添加到一个现有的项目中。您将在下拉列表中看到的唯一项目是那些匹配所选择的测试框架的项目。

剩下的四个字段用于定义命名测试项目、名称空间、测试类和测试方法的模式。您会注意到有四个占位符:项目、名称空间、类和方法。通过用方括号将占位符括起来,将它们与名称的静态部分区分开来。您可以在任何字段中使用任何占位符。当您根据需要配置好框架和名称后,单击 OK。这会生成测试项目(如果尚未选择现有的),并添加脚手架代码来运行在使用 IntelliTest exploration 结果窗格中的保存功能时创建的测试。

代码覆盖率

单元测试的一个更常见的度量是代码覆盖率。代码覆盖率的目标是描述单元测试覆盖的代码的百分比。虽然对于什么是足够的百分比以及应用的哪些部分应该或不应该包括在计算中有许多观点,但是您需要知道的是如何为您的项目生成该数字。

您开始在 IntelliTest exploration 结果窗格中看到与代码覆盖率相关的信息(图 3-16 )。在工具栏的正下方有一个条,粗略地显示了覆盖率,以及发现和覆盖的块的数量(9/22 块意味着 22 个发现的块中有 9 个被单元测试覆盖)。

然而,为了生成整个项目的代码覆盖率信息(或者在一个更细粒度的层次上),需要使用测试资源管理器。要在收集代码覆盖率统计数据的同时运行您的测试,单击运行图标右侧的下拉菜单,并选择分析所有测试的代码覆盖率菜单选项(图 3-18 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-18

生成代码覆盖率统计数据

在这一点上,这个过程与运行测试非常相似。项目构建并执行测试。不同之处在于,一旦测试运行完成,就会出现一个不同的窗格,它包含代码覆盖信息,而不是测试结果。在图 3-19 中可以找到一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-19

代码覆盖率结果窗格

图 3-19 中显示的代码覆盖率信息是代码覆盖率按程序集、命名空间、类和(图中不可见)方法的层次分解。对于每个不同的级别,列出了覆盖和未覆盖区块的数量,以及各自的百分比。

块不是唯一可以用来度量代码覆盖率的单位。Visual Studio 还支持使用行数作为代码覆盖率的度量单位。换句话说,您可以查看代码覆盖率分析,它显示了基于作为测试的一部分而执行的代码行数的数字和百分比。

为什么你会选择一种度量单位而不是另一种?这实际上取决于您试图从代码覆盖率中提取的信息。以块为单位查看覆盖率并不能说明块的实际大小。在图 3-19 中,有 24 块未被覆盖。那是 24 行逻辑还是 2400?没有办法知道。对于一些经理来说,这种差异是代码质量的潜在盲点。

没有必要为了从块到行再返回而重新运行代码覆盖分析。相反,右击代码覆盖率结果区域的标题,然后选择“添加/移除列”。图 3-20 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-20

添加和移除代码覆盖率列

你可以看到出现在图 3-19 中的四列都被选中。如果您想以行的形式显示代码覆盖率,那么检查末尾有(行)的任何或所有列。当您单击“确定”时,它们现在将出现在代码覆盖率结果中。

虽然代码覆盖率数字很好,可以让您了解代码库中可能需要更多测试的区域,但对于一些开发人员来说,数字本身并不能真正提供足够的粒度来确定需要编写哪些测试。为了帮助实现这一点,Visual Studio 2019 中的代码覆盖率能够根据代码行是否作为单元测试的一部分执行来为代码行着色。

若要打开和关闭线条的颜色,请在“代码覆盖率结果”窗格的工具栏上,单击紧靠“移除”按钮左侧的“显示代码覆盖率颜色”按钮。当打开着色时,单个代码文件的线条会变暗,如图 3-21 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-21

代码颜色的源代码

虽然实际的颜色在黑白图像中不明显,但是单元测试覆盖的代码行是淡蓝色的,而没有覆盖的代码行是浅红色的。这允许您针对尚未解决的代码进行测试。这并不是一个完美的方法(毕竟,单个测试覆盖了代码并不意味着代码不包含 bug),但是这是一个查看您的测试可能存在不足的方便方法。

合并结果

“代码覆盖率结果”窗格仅显示代码覆盖率分析过程的最新运行结果。然而,如果您试图确定所有测试所提供的覆盖率,这可能是不够的。为了覆盖所有的代码,一些单元测试可能需要多次运行(例如,每次运行之间的配置信息不同)。

在“代码覆盖率结果”窗格中,您可以将多个测试运行的结果合并成一组代码覆盖率结果。首先,如果您想查看以前分析的结果,它们位于工具栏左侧的下拉列表中。图 3-22 显示了包含几个先前执行的测试的下拉列表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-22

合并代码覆盖率分析结果

当您选择出现的任何项目时,该分析的结果会出现在窗格中。但是,请注意,该列表只包括自 Visual Studio 启动以来发生的分析运行。如果您想包含之前的结果,您需要导入结果,这个过程将在下一节中描述。

但是我们正在考虑将不同分析的结果合并成一份报告的能力。为此,单击工具栏中的合并结果图标(在图 3-22 中圈出)。这将启动如图 3-23 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-23

合并测试运行对话框

选择要合并到单个报告中的不同运行,当您单击“确定”时,它们将与“代码覆盖率结果”窗格中可见的结果相结合。

合并不同运行结果的主要限制是,它们都需要针对相同版本的代码执行。仔细想想,这是有道理的。结果中有信息将测试执行与被执行的代码行相匹配。如果您对正在测试的代码进行了更改,那么先前的结果可能不再能够与代码匹配。因此任何分析对于确定代码覆盖率都不再有效。如果您合并不同版本的结果,它不会阻止信息显示。它们将在两个不同的顶级节点下独立显示,而不是合并到一个层次结构中。

管理结果

无论结果是否合并,都可以根据需要导出并发送给其他人。首先,分析结果本身存储在一个名为 TestResults 的文件夹中,与您的解决方案文件处于同一级别。在 TestResults 文件夹中,每次运行都有一个单独的文件夹。文件夹名是烦人的 GUIDs(全局唯一标识符),所以没有办法根据名称来判断运行来自哪里。只有日期可以指引你。

该文件夹中有一个.coverage文件,包含二进制格式的测试结果。也就是说,你可以在记事本中打开它,但不要指望从中获得任何有用的信息。如果您需要与他人分享分析运行的结果,您只需向他们发送.coverage文件。然后他们可以使用 Import Results 按钮(弯曲的箭头图标,见图 3-22 中合并结果图标的左边)将结果导入到代码覆盖率结果窗格中。

如果您喜欢更易于阅读的结果形式,您可以使用 Export Results 按钮(下拉列表右侧的图标,烧瓶是它的一部分)来实现这一点。单击时,您会看到标准的保存文件对话框,保存文件的唯一格式是一个.coveragexml文件。这个文件包含与.coverage相同的信息,但是是 XML 格式的,所以人类和其他工具更容易理解。

用户化

至此,代码覆盖率已经使用了默认设置。从实用的角度来看,这意味着分析过程考虑了作为解决方案一部分的所有程序集。对于许多情况,这种默认行为已经足够了。但是,如果您想更具体地了解包含或排除的工件,Visual Studio 2019 提供了一种可以使用的机制。

自定义信息保存在一个.runsettings文件中。这只是一个扩展名为.runsettings的 XML 文件。若要创建文件,请向解决方案中添加一个新项。这个项目是一个 XML 文件,只要扩展名是.runsettings,它的名称可以随意设置。Visual Studio 2019 中没有此文件的模板。如果您想要一个基本布局,请考虑以下几点:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <RunConfiguration>
    <MaxCpuCount>1</MaxCpuCount>
    <ResultsDirectory>.\TestResults</ResultsDirectory>
    <TargetPlatform>x86</TargetPlatform>
    <TargetFrameworkVersion>Framework45
    </TargetFrameworkVersion>
    <TestSessionTimeout>10000</TestSessionTimeout>
  </RunConfiguration>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage"
         uri="datacollector://Microsoft/CodeCoverage/2.0"
         assemblyQualifiedName=
"Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
        <Configuration>
          <CodeCoverage>
            <ModulePaths>
              <Exclude>
                 <ModulePath>.*MyUnitTestFramework.*
                 </ModulePath>
              </Exclude>
            </ModulePaths>
          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
  <TestRunParameters>
    <Parameter name="testUserId" value="Admin" />
    <Parameter name="testPassword" value="P@ssw0rd" />
  </TestRunParameters>
</RunSettings>

这里的大多数值都是代码覆盖率分析的默认设置,因此可以在不改变行为的情况下删除这些值。可能最常定制的两个元素是 ModulePaths 和 TestRunParameters。

ModulePaths 元素可以选择包含 Exclude 和 Include 子元素。Exclude 元素是 ModulePath 元素的集合,其中的内容是用于指定不包括在代码覆盖率分析中的程序集的正则表达式。Include 元素是 ModulePath 元素的集合,其中的内容是一个正则表达式,用于指定要包含在分析中的程序集。

ModulePath 元素并不是指定处理中包含或排除的元素的唯一方式。还提供了

  • CompanyName 使用程序集的 company 属性。

  • PublicKeyToken 使用程序集的公钥标记。这假定程序集已经过签名。

  • Source 通过源文件的路径查找程序集。

  • Attribute 寻找用特定属性修饰过的元素。必须使用属性的全名,包括类名的“属性”部分。

  • 函数找到具体的方法。这个元素的内容是一个正则表达式,它必须与函数的完全限定名相匹配。此实例中的完全限定包括命名空间、类名、方法名和参数列表。例如,在类OrderTest和名称空间OrderWebApp.Library.Test中带有签名public void Lines_AddOrderLine_LinesCountIsCorrect(int orderNumber)的方法需要一个匹配以下字符串的正则表达式:

OrderWebApp.Library.Test.OrderTest.Lines_AddOrderLine_LinesCountIsCorrect(int)

TestRunParameters 元素是参数元素的集合。每个参数都是一个名称/值对,用于提供可以在测试执行中使用的参数信息。在这方面,集合看起来像许多其他名称/值对,如 AppSettings,用于参数化执行。诀窍是现在检索当前值并在测试中使用它。对于 TestRunParameters,这些值通过 TestContext 对象中的属性列表显示出来。MSTest 中的以下示例提供了一个说明:

private string _testCompanyName;
private string _testEmployeeName;

[ClassInitialize]
public static void TestClassInitialize(TestContext context)
{
   _testCompanyName =
      context.Properties["testCompanyName"].ToString();
   _testEmployeeName =
      context.Properties["testEmployeeName"].ToString();
}

TestContext 可作为用于初始化该类的方法中的一个参数。是 ClassInitialize 属性指示在类初始化时调用哪个方法。在该示例中,使用参数名称作为索引从 Properties 属性中提取值。然后,该值存储在类级别的变量中,以便所有方法都可以使用它们。

您可以在您的项目中有多个可用的.runsettings文件,并选择一个用于测试执行期间。要选择所需的文件,请在测试资源管理器中单击齿轮旁边的下拉按钮。图 3-24 显示了出现的选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-24

选择运行设置文件

“选择设置文件”选项启动“打开文件”对话框,允许您选择所需的设置文件。一旦您使用了某个特定的文件,它就会被添加到与齿轮图标相关联的选项列表中,以便您下次需要它时更容易使用。图 3-25 显示了相同的菜单,但是之前已经选择了一个设置文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-25

再次选择. runsettings 文件

在齿轮图标下有几个其他选项,可能适用于您的情况。通过在菜单中选择选项,可以打开和关闭每个选项。

构建后运行测试使得测试在任何成功的构建后立即运行。如果你有一个有很多测试的应用,并且你是在有规律的基础上做构建,这可能是有问题的。构建完成和调试启动之间的延迟可能令人抓狂。然而,一个简单的解决方案是创建一个.runsettings文件,该文件针对与您正在做的工作相关的测试。现在延迟减少了,如果任何测试失败,它们都与您正在做的工作直接相关。

AnyCPU 项目的处理器架构允许您设置标记为“Any CPU”的项目的处理器架构是使用 x86 还是 x64 运行。这与配置管理器中的设置不同,因为该值只影响测试,而不会影响整个项目或解决方案。

保持测试执行引擎运行当 Visual Studio 运行单元测试时,它们实际上是在一个单独的进程中执行的。这允许 Visual Studio 在单元测试执行期间保持交互性和稳定性。该选项用于确定在测试执行完成后,您是否希望保持该单独的进程运行。这是速度(下一次运行单元测试时,您不会花费公认的很少的时间来启动单独的进程)和资源(测试执行进程在运行时使用内存)之间的权衡。Visual Studio 2019 的默认设置是打开此设置。

并行运行测试这个选项提供了一个机会,通过并行运行测试来加速测试的整体执行。首先,为了让这成为一个优势,您需要在一台拥有多个内核的机器上运行。一旦满足该要求,如果该选项已经打开,那么将在每个内核上启动单独的测试执行过程。然后每个过程将被给予一个测试工件来工作。通常,测试工件是一个包含单元测试的程序集。运行这些测试并将结果传回 Visual Studio 将取决于流程。

当涉及到并行测试时,需要理解一些注意事项和警告。您可以配置测试中使用的内核数量,从而确定并行化的程度。这是通过.runsettings文件中的RunConfiguration元素完成的。以下元素确保使用的内核不超过两个:

<RunConfiguration>
    <MaxCpuCount>2</MaxCpuCount>
</RunConfiguration>

警告来自于你如何组织单元测试的形式。为了并行工作,测试必须彼此完全独立。完成这项工作的难易程度在很大程度上取决于您的环境。从经验上来说,当单元测试使用一个共享的资源,比如一个数据库时,与并行测试最大的冲突就来了。所以如果你计划并行化你的测试,记住这一点。这将节省你调试挫折的时间。

实时单元测试

Visual Studio 2019 Enterprise 包括一项技术,允许您的单元测试实时执行。换句话说,当您对代码进行更改时,涵盖您正在更改的代码的单元测试会得到执行。这为您编写的代码提供了即时反馈,无论您所做的更改是否会对代码先前的功能产生负面影响。这项技术被称为实时单元测试。活单元测试不仅向您显示基于您的更改哪些测试会通过或失败,它还更新了代码覆盖信息。

默认情况下,不启用实时单元测试。正如您所想象的,连续运行单元测试会对性能产生影响。虽然 Visual Studio 能够智能地处理每次更改时需要执行的测试,但是测试仍然在运行,这确实会占用 CPU 周期。

现场单元测试的起点是任何测试项目。两者都有人支持。NET 框架和。NET 核心测试项目。对于测试框架,实时单元测试支持 MSTest、NUnit 和 xUnit.net。对于下面的例子,我们将使用本章前面创建的测试项目。

如前所述,默认情况下,没有启用实时单元测试。您需要手动启动它。这是通过 Visual Studio 中的“测试➤现场单元测试➤”开始菜单项完成的。当您开始实时单元测试时,第一步是执行您当前所有的单元测试。这与您手动运行所有测试是一样的。测试完成后,将出现测试资源管理器,向您显示结果。同样,这与您手动运行测试是一样的。图 3-26 展示了当所有测试都通过时的测试浏览器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-26

通过测试的测试资源管理器

当你查看你的代码时,你会发现与实时单元测试的区别。图 3-27 显示了典型方法的样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-27

一种在实时单元测试中通过单元测试的方法

不同之处在于绿色的复选标记出现在每一行和方法的旁边,这些方法包含了所有测试。复选标记(好像绿色还不够)表示测试成功。但是,正如章节标题所暗示的,这是现场单元测试。要查看运行中的活动零件,请更改将数量更新为自增量的行中的自减量运算符。现在单元测试失败,复选标记变为红色 X,如图 3-28 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-28

一种实时单元测试中单元测试失败的方法

X 不仅仅是单元测试失败的指示器。如果您单击其中一个 X,您可以看到覆盖该行代码的特定测试,以及哪些测试通过了,哪些测试失败了。图 3-29 显示了点击时可用的信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-29

现场单元测试详细信息

该信息包括一个测试列表,这些测试覆盖了与您单击的 X 相关联的行。每个测试旁边的 X 和复选标记表示测试是通过还是失败。如果双击其中一个测试,将打开包含该测试的文件,并将光标放在该测试方法上。

除了关于测试及其状态的信息之外,您还可以运行所有的测试或调试所有的测试。如果您选择了其中一个测试,您还可以运行或调试那个单独的测试。

如前所述,如果您使用实时单元测试,可能会对性能产生影响。有许多设置可以帮助缓解您可能遇到的任何问题。要获取设置,请单击“工具”“➤选项”菜单项,并导航到左侧的“实时单元测试”节点。应看到图 3-30 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-30

实时单元测试设置

这些设置中的大部分与帮助您确保保持性能水平直接相关。使用图 3-30 中的选项,可以配置以下区域:

如果考虑到 Visual Studio 正在做的事情和所涉及的 CPU 工作,那么在构建和调试解决方案时暂停实时单元测试是有意义的。至于调试,除非您计划在调试时对应用进行更改,否则没有必要运行实时单元测试。

当电池电量低于某个百分比时,再次暂停实时单元测试,这有助于延长电池寿命。

打开解决方案时启动实时单元测试这决定了当解决方案完成加载时,是否自动启动实时单元测试。

生成调试符号和 XML 文档。作为实时单元测试过程的一部分,应用不断地自我编译。此标志指示应用是否应生成调试符号(即。pdb 文件)和 XML 注释。

作为调试符号生成的一部分,您还可以指定存放持久化数据的目录。同样,如果您想删除任何已经持久化的数据,有一个按钮可以做到这一点。这将清理数据,允许在下次需要时重新生成数据。

在测试级别,您可以使用 Testcase Timeout 来限制单元测试在被标记为失败之前可以运行的毫秒数。此外,还有一个选项来定义将要运行实时单元测试的进程的数量。

最后一个基于性能的选项用于设置用于实时单元测试的内存上限。同样,这是在编码时更新的 X 或复选标记的响应性和 Visual Studio 的性能之间的权衡。

最后,还有编译过程在实时单元测试中使用的日志记录级别。通过编译生成的信息出现在“实时单元测试输出”窗口中。

摘要

编写和执行测试,无论是手动完成还是作为实时单元测试的一部分,都是本章顶部描述的红/绿/重构过程的核心功能。它几乎是红色和绿色部分的核心。本章介绍了 Visual Studio 在编写、运行和受益于应用中的单元测试方面提供的支持。

在下一章中,你将学习开发三元组的第三条腿:重构。这是获取有效代码(根据单元测试)并通过精简、重组和重构来改进它的过程。

四、重构代码

在第三章,“单元测试”中,红/绿/重构作为一种开发范例被引入。通过单元测试,有可能实现三人组的红色和绿色部分。讨论中遗漏的是重构,幸运的是,对于从前到后阅读书籍的人来说,重构是本章的主题。在简要描述了重构的功能和形式之后,您将了解 Visual Studio 2019 带来的许多不同工具来帮助完成这一步。

什么是重构

首先,让我们把重构看作一个概念,而不是用来实现它的工具。简单来说,重构的目的是让你的代码更干净;6 个月后,当你通读你的申请,想知道是谁写的时,更容易跟踪;并且当您将代码从一个地方复制到另一个地方时,更不容易出现由重复的 bug 引起的错误。随着时间的推移,所有这些都是潜移默化进入代码库的问题。这也是重构想要帮助解决的问题。

重构背后的思想是在不改变功能的情况下改变代码的结构。考虑一个简单的例子:

decimal CalculatePay(decimal hours, decimal? rate)
{
   return hours *
      rate.HasValue ? hourlyRate.Value : 15.0d;
}

此方法通过小时数乘以小时工资率来计算工资。但是,计算中使用的三元运算包括 15 的文字值。从代码中看不出 15 到底代表什么。事实证明,这是最低工资。但是六个月后你真的会记得吗?还是 2 年?或者接手这段代码的初级开发人员看的时候?可能不会。解决方案是重构代码,以便将 15 定义为常数,如下所示:

const decimal minimumWage = 15.0d;
decimal CalculatePay(decimal hours, decimal? rate)
{
   return hours *
      rate.HasValue ? hourlyRate.Value : minimumWage;
}

至少,现在代码的意图更加清晰了。这也意味着,想要改变最低工资的人只需要在一个地方这样做(在那里定义了常数变量)。如果在某个时候,最低工资的计算变得更加复杂(因此函数是一个更好的实现选择),代码已经设置好了,只需做最小的修改就可以处理。

您刚刚重构了代码。代码的结构已经改变,但是公开的功能没有改变。这是重构背后的关键思想。这也是编写单元测试如此重要的原因。在红色/绿色/重构工作流中,在执行重构之后,单元测试保持绿色是非常关键的。这就是为什么您知道功能保持不变,即使实现被修改了。如果您重构了没有单元测试的代码,那么您确定功能是否相同的能力仅限于运行手动测试。或者只是目测。拥有单元测试覆盖要好得多(也不那么伤脑筋)。

您可能会问自己,为什么需要特殊的工具来执行这样的重构。而且,在这种特殊情况下,可能不值得。然而,正如你将会看到的,本章所涉及的更复杂的重构从自动化中受益匪浅。这并不是说你不能用手做。但是如果你使用工具的话,会更容易,更不容易出错。

可用重构

重构工具的质量基于几个标准。首先是可用的不同重构的数量,不仅仅是重构的数量,还有它们的有用性。一年只看一次的重构不如一天看多次的有用。第二个标准是工具能够识别常见的重构模式,并以一种容易发现的方式呈现重构选项。

Visual Studio 2019 满足这两个标准。在深入研究可用的不同重构之前,让我们看看在开发过程中的任何一点可用的重构是如何出现的。

表面重构选项

Visual Studio 2019 使用其快速操作菜单(俗称灯泡图标)进行各种与编写代码相关的通知。图 4-1 显示了灯泡图标的一个位置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-1

灯泡图标

出现在编辑器中某一行的槽中的灯泡图标是您可能对该行感兴趣的指示器。当有人对你的代码提出建议时,它就会出现。这可能是语法错误,或者是风格上的改进,或者是您有一个未定义的方法或者数据类型未被语句引用。快速行动涵盖了许多不同的情况。而且,与手头的主题相关,当有可能的重构可用时,它就会出现。

灯泡是下拉菜单的一部分。当您单击灯泡右侧的三角形时,将显示该行感兴趣的项目列表。列表中显示的项目对上下文非常敏感。在图 4-1 中,有四种不同类型的重构和一个选项来抑制在生产线上发现的问题。但是移动到其他行,甚至是你放置光标的地方,可以完全改变列表。

您也可以使用其他技术来访问灯泡选项。如前所述,灯泡功能的实际名称是快速动作。如果右键单击该行,会出现一个标记为“快速操作和重构”的上下文菜单选项。选择该选项会得到一个类似的列表,如图 4-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-2

使用快速动作上下文菜单选项

您还会偶尔在代码的某些部分看到模糊的指示器。将鼠标悬停在指示器上,会显示灯泡的另一种变化,包括一组可能的重构,如图 4-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-3

潜在代码修复菜单

在图 4-3 中,微弱的指示器是出现在参数名称开头下方的三个点。这个界面与其他界面的最大区别在于,解决问题的选项并不容易获得。相反,您可以单击“显示潜在的修复”来获取问题列表。这种具体情况的列表如图 4-4 所示。不同的情况会产生不同的列表,虽然基本流程大致相同。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-4

对建议的代码更改采取快速行动

在图 4-4 中需要注意的一点是出现在项目列表右边的代码块,这一点到现在还没有被看到。当您将鼠标悬停在项目上时,在适当的时候,右侧会出现一个弹出按钮,其中包含将要进行的更改的示例,使用当前上下文作为起点。明确地说,当通过选择项目进行的更改不需要开发人员的任何进一步输入时,预览是合适的。例如,更改签名选项不包括代码预览,因为选择该选项会显示一个对话框,允许您重新组织方法中的参数。

如果弹出按钮中的示例更改不够,请单击弹出按钮左下角的预览更改链接。这导致类似图 4-5 的对话框出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-5

预览更改对话框

图 4-5 中的预览更改对话框分为两部分。顶部是一个分层视图,其中包含了您的解决方案中提议的更改将发生的所有位置。它被分解为项目、文件和代码行。在底部,建议的更改会出现,并突出显示这些更改。当您单击顶部树中的不同节点时,底部的代码窗口会发生变化,以显示相应的建议。要进行更改,请单击“应用”按钮。

灯泡图标并不是开始重构过程的唯一方式。“编辑”菜单下有一个“重构”菜单项,但是其中的选项不是上下文敏感的。它们也不像灯泡选择那样细致。开始重构过程的第二种可能更有用的方法是使用键盘快捷键。当您面对灯泡图标时,可以使用 QuickActionsForPosition 命令触发选项列表显示,该命令的默认值为 Ctrl+…通过使用这个,您可以触发重构,而无需将手离开键盘。开始重构的第三种方法是右键单击灯泡旁边的代码行。有一个快速动作选项,其作用与键盘快捷键相同。在这两种情况下,可用的选项都是上下文相关的。

现在,开始重构过程的机制已经出来了,让我们看看 Visual Studio 2019 提供的不同重构。

重构

在本节中,将详细介绍 Visual Studio 2019 提供的不同重构选项。这个列表令人惊讶地长(嗯,也许令人惊讶……它确实随着每个版本变得越来越长),并且它涵盖了许多经常使用的重构。话虽如此,有些人最终会将这个列表与 ReSharper 等第三方工具进行比较。最终的选择是个人的选择。如果第三方工具有你每天使用十次的精确重构,那么它值得你为它付出的代价。但是,确切了解 Visual Studio 2019 必须提供什么可以帮助您做出决定。

Note

如果您使用的是第三方重构工具,某些菜单描述可能与您的 Visual Studio 实例不匹配。这是因为一些工具覆盖了与重构相关的各种菜单(工具栏和上下文菜单)。

本节的其余部分将介绍 Visual Studio 提供的许多不同的重构。虽然按字母顺序排列看起来很合理,但是你找到某个特定名字的能力取决于你是否知道这个名字。而且有些名字不一定显而易见。因此,它们大致分为三类:类声明、编码结构和文件组织。希望这能帮助你更容易地找到此刻对你重要的东西。

类声明

在这一节中,我们将描述与类和方法的定义相关的重构。粗略地说,这转化为影响由类公开的方法和属性的变化。

更改方法名称

更改方法名重构名副其实。它用于更改方法的名称。更准确地说,它允许您在声明方法时对其进行重命名,并将更改传播到代码中使用该方法的每个地方。

这种重构的起点不是一个灯泡图标。相反,您可以通过就地修改名称来更改方法名称。然后当你触发快速动作菜单时,你会得到一个类似图 4-6 的选项。或者,您可以使用右键单击方法名称时出现的上下文菜单中的 Rename 选项到达相同的位置。或者在光标位于方法名称上时使用 Ctrl+R 键盘命令来启动该过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-6

更改方法名称

列表中的第一个选项是将“Greeting”(方法的旧名称)重命名为“NewGreeting”(方法的新名称)。在弹出菜单中,代码以红色显示旧代码,以浅绿色显示新代码。如果选择此选项,则在解决方案中调用 Greeting 方法的每个地方,该方法的名称都将从 Greeting 更改为 NewGreeting。

完全清楚地说,只有当编译器能够确定 Greeting 正在被调用时,更改才会发生。重要的区别是编译器必须能够识别调用。重构过程无法识别 Greeting 方法的任何运行时调用。只有编译时调用被重命名。

例如,考虑一个场景,其中名称“Greeting”嵌入在配置文件的字符串中。当应用运行时,将提取配置值,并使用反射调用该方法。重构过程检测不到这种用法,因为没有对存储在配置文件中的方法名进行编译时检查。

将匿名类型转换为类

匿名类型在。网了好一阵子。下面是一个例子:

var tempType = new {FirstName="Kyle", LastName = "Johnson"};

匿名类型非常有用,但是它们的特点是只能在本地环境中使用。不能从一个方法返回它们,也不能将它们作为参数传递给另一个方法。如果这成为一个限制,有一个重构允许您将匿名类型转换成一个类。将光标放在new关键字上,启动快速操作。您将看到如图 4-7 所示的显示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-7

将匿名类型转换为类

在这里你可以看到一个新的类被创建了。该类用 internal 的访问修饰符标记,该类的构造函数接受匿名类型中包含的字段。同样,Equals 和 GetHashCode 方法也有一个自定义实现。这些是必需的,以便两个匿名类型的比较与该类的两个实例的比较保持一致。

在 Get 方法和属性之间转换

在检索一个类的属性值时,可能会对 get 方法和属性的正确使用进行长时间的讨论。但首先,请考虑以下代码片段:

public string Name { get; set; }

private string name;
public string GetName()
{
   return name;
}

首先,您有一个名称属性,使用自动属性getset函数。第二,你有一个GetName方法,返回一个私有变量的值,这个私有变量(大概)被设置在类中的某个地方。甚至可能有一个SetName方法允许设置名称的值。

这些方法之间的差异(以及“正确”方法之间任何分歧的来源)本质上是语义上的。其思想是一个性质应该是幂等的。如果你得到一个属性值,类的状态不应该以任何方式改变。如果你设置了一个属性的值,唯一改变的就是这个属性。另一方面,调用方法不应该是等幂的。仅仅调用一个方法来修改包含该方法的类中的属性并不是不合理的。

这种重构的目的是允许您在不同的模态之间切换。将光标置于 Name 属性中,并调用快速操作。图 4-8 是你可能会看到的一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-8

用方法替换属性

您可以看到 Name 属性将被删除,并替换为 GetName 方法和 SetName 方法,这两个方法都以新值作为参数。

这种重构还包括相反的变化,即从 GetName 方法转移到 Name 属性。起点仍然是将光标放在方法名上。但是现在启动快速行动会显示如图 4-9 所示的选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-9

用属性替换方法

在这里,您可以看到 GetName 方法被移除,并替换为 lambda 形式的属性声明。如果你的类中也有一个 SetName 方法,那么重构预览会有些不同(见图 4-10 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-10

用一个属性替换两个方法

现在这两个方法都被删除了,取而代之的是一个包含 getter 和 setter 定义的属性。

将局部函数转换为方法

这种特殊的重构有点深奥,也就是说,它在大多数开发环境中很少出现的场景中发挥作用。它接受一个局部函数,并将其转换为私有方法。

让这变得深奥的是局部函数的定义。它是一个作用于特定方法的函数。考虑以下代码:

public  static decimal CalculateTotalCost(decimal price,
   decimal federalTax = 0,
   decimal stateTax = 0,
   decimal cityTax = 0)
{
   decimal TotalTax(decimal tax1, decimal tax2, decimal tax3)
   {
      return tax1 + tax2 + tax3;
   }
   return price * (1 + TotalTax(federalTax, stateTax,
      cityTax));
}

在这段代码中,CalculateTotalCost方法中定义的TotalTax函数是一个局部函数。该功能仅在CalculateTotalCost范围内可用。

重构用于将局部函数从方法中取出,并将其移到类级别。这允许该类中的其他方法使用它。它还被赋予了一个 private 访问修饰符,这样它只能在类中使用。图 4-11 显示了建议的代码变更。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-11

将局部函数转换为方法

封装字段

从深奥的到更常见的重构,封装字段用于修改公共字段,使其成为公共属性。考虑以下代码:

public double Age;

这是公共字段的声明。如果您调用变量上的快速操作,您将看到图 4-12 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-12

封装字段

您可以看到提出了两项更改。第一,公有领域改为私有领域。其次,添加了一个名为 Age 的属性(使用 lambda 声明)。关于这些变化,有两点需要注意。首先,年龄属性不一定放在声明公共字段的地方。相反,Visual Studio 将它放在它第一次看到声明的其他属性的地方。这符合将声明组合在一起的标准,通常在类定义的顶部。有一个重构(将声明移动到引用)将声明移动到接近使用值的地方。

其次,在快速操作菜单中有两个选项可用于封装字段。就可见的提议变更而言,它们是相同的。不同之处在于,如果您选择第一个选项(使用“并使用属性”),那么对该类中字段的任何引用都将改为使用属性。如果选择第二个选项(带有“但仍使用字段”),则不会对该字段的任何引用进行任何更改。

提取接口

更有用的重构之一是提取接口,尤其是如果您经常使用接口编写可测试代码的话。它的目的是查看类中声明的属性和方法,并使用这些元素创建一个接口。事实证明,您可以挑选哪些属性和方法包含在接口中。当它完成时,不仅接口被声明,而且类被配置为实现接口。

考虑下面的类:

    public class ExtractInterfaceSample
    {
        public int Counter { get; private set; }
        public string Title { get; set; }
        public void Increment()
        {
            Counter++;
        }
        public void Reset()
        {
            Counter = 0;
        }
    }

起点是将光标放在类声明上并调用快速操作。选择提取接口选项。这将显示如图 4-13 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-13

提取接口对话框

图 4-13 中的对话框用于提供正在提取的接口的具体信息。从顶部开始是接口的名称(默认情况下,它是以“I”为前缀的类名)和完全限定名(使用类的名称空间)。接下来,您可以为创建的文件指定目标。虽然默认情况下使用接口的名称创建一个单独的文件,但是您可以更改名称或将接口放入当前文件中。

最后,列出公共成员(方法和属性)。默认情况下,所有这些成员都将包含在接口中,但是您可以通过取消选中不想包含的属性来设置所需的属性。准备就绪后,单击 OK,接口就创建好了,并且该类被标记为实现它。

提取方法

提取方法是最常用的重构之一。当您有一个代码块想要从它的当前位置提取并放入一个新方法中时,就使用它。虽然您可以复制和粘贴代码,但重构的亮点在于它能够自动为您设置参数列表,并添加类型和引用关键字(如outref)。

起点是一段代码,通常嵌入到一个方法中。考虑以下代码:

public bool Validate(Person person)
{
   bool result = true;

   if (String.IsNullOrEmpty(person.FirstName))
      result = false;
   else if (person.FirstName.Length > 50)
      result = false;

   if (String.IsNullOrEmpty(person.LastName))
      result = false;
   else if (person.LastName.Length > 50)
      result = false;

   return result;
}

暂且不论代码是如何的不自然,很明显有一段代码已经被复制,并且可以很容易地放入它自己的方法中。要使用这个重构,选择要提取的代码(在这个例子中,是两个if块中的任何一个)并触发快速动作。您的编辑器将类似于图 4-14 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-14

提取方法

提议的主要变化有两个。首先,在所选代码块的位置,整个代码块都被替换为一个方法调用。参数列表通过考虑在块之前定义并在块中使用的变量来确定。

返回值是通过查看块内发生变化的变量来确定的。如果不止一个变量被改变,这些变量将在参数表中被标记为ref或 o ut。例如,考虑如下所示的方法:

public bool Validate(Person person)
{
   bool result = true;
   bool result1 = false;

   if (String.IsNullOrEmpty(person.FirstName))
   {
      result = false;
      result1 = true;
   }
   else if (person.FirstName.Length > 50)
      result = false;

   return result && result1;
}

现在,如果对 if 语句执行提取方法重构,新方法的签名如下所示:

NewMethod(person, ref result, ref result1)

您可能会注意到,调用和被调用位置的方法都被赋予了通用名称NewMethod。光标放在方法声明处。如果您现在更改方法名,它也会更改调用位置。同样,在编辑器的右上角,有一个与重命名方法相关的小表单。如图 4-15 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-15

重命名方法设置

此表单中的选项还允许您在任何注释或字符串中进行更改。或者,您可以使用复选框在单击“应用”时预览更改,而不只是立即更新代码。

生成参数

“生成参数”重构的目标是为您提供一种简单的方法,在将注意力放在代码编辑器上的同时将参数添加到方法中。首先,您需要一个没有在方法中声明的变量。

public void GenerateParameter()
{
   taxRate = 0.13m;
}

然后,将光标放在变量上,触发快速动作命令以查看以下内容(图 4-16 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-16

生成参数

从建议的更改中,您可以看到一个适当类型的参数被插入到方法的签名中。参数名与变量名相同。

一个警告是变量不能被完全声明。换句话说,如果该行显示为var taxRate = 0.13m;,那么您将不会在快速动作菜单中找到生成参数选项。

向上拉构件

这种重构适用于实现接口或从基类派生的类。这个想法是你已经创建了基类或者接口。然后,您已经创建了一个从该工件派生或实现该工件的类。随着您继续开发,您向类中添加了一个新方法,并决定该方法真正属于基类或接口。因此,您将成员提升到更高的类型或接口。

要调用,首先将光标放在方法名称上,然后调用 Quick Actions 命令。图 4-17 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-17

拉起一个成员

在重构列表中,有两个功能非常明确。有一个将选定的方法(Reset)拉至该类实现的接口(IPullMethodUpSample)。如果该类实现多个接口,列表中会有额外的选项。此外,您可以选择将方法提升到基类(SampleBase)。在这种情况下,方法声明和实现都从派生类移动到基类。

第三个选项是将成员提升到基类型,它提供了对过程的更多控制。当您选择此选项时,您将看到一个类似于图 4-18 的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-18

向上拉成员对话框

首先,您可以选择目的地,无论它是基类还是一个实现的接口。接下来是不属于基类和接口的成员列表。对于每个成员,您可以决定是否要将该成员拉至所选目标。完成所有选择后,单击“确定”完成重构。

重新命名

重命名事物的能力是最基本的重构之一。它适用于变量、属性、方法、类和接口。用户界面非常简单。首先对项目进行更改,然后触发“快速动作”命令。图 4-19 显示了一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-19

重命名重构

这种流程在所有可以重命名的不同类型的事物中是一致的。而且,随着您熟悉这些步骤,重命名所有这些不同的项目将会在您的手指中根深蒂固。改变一个变量的名字,做一个 Ctrl+。,按回车键,继续前进。Visual Studio 对所有已知的引用进行了更改(关于不能重命名字符串中的值的标准免责声明适用)。

代码结构

令人惊讶的是,保持代码看起来整洁对于理解代码在做什么非常有用。空白会有所帮助,将代码转换成功能相当的版本也会有所帮助,因为这样做的目的更加明显。为了帮助解决潜在的错误,增加了对经常被忽略的场景的检查。本节将介绍有助于这一过程的重构。

为参数添加空检查

作为开发人员,创建接受对对象的引用作为参数的方法,然后不加思索地访问该对象中的属性或方法是很常见的。如果将空值传递给该方法,就会出现问题。并且引发了空引用异常,由应用(或者可能是不同的开发人员)来找出异常发生的位置和原因。

为了帮助缓解这个问题,在使用传入的参数值之前对它们执行空检查通常是一个好主意。这种重构允许您快速创建一个代码块来完成这一任务。

要开始重构,将光标放在参数列表中并触发快速操作。图 4-20 显示了产生的菜单和显示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-20

添加空检查重构

您可以看到,对于person参数,将添加一个 null 检查,如果为 null,将抛出一个异常。顺便说一句,只为引用类型的变量添加 null 检查。例如,它不包括整数变量。

将匿名类型转换为元组

元组是将相关值集合合并为一个单元的轻量级方式。类似于类,但是没有一些语法开销,当需要在一个类中的方法之间移动相关值时,它们非常有用。这个重构与上一节中的将匿名类型转换为类重构相关,不同之处在于结果是一个元组,而不是一个类。更有用的是,它实际上是一个命名元组,区别在于命名元组将字段名公开为元组中的值(与 Item1、Item2 等相对)。在常规元组中发现)。

要触发重构,从匿名类型的声明开始。例如,它可以直接完成或者作为 LINQ 查询的一部分。以下代码是一个示例:

var tempType = new {FirstName="Cam", LastName = "Johnson"};

将光标放在new关键字或匿名类型中的任何地方,并触发快速动作。你会看到类似图 4-21 的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-21

将匿名类型转换为命名元组

一旦完成重构,就可以将元组传递给参数,这在匿名类型中是不可能的。例如,下面的方法签名将接受图 4-21 中创建的元组:

private void TakeParameter(
   (string FirstName, string LastName) temporaryType)

在 For 循环和 Foreach 之间转换

C#提供了许多不同的方法来创建循环。这种重构是从一种形式转换到另一种形式的第一步。这里的起点是一个for循环。更具体地说,这是一个包含所有三个标准部分的for循环:初始化器、条件和迭代器。以下代码是一个示例:

List<string> data =
   new List<string>() { "a", "b", "c", "d", "e" };
for (int i = 0; i < data.Count; i++)
{
   Console.WriteLine(data[i]);
}

如您所见,for语句包含了所有三个必需的组件。但是,要实现重构,还需要有另外一个元素。在循环本身中找到的。需要对作为循环基础的集合进行索引引用。例如,参考data[i]即可满足该标准。当满足这些条件时,将光标放在 for 关键字上,并触发快速操作。出现如图 4-22 所示的显示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-22

将 for 转换为 foreach

从 for 到 foreach 的更改在预览更改中可见。事实证明,还有一个重构来逆转这些变化。触发重构几乎是一样的,因为它涉及到将光标放在 foreach 关键字中并执行 Quick Actions 命令。图 4-23 为结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-23

将 foreach 转换为 for 循环

虽然这种反转不是完全逐字符的,但结果非常接近原始代码。

在外国和 LINQ 之间转换

与上一节类似,这两个重构用于在 foreach 结构化循环和 LINQ 查询之间移动。然而,起点略有不同。考虑以下代码:

public IEnumerable<string> Sample()
{
   List<string> data =
      new List<string>() { "a", "b", "c", "d", "e" };
   foreach (var datum in data)
   {
      yield return datum;
   }
   yield break;
}

这个代码示例与上一节中的代码示例之间最大的变化是引入了关键字yield。这表明被调用的方法是一个迭代器。它通常在处理可枚举集合(如泛型类列表)时使用。这里迭代器的实现是显式的,与列表隐藏的隐式实现相反。LINQ 查询的结果也是一个迭代器,这就是为什么需要这种形式的代码来支持重构。

将光标放在 foreach 关键字上并触发快速操作。结果如图 4-24 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-24

从外国转换到 LINQ

在显示潜在变化的部分,您可以看到从该方法返回的值现在是一个 LINQ 查询结果。还有一种稍微不同的重构形式。如果选择转换为 LINQ(调用形式),而不是 LINQ 的内联语法形式,则使用 Select 方法。因此,return语句将如下所示:

return data.Select(datum => datum);

还有一个执行反向转换的重构。起点是前面重构的输出(内联语法,而不是调用形式),将光标放在from关键字上。然后触发快速动作显示图 4-25 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-25

从 LINQ 转换到外国

提议的更改与本节开头的原始代码相似。

将 Switch 语句转换为 Switch 表达式

这是一个新的重构,它利用了 C# 8.0 中引入的一个特性:一个开关表达式。switch 表达式的目的是简化用于为变量赋值的 switch 语句的使用。考虑以下代码:

int place = 1;
string result;
switch (place)
{
    case 1: result = "First"; break;
    case 2: result = "Second"; break;
    case 3: result = "Third"; break;
    default: result = "Unknown"; break;
}

示例中 switch 语句的最终目的是根据place的值设置result的值。开关表达式允许您使用一个switch关键字和 lambda 操作符的组合来更直接地执行赋值。下面是等效的开关表达式:

int place = 1;
string result = place switch
{
   1 => "First",
   2 => "Second",
   3 => “Third",
   _ => "Unknown"
};

重构旨在为您自动完成这一转变。从第一个代码示例开始,将光标放在 switch 关键字上,开始快速操作。图 4-26 应该变得可见。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-26

将 switch 语句转换为 switch 表达式

在预览区域中,您可以看到原始代码被删除,替换为 switch 表达式的 lambda 语法。

内嵌临时变量

通过内联临时变量来重构代码的能力很好,但并不总是至关重要的。前提是你有一些使用临时变量的代码。然后,该变量被用于不同的表达式,而没有修改。例如,考虑以下代码行:

double temp = diameter / 2;
double area = Math.PI * temp * temp;

从变量名看不出temp的目的是什么。因此,您可能希望内联原始计算。将光标放在temp的声明上,并启动快速操作。图 4-27 就是结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-27

内联临时变量

虽然重构是好的,因为只是用来保存计算结果的变量已经被删除了,但有时保留它还是有好处的。例如,如果变量的名字是radius而不是temp,那么在未来许多年阅读该代码的人将能够更快地理解正在执行的计算。情况并不总是这样,但是在使用这种重构之前,值得考虑一下。

反转条件和逻辑表达式

条件值是任何应用的常见部分。以及如何设置条件值可能是未来可读性的一个重要方面。当开发人员回顾代码时,如果与设置值相关的逻辑很复杂,就很难解开最初的意图。更糟糕的是,如果计算中使用的变量名在条件的上下文中难以理解。

考虑以下代码:

bool continueProcessing =
   valueToTest > 100 ? false : canUpdate;

从阅读代码中可能看不出,只要valueToTest小于或等于 100 并且canUpdate为真,continueProcessing就被设置为真。虽然该语句工作正常,因为正确的值被赋给了continueProcessing,但它的可读性并不是特别好。如果将光标置于表达式中的任意位置并触发快速动作,图 4-28 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-28

反转条件表达式

重构完成后,结果如下:

bool continueProcessing =
   valueToTest <= 100 ? canUpdate : false;

虽然这在功能上等同于前面的语句,但条件的排序使其更易于阅读。在查看现有代码时,越简单越好。

反转 If 语句

这个重构的目的与前一个相同。它采用 if 语句或 if-else 语句,并反转条件中的逻辑。同时,它交换块,以便代码工作相同。请考虑以下几点:

public bool SampleMethod(int valueToTest, bool canUpdate)
{
    bool result;

    if (valueToTest > 100)
    {
        result = false;
    }
    else
    {
        result = canUpdate;
    }

    return result;
}

与前面的代码一样,条件的意图不是特别清楚。通过反转 if 语句,可以使其成为更容易阅读的代码块。将光标放在第一行(包含 if 和条件的那一行)并触发快速操作。图 4-29 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-29

反转 if 语句

if 语句的意图在倒置后更加明显。至少可以说。有些人总会找到不同意的理由。

将声明移动到引用附近

当谈到在哪里放置变量声明时,至少有两个主要的思想流派。一种是收集方法顶部的所有声明。第二种方法是在方法中第一次引用变量的地方附近声明变量。无需权衡每个选项的利弊,这种重构允许您轻松地移动声明位置。以下代码是该示例的起点:

int variableToMove;

ConvertAnonymousTypeToClassSample();
ConvertForEachLoopToFor();

variableToMove = 100;

将光标放在变量声明中,并启动快速操作命令。图 4-30 显示了可用的选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-30

将声明移动到引用附近

预览窗格中可见的更改是将声明从方法调用的上方向下移动到它的使用位置的正上方。

拆分或合并 If 语句

这种重构也属于让代码更容易阅读的范畴。或者您需要为您的if语句中的不同条件添加功能。拆分if语句的前提是你有一个带有多个条件的单个if。例如,考虑以下情况:

if (valueToTest <= 100 && canUpdate)
{
   ...
}

这是一个带有两个条件的 if 语句。拆分 if 语句将产生以下代码:

if (valueToTest <= 100))
{
   If (canUpdate)
   {
      ...
   }
}

合并一个 if 语句执行相反的功能,将嵌套的if语句和条件放在一个if中。

要查看重构,请将光标放在两个条件之间的操作符上,并触发快速操作。图 4-31 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-31

拆分 if 语句

您可以在 proposed changes 窗格中看到前面描述的拆分。同样,如果原始的if语句包含一个 else 块,那么这个块将被复制,这样无论哪条路径通过if语句,它都将在适当的时候被执行。

合并重构是类似的。不同之处在于,您需要将光标放在嵌套的if语句的 if 命令中。上下文菜单上的选项表明它将把if语句与外部的if块合并。

使用 Lambda 表达式或块体

在 C#中使用 lambda 表达式定义函数时,有两种语法选择。它们在功能上是等效的,因此“正确”的选项取决于您的偏好。这种重构允许您轻松地从一种风格转换到另一种风格。例如,lambda 的表达式样式如下所示:

delegate int del(int i); // Defines the delegate signature
del myFunction = x => x * x;

以块主体样式表示的相同定义如下:

del myFunction = x => {
   return x * x;
}

虽然有时不得不使用块主体样式,例如当主体有多个语句时,但是这种重构在任何一种样式中都是可以接受的。

要执行重构,请在光标位于 lambda 表达式内时触发快速操作。图 4-32 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-32

将 lambdas 从表达式转换为块体

正如在预览窗格中可以看到的,如果您选择此重构,将执行从表达式语法到块主体的转换。反其道而行之,从块状体到表情,也可以。将光标放在块内并触发快速动作。图 4-33 为结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-33

将 lambdas 从块体转换为表达式

未使用的赋值、变量和参数

Visual Studio 的一个微妙特性是,它淡出不使用的方法、参数和变量。还会生成一个警告,因此它会出现在任何编译器输出中。这种行为有几个好处。首先,这是一种快速找到代码死部分的方法。从可读性的角度来看,删除代码的死部分是一件好事。但是有时看到一个变量未被使用可能是一个错误的指示。也许调用了错误的方法,因为你确定你调用了那个淡出的方法?

不管怎样,对于 Visual Studio 来说,这是一个很好的特性,可以表明缺乏使用。还有一个重构来帮助你删除不合适的变量、参数或赋值。

如何触发重构取决于要删除的内容。对于未使用的变量或方法,将光标放在变量声明上就足够了。对于未使用的参数,需要将光标放在该参数上。如果有问题的语句是一个不需要的赋值,将光标放在初始赋值上就可以进行重构。无论你处于哪种情况,这个过程现在都很熟悉了。触发“快速动作”命令,您将看到您所做更改的预览窗格。图 4-34 就是一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-34

移除未使用的变量

在本例中,唯一的变化是删除了变量的声明。这种模式适用于移除参数或方法。不同之处在于,如果您正在移除赋值。在这种情况下,问题是变量在声明时已经被赋予了一个值。与值中的var a = 1一样,a立即被赋值为 1。然而,如果a在使用之前就被赋予了另一个值,那么初始值的赋值就是多余的。这种重构会移除声明中的赋值。

使用显式类型

几个版本之前引入到 C#中的一个特性是用隐式类型声明变量的能力。最简单的例子如下:

var a = 1;

编译器从语句中计算出 a 是一个整数没有问题。所以,它编译你的代码时已经做了这个假设。

所有这些都很好,直到有了要求变量显式类型声明的编码标准。对于前一种情况,这没什么大不了的。但是随着类型变得越来越复杂,人们习惯于使用一个工具来帮助从隐式类型声明转换到显式类型,然后再转换回来。

将光标放在变量名上并触发快速动作。图 4-35 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-35

转换为显式类型

正如您所看到的,唯一的变化是在变量声明中,这正是您从重构中所期望的。如果变量最初是使用var关键字声明的,那么重构选项会读作“使用 var 而不是显式类型”此时,执行快速动作将显示图 4-36 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-36

转换为隐式类型

在此示例中,完成该操作会将变量声明转换为隐式类型声明格式。

Note

从隐式类型声明转换为显式类型并不总是可能的。如果您使用匿名类型,尤其如此。在这种情况下,重构将不可用。相反,您可以使用本章前面介绍的将匿名类型转换为元组重构来创建具体类型。

文件组织

这一节讨论的重构不是关于移动功能,而是关于识别代码驻留的位置,向文件中添加必需的元素,以及一般来说,使您的代码和项目更加一致。

未导入类型的智能感知完成

如果您使用 IntelliSense 已经有一段时间了,那么您可能熟悉这样一个功能:当您使用非完全限定的类时,该功能允许您自动将 using 语句添加到代码的顶部。例如,假设您键入了以下代码:

File f = new File();

现在,如果在与您当前的类相同的名称空间中还没有一个File类,您将会看到红色的曲线,表示在File的两个实例下都有错误。如果将光标放在File上并触发快速动作,将会看到图 4-37 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-37

自动添加 using 语句

在这种情况下,您可以选择使用 File 作为系统中的类。IO 命名空间或系统。WebRequestMethods 命名空间。包含每个命名空间定义的程序集已经作为原始项目模板的一部分或因为您添加了引用而导入到您的项目中。

到目前为止,这种重构的限制是程序集需要已经在项目中被引用。Visual Studio 2019 增加了不仅可以添加适当的 using 语句,还可以添加对当前不存在的项目或程序集的引用的能力。

流程和之前一样。您将光标放入该类型并启动快速操作。图 4-38 是结果的一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-38

通过快速操作添加参考

与图 4-37 不同,在图 4-37 中,选项是添加一个using语句来导入名称空间,在这种情况下,您的选择包括添加一个对定义了缺失类的项目的引用。RefactoringDemo。Library 是当前解决方案中的一个项目,所以SampleClass是一个类,它的实现当前在您的解决方案中。然而,在寻找可能添加的引用时,IntelliSense 并不仅仅停留在解决方案中的项目上。它也考虑标准。NET Framework 程序集(至少是基于您的目标框架可用于您的项目的程序集),以及查看 NuGet 以寻找可能添加的库。如果你选择执行这个动作,一个对项目或程序集的引用会被添加到你的项目中,同时在文件的顶部添加using语句。如果您选择一个 NuGet 包,包引用将与using语句一起添加。

将类型移动到匹配文件

当您专注于一个特性的开发时,通常会将实现代码(如枚举或类)放在您正在工作的文件中。这可能看起来没有很大的区别,但是对于一些开发人员来说,它允许他们停留在流程中,而不用从一个选项卡移动到另一个选项卡或者从一个编辑器窗口移动到另一个编辑器窗口。

一旦开发完成(也就是说,所有的测试都通过了…这仍然是红/绿/重构模式),将类型和枚举转移到单独的文件是一个好的实践。下一个开发代码库的开发人员更容易找到实现。

这种重构正是您所需要的,使您的代码与每个文件一种类型的目标保持一致。为此,将光标放在该类型的名称上,并触发快速操作。图 4-39 变得可见。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-39

将类型移动到不同的文件

如您所见,重构提供了类型定义并将其移动到一个单独的文件中。并且文件的名称与类型的名称相同。

为了完整起见,请记住该类型的命名空间将与当前声明该类型的命名空间相同。并且该文件与当前找到该声明的文件放在同一个文件夹中。虽然这肯定会使应用继续编译,但它可能不一定是正确的位置,这取决于您项目的标准。也不能保证它位于项目的正确名称空间中,因为它依赖于您的标准,这将导致下一次重构。

将类型移动到命名空间

这种重构的目的是修改特定类的名称空间。首先,将光标放在类名上,并触发快速操作。图 4-40 为结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-40

移动到特定的名称空间

当您选择将类型移动到不同的命名空间时,下一步涉及选择目标命名空间。为了适应这种情况,出现如图 4-41 所示的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-41

选择新的名称空间

下拉列表包含项目中当前定义的所有名称空间。当您选择不同的命名空间并单击“确定”时,该类的命名空间将会更改,并且解决方案中对该类的所有引用都会更新。或者,您可以在框中键入一个新的名称空间,它将作为完整重构的一部分被创建。

通过智能感知完成正则表达式

虽然这种智能感知功能严格来说不是重构,但 Visual Studio 2019 在文档的“重构”一节中描述了它。不管你如何归类,它的有用性是无可争议的。

其思想是,在编写表达式的过程中,IntelliSense 会提示您如何构造正则表达式字符串。考虑图 4-42 中可见的智能感知选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-42

通过智能感知完成正则表达式

当您触发 IntelliSense(默认击键是 Ctrl+space)时,会出现一个包含正则表达式选项列表的下拉列表。虽然这只是一个列表,但至少它给了你一个起点,或者一个提醒。无论哪种方式,当您考虑创建良好的正则表达式的挑战时,每一点点的帮助都会受到感谢。

删除无法访问的代码

如果没有一组允许代码执行的条件,则代码被视为不可访问。举个简单的例子,检查下面几行:

throw new Exception();
Console.WriteLine("This will never be executed");

第二行(控制台)是不可能的。WriteLine 方法)来执行。前一行抛出一个异常,它立即终止当前代码块的执行。控制台。WriteLine 被视为不可读。

在 Visual Studio 2019 中,无法访问的代码是灰色的。同样,该行的开头包含一条绿色曲线,表示存在问题。关于该问题的工具提示解释说检测到了不可达的代码。

这种重构的目的是为了轻松地删除无法访问的代码。将光标放在无法访问的代码行上,并执行快速操作注释。显示图 4-43 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-43

删除无法访问的代码

当调用重构时,当前节中所有不可到达的代码都被删除。

Note

您可能想知道为什么 Visual Studio 中包含这样的重构。毕竟,手动删除这一行代码是非常琐碎的。原因之一与开发人员的流动有关。如果您熟悉各种击键命令,可以使用 Ctrl+ .来触发快速操作。然后,按 Enter 键会导致执行重构。换句话说,您可以通过两次击键来删除无法访问的代码。而且,在一个有经验的开发人员手中,这将在几分之一秒内发生。这比用鼠标选择一两行更容易。换句话说,这一切都是为了从开发人员的工作流程中节省时间。

排序使用

这种重构是美学功能的典型代表。它的目的是将代码文件顶部的using语句按字母顺序排序。表面上看,原因是当列表按字母顺序排列时,更容易找到特定的名称空间。有一些开发者(包括作者在内)仅仅因为看起来更漂亮就觉得有必要对 usings 进行排序。

这种重构的不同之处在于,它不是通过快速动作命令触发的。相反,该选项是通过使用“编辑➤智能感知➤排序”菜单命令调用的。或者,您可以右键单击导入并从上下文菜单中选择移除和排序 Usings 选项。

然而,有一个相关的重构是可用的。如果您有不再需要的using语句(也就是说,在文件中没有引用名称空间中的类),它们将显示为灰色,类似于不可访问的代码。如果您在光标位于using部分时执行快速操作,有一个删除不必要的 usings 选项将删除不需要的语句。

同步命名空间和文件夹

当你对你的项目进行清理的时候,这种重构也会出现。常见的操作是将文件拖到不同的文件夹中,或者决定将多个文件分组到各自的文件夹中会更好。不管理由是什么,你在文件夹之间移动文件,直到你对结果满意为止。

问题在于 Visual Studio 如何在您的文件中定义命名空间。当您第一次创建文件时,默认分配的命名空间是项目的名称,后跟以点分隔的文件夹层次结构。因此,如果在您的 RefactoringDemo 项目中,您在控制器➤产品文件夹中创建了一个文件,则命名空间将如下所示:

namespace RefactoringDemo.Controllers.Products

通常,这正是您想要的名称空间。但是您只是将一些文件移到了不同的文件夹中。并且它们的名称空间还没有被更新以归档它们的新家。这就是同步名称空间和文件夹重构的用武之地。将光标放在名称空间命令上,并触发快速操作。出现类似图 4-44 的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-44

同步命名空间和文件位置

正如您所看到的,对文件的更改是您所期望的。并且在文件中找到的对类的任何引用也被更新。

同步类型和文件名

这个重构和上一个类似。在这种情况下,它将文件中声明的类型的名称与文件名的名称同步。它有几种不同的触发方式。首先,如果您使用解决方案资源管理器的上下文菜单中的重命名选项更改文件的名称,系统会提示您是否也要更改文件中类型的名称。

或者,如果文件中的类型名称已经不同于文件的名称,可以将光标放在文件中的类型名称上并触发快速操作。图 4-45 出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-45

更改类型以匹配文件名

这种重构带来的变化非常简单。任何对原始类名的引用都会更新以匹配新名称,就像您手动重命名该类型一样。

换行、缩进和对齐参数

这种重构绝对符合美学的范畴。它的目的是在方法声明中或调用方法时改变参数的布局。起点要么是方法声明,要么是方法调用。将光标放在参数列表中,并执行“快速动作”命令。将显示图 4-46 或与之相近的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-46

包装参数

这种重构背后的前提是允许您轻松地让所有方法声明和调用符合一个标准。从图 4-46 中,所有的参数都放在一行中。包装参数时,有三个选项:

  • 对齐包装的参数参数放在不同的行上(除了第一个,它和方法名在同一行)。并且参数与第一个参数的开头对齐。

  • 缩进所有参数参数放在单独的行上(第一个是而不是在方法名的同一行上),然后从行首缩进一个制表位。

  • 缩进换行参数参数放在单独的行上(第一个参数在方法名所在的行上),从行首缩进一个制表位。

这些选项在调用方法时也可用。有一个重构来执行相反的功能。也就是将不同行上的参数移动到与方法名相同的行上。

摘要

在过去的两章中,您已经仔细了解了 Visual Studio 2019 包含的工具,这些工具可以帮助您使用红/绿/重构范式进行开发。但是即使这不是你喜欢的开发风格,单元测试和重构都是可靠的(双关语)开发实践的核心。

在下一章中,现代开发的另一个不可或缺的部分将被涵盖——源代码控制。更具体地说,Git 和 Visual Studio 2019 之间的集成与您日常面临的常见任务有关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值