VS2019-基础知识-全-
VS2019 基础知识(全)
一、安装和 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;
}
首先,您有一个名称属性,使用自动属性get
和set
函数。第二,你有一个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,接口就创建好了,并且该类被标记为实现它。
提取方法
提取方法是最常用的重构之一。当您有一个代码块想要从它的当前位置提取并放入一个新方法中时,就使用它。虽然您可以复制和粘贴代码,但重构的亮点在于它能够自动为您设置参数列表,并添加类型和引用关键字(如out
和ref
)。
起点是一段代码,通常嵌入到一个方法中。考虑以下代码:
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 之间的集成与您日常面临的常见任务有关。
五、在 Visual Studio 2019 中使用 Git
不使用某种形式的源代码控制的现代开发是不可想象的。虽然有很多选择,比如 Subversion、Mercurial 和 CVS(并发版本系统),但是粗略地看一下不同产品的流行程度,Git 似乎更有优势。取决于你如何计算不同的东西,GitHub(托管 Git 库的最受欢迎的站点)的受欢迎程度似乎是下一个托管站点的七倍。即使考虑到计数的差异,也可以相当安全地得出结论,Git 正被数百万开发人员使用,以托管从爱好网站到开源项目到商业产品的一切,所有这些都支持本章的前提,即专注于 Visual Studio 2019 为使用 Git 的开发人员提供的工具。
将 Git 与 Visual Studio 集成
对 Git 的详细描述远远超出了本书的范围。事实上,它将采取自己的书做公正。幸运的是,有这样一本书,在 Git 的文档部分( https://git-scm.com/book/en/v2
)找到。这本书名为 Pro Git ,也由 Apress 出版社出版,在 Amazon.com 上可以买到。
本章假设读者熟悉基本的 Git 概念,比如存储库、提交、分支、标签和拉请求。如果提到不寻常的东西,会有描述。但是对于基础的部分,将会假设一个基本的知识水平。
Visual Studio 2019 提供了几个组件,使您能够轻松地与 Git 进行交互。但是,这些组件不会随任何当前工作负载一起安装。为了使用 Git,您需要添加几个组件。
首先,启动 Visual Studio 安装程序。在第一个屏幕上(图 5-1 ,您应该会看到您已经安装在系统上的 Visual Studio 实例。
图 5-1
Visual Studio 安装程序
在要添加 Git 支持的实例旁边,单击 Modify 按钮。这将打开表单(图 5-2 ),允许您修改 Visual Studio 2019 的工作负载、组件和语言包。
图 5-2
为安装选择 Git 组件
在“单个组件”部分,向下滚动,直到到达“代码工具”部分。在那里,你会看到两个与 Git 相关的组件:Git for Windows 和 GitHub extension for Visual Studio。当然,您也可以在左栏顶部的搜索栏中输入Git
来归档所需的组件。
确保选中这两个组件,然后单击右下角的修改按钮。这将启动安装过程。
安装完成后,安装完组件后,已安装的 Visual Studio 实例列表可能会重新出现。您可能会看到一条消息,指出有可用的更新。此消息通常意味着 Visual Studio 安装程序检测到有新版本的 Visual Studio 可用。更新不一定与添加的组件相关。单击查看详细信息链接并阅读发行说明以确保。
创建和克隆 Git 存储库
作为一个源代码控制系统,Git 的基本目的是跟踪一段时间内对一组文件所做的更改。这些文件可以在项目中,也可以是解决方案的一部分,或者只是在文件夹中。这些信息存储在称为存储库的文件集合中。存储库的详细信息存储在.git
文件夹中。
另外,Git 是一个分布式源代码控制系统。实际上,这意味着项目没有中央存储。相反,无论项目在哪里,存储库都存在。在服务器上,有一个存储库。在客户端,有一个存储库。当您推送您的更改时,您正在将客户端上的存储库中的更改移动到服务器上的存储库中。当您克隆一个存储库时,您是在服务器上获取该存储库并将其复制到客户端。请记住,存储库包括所有文件。
Visual Studio 2019 支持 Git 库的创建和克隆。这两个操作中的一个是在开发中开始使用 Git 的关键。我们将通过创建一个新项目并将其添加到源代码控制中来创建一个 Git 存储库。这不是创建 Git 存储库的唯一方法。Git 命令行包含一个init
命令,它也可以创建 Git 存储库所需的文件。但是我们将演示 Visual Studio 如何做到这一点。
创建 Git 存储库
正如本章前面提到的,Git 存储库是存储在文件和文件夹集合中的版本信息。关键是在适当的位置创建适当的文件。Visual Studio 2019 提供了一种直观的方式来实现这一点。
首先,创建一个全新的项目。这可以通过使用文件➤新➤项目菜单项来完成。在新建项目对话框中(图 5-3 ,选择 ASP.NET 核心 Web 应用的项目模板,点击下一步。
图 5-3
创建新项目
在下一个对话框中(图 5-4 ,提供项目名称和位置。您可能还会注意到,有一个“将解决方案和项目放在同一目录中”选项已经被选中。这不是默认设置,但在这里使用,以便项目的所有文件都在一个文件夹中。这使得将文件传输给其他人变得更容易,就像作为书籍下载的一部分。
提供值后,单击创建转到创建应用的下一步。
图 5-4
配置新项目
最后一步是选择用于创建 web 应用的模板,如图 5-5 所示。
图 5-5
选择 ASP.NET 核心 Web 应用模板
该示例将使用 Web 应用,以避免在不同技术的支持者之间引发激烈的争论。但是请注意,Git 对您使用的模板类型是完全中立的,就像它对您用来构建项目的语言或部署中使用的底层技术是中立的一样。Git 是关于存储作为项目一部分的工件。你想用这些艺术品做什么是不可知的。
现在您已经有了一个要处理的项目,让我们看看为它创建一个 Git 存储库的相关步骤。
你实际上有很多选择。最简单的可以在 Visual Studio 的右下方找到(见图 5-6 )。
图 5-6
添加到源代码管理
当您单击“添加到源代码管理”标签时,将出现已配置的源代码管理环境列表。图 5-6 显示 Git 是一个选项,如果你已经安装了本章前面描述的 Git 工具,就会出现这种情况。如果您在 Visual Studio 中配置了其他源代码管理工具,它们也会出现在此列表中。
一旦单击 Git,Visual Studio 就会在幕后创建所有必需的文件夹和内容。一旦完成,这应该只需要一分钟左右,Visual Studio 的右下角将如图 5-7 所示。
图 5-7
Git 存储库的状态栏
左边的标签是当前存储库的名称。单击标签将在团队资源管理器中打开存储库。名称右侧是当前分支。单击分支会提供许多选项来管理分支,这将在本章稍后讨论。
请注意,您刚刚创建的 Git 存储库只存在于您的机器上。它尚未保存到中心位置。因此,虽然您可以执行许多有用的操作,比如放弃对以前提交的项目的更改,但是如果您的机器发生问题,您仍然有可能丢失您的工作。为了防止这种情况,有必要将您的代码推到一个中央存储库中。一旦存储库被创建,随着状态栏的改变,团队资源管理器中的推送窗格(图 5-8 )被打开。
图 5-8
团队资源管理器中的推送窗格
在推送窗格中,有三个可能的目的地来推送可见的代码。首先是 Azure DevOps。这是一个与微软 Azure 相关联的托管服务(也许从名字就可以看出来)。它使用您随 Visual Studio 提供的凭据来访问适当的帐户,并且您可以存储无限数量的私有存储库。如果您希望配置您的存储库以便其他人可以使用它,您最终可能需要付费使用该站点。
第二个选择是 GitHub。这可能是世界上最受欢迎的公共 Git 存储库站点,托管着数十万个项目。与 Azure DevOps 一样,免费托管的网站种类也有限制(公共或私人回购可以有最多三个合作者才能免费),但更广泛的情况下有付费选项。
最后一个选项更加通用。它允许您指定远程 Git 存储库的 URL。URL 应该代表远程服务器上现有的空存储库。例如,这个选项允许您使用 Git 存储库的企业实例作为远程存储点。
虽然对于还没有 Git 存储库的项目,状态栏底部提供的选项非常好,但是如果您不是从一个项目开始,还有其他一些方法可以使用。
起点是团队资源管理器,可以使用“查看➤团队资源管理器”菜单选项或使用键盘快捷键(Ctrl+\,默认为 Ctrl+M)来启动它。然后点击工具栏中的绿色插头以显示连接窗格,如图 5-9 所示。
图 5-9
团队资源管理器中的连接窗格
与 Push 窗格一样,有三个地方可以创建 Git 存储库。在底部,您可以看到任何本地 Git 存储库的列表。要创建新标签,请单击新标签。这将显示一个文本框,如图 5-10 所示,其中可以指定 Git 存储库的路径。
图 5-10
指定存储库的路径
输入所需路径后,单击“创建”创建存储库。
图 5-9 中的另外两个部分与 Azure DevOps 和 GitHub 有关。对于 GitHub,您可以使用 GitHub 标签下的链接创建一个存储库。首先,您需要向 GitHub 提供您的凭证,如果您在之前的会话中还没有这样做。当您点击登录链接时,系统会提示您输入 GitHub 用户 ID 和密码。登录后,您可以通过单击“创建”来创建存储库。出现如图 5-11 所示的对话框。
图 5-11
创建 GitHub 存储库
该对话框中的前三个字段可能是您所期望的。它们是存储库的名称和描述,以及存储库在 GitHub 中创建后将被克隆到的本地路径。接下来的两个字段有点不同,需要一些解释。
Git ignore 选项是一个下拉列表,用于选择应该包含在存储库中的.gitignore
文件。对于外行来说,.gitignore
文件是一个模式集合,它定义了不应该包含在存储库中的文件和文件夹。在 Visual Studio 世界中,一个不应该提交的常见文件是.suo
文件。这是一个包含特定于当前用户的 Visual Studio 解决方案设置的文件。因为它对于每个开发人员来说都是唯一的,所以它是一个不应该提交到存储库中的文件,因此应该包含在.gitignore
文件中。下拉列表包含了.gitignore
文件的不同模板列表。而且有很多。虽然默认设置是 Visual Studio,但是由于存储库的创建方式,请确保选择最适合您正在创建的项目的设置。但是也要注意,在.gitignore
文件创建之后,您可以随时更改它。
下一个字段是您的项目将使用的许可证。默认情况下不选择许可证,但是如果您预计在某个时候您的项目将被商业使用或成为开源项目的一部分,您可能希望从下拉列表中选择最合适的许可证。再说一遍,你做的决定不是不可改变的。这只是你项目的一个起点。
一旦您提供了所有必要的信息,包括将成为存储库所有者的用户(图 5-11 中包含 LACanuck 的字段)和指定这是公共还是私有存储库的复选框,单击 Create 创建存储库。同时,存储库将被克隆到您的机器上,位于您指定的路径下。
第二组与 Azure DevOps 相关,在创建 Git 存储库时没有用。虽然你可以克隆已经在 Azure 中的存储库,但是你不能通过这个接口创建一个。这种情况下的解决方案是创建一个本地 Git 存储库,然后将其推送到 Azure DevOps。
克隆 Git 存储库
当谈到克隆 Git 存储库时,有许多途径可以实现。其中最直接的一个就发生在你启动 Visual Studio 2019 的时候。图 5-12 是启动屏幕的一个例子。
图 5-12
Visual Studio 2019 启动屏幕
右边是四个按钮的集合,这四个按钮是开发人员的常见起点。这包括打开一个项目和创建一个新项目。最顶端是能够克隆或检出代码的选项。点击该按钮将显示图 5-13 ,用于指定要克隆的存储库的位置。
图 5-13
克隆或检出项目
对于任何一个任意的存储库,都会有一个标识位置的 URL。该 URL 需要在第一个文本框中输入,而第二个文本框包含本地机器上的路径。单击“克隆”按钮时,在特定位置找到的存储库将被克隆到指定的路径中。除了所有的项目工件之外,还将建立本地和远程存储库之间的上游链接。这个链接使得将提交从本地推送到远程存储库变得更加容易。
除了这两个文本框之外,在 Browse a repository 标签下还有另外两个链接,如果您不知道合适的 URL,它们可以让您找到存储库。
如果你点击 Azure DevOps 链接,你会被带到一个类似图 5-14 的对话框。
图 5-14
浏览 Azure DevOps 帐户
顶部是一个下拉列表,包含您已添加到计算机的不同 Microsoft 帐户。当选择一个帐户时,可访问的不同 Azure 帐户出现在占据对话框大部分的树中。在这里,您可以导航到您正在寻找的存储库。当您选择存储库并单击对话框底部的连接时,将出现 Visual Studio IDE 并显示团队资源管理器窗格(图 5-15 )。
图 5-15
团队资源管理器的主页窗格
如前所述,有多种方法可以克隆 Git 存储库。刚刚描述的工作流在您第一次打开 Visual Studio 时可用。但是,也可以从 Visual Studio 2019 IDE 本身中访问该流。如果使用文件➤克隆或检出代码菜单选项,出现如图 5-13 所示的对话框,所述流程从那里继续。
团队资源管理器提供了一个完全不同的流程。首先,点击工具栏中的插头图标,导航至连接窗格(图 5-16 )。
图 5-16
团队资源管理器中的连接窗格
在此窗格中,您有两种不同的方法来克隆存储库。在顶部的 GitHub 部分下面,有一个克隆选项。点击该选项的结果是一个屏幕(图 5-17 ),让您在 GitHub 上找到一个存储库,然后将其克隆到您的本地机器上。
图 5-17
从 GitHub 克隆
在此对话框中,会出现一个存储库列表,您可以从中进行选择。在那里,存储库或者是您的,属于您被添加到的组织,或者是您被添加到的项目。当然,这假定您有一个 GitHub 帐户。首次访问该对话框时,系统会提示您登录 GitHub。
如果您想要克隆的存储库不在列表中,您可以在对话框顶部提供它的 URL。同样,您可以在顶部文本框中输入一个搜索词。但是,搜索仅在列表中显示的存储库中执行。如果你想在 GitHub 中搜索库,你需要访问 GitHub 并直接在那里搜索。
对话框底部的文本框是存储库将被克隆到的本地路径。一旦提供了所有必要的信息,克隆按钮就会变为启用状态。单击它将启动克隆过程。
在 Connect 窗格的下半部分,有第二种克隆存储库的方法。在本地 Git 存储库标题下,有一个克隆选项。点击后,会显示一个类似图 5-18 的区域。
图 5-18
克隆非 GitHub Git 存储库
所需的信息与您尚未关联的 GitHub 存储库所需的信息类似。在顶部的文本框(浅黄色背景)中,指定 Git 存储库的 URL。在第二个文本框中,指定将存储库克隆到的本地路径。右边的按钮(带有省略号)用于打开“打开文件”对话框。当提供这些信息时,克隆按钮被启用。单击它将启动克隆过程。递归克隆子模块复选框用于允许克隆子模块,如果它们是基本存储库的一部分。
Git Submodules
Git 中子模块背后的思想是将一个现有的存储库附加到另一个存储库的文件夹结构中。虽然这种机制有许多好处,但主要的好处是能够从多个存储库中引用一个存储库,并且当其他存储库愿意接受对这个存储库的更改时,可以将这些更改提供给其他存储库。
当您使用这些步骤中的任何一个步骤克隆了一个存储库之后,您将得到一个您可以修改的代码的本地版本。这被称为本地存储库或本地回购。该存储库还与作为克隆源的存储库相关联。该存储库被称为远程存储库或上游存储库。当您完成开发时,这种关系会变得很有用,如下一节所述。
提交和同步更改
一旦您在本地机器上有了 Git 存储库,通常的下一步包括进行修改、测试、进行更多的修改等等,直到您对自己的工作满意为止。虽然 Visual Studio 有一些工具可以帮助您(正如在前面和后面的章节中所介绍的),但是您使用 Git 的事实对您的开发工作流并没有太大的影响。嗯,至少没那么频繁。但是一旦你完成了你的代码,并准备将它转移到生产中,Git 肯定会参与进来。
第一步是将您的代码放入您的本地存储库中。这是通过执行“提交”来完成的
提交过程由两个独立的步骤组成。第一个是识别提交中要包括的文件。这就是所谓的暂存文件。第二个是在本地存储库中记录已识别的变更(即暂存文件)。
在 Visual Studio 2019 中,提交过程的起点是团队资源管理器中的提交窗格(图 5-19 )。可以通过单击团队资源管理器主窗格中的“更改”选项来访问它。
图 5-19
团队资源管理器中的更改窗格
中间的大部分标记为 Changes,是自上次执行提交以来添加、删除或修改的文件的集合。这些文件(或它们的子集)将是您提交的一部分。
在 Visual Studio 中,默认情况下,所有已更改的文件最初都被视为暂存文件。这是为了让开发人员更容易执行提交。开发人员执行的大部分提交包括所有更改的文件,因此这种优化通常会节省一个步骤。但是,您总是可以明智地选择要转移的文件。右键单击单个文件和文件夹,然后从上下文菜单中选择“转移”,即可转移这些文件和文件夹。当您暂存任何文件时,Visual Studio 开始时的假设(所有文件都应被视为暂存的)将被放弃,只有显式暂存的文件才有资格提交。图 5-20 显示了一些文件已经暂存的变更窗格。
图 5-20
包含一些暂存文件的“更改”窗格
除了添加文件和文件夹,您还可以通过单击“更改”标签右侧的加号来暂存整套更改。也可以通过单击单个文件并从上下文菜单中选择“取消登台”来取消登台已登台的文件。或者,可以使用暂存更改标签右侧的减号取消暂存所有暂存文件。
除了转移和取消转移文件之外,还有一种方法可以防止文件包含在提交中。那就是撤销所有已经做出的改变。如果您临时修改了一个文件(比如说,通过修改一个连接字符串),或者您正在进行一些实验性的开发,这将非常有用。也许你并不完全确定如何解决一个问题,在走了一条特定的路之后,你决定结果并不符合你的喜好。不管是什么原因,自上次提交以来对文件所做的更改都可以通过上下文菜单来撤消。在“更改”或“分段更改”部分中选择要还原的一个或多个文件。然后右键单击并从上下文菜单中选择撤消更改。几分钟后,最后提交的文件版本将替换当前文件。
此时,文件集合已经暂存。下一步是提交它们。从功能上来说,这包括用暂存文件中的更改更新本地存储库。就您采取的步骤而言,它们仍然在团队资源管理器的“更改”窗格中。
提交文件有一个必填字段。在图 5-21 中,顶部有一个文本框。如果留空,背景颜色为浅黄色。
图 5-21
“更改”窗格中的“提交注释”字段
此字段用于为您的提交提供注释。注释与一组变更相关联,用于识别(至少在较高层次上)变更的原因,这意味着虽然在注释中加入一些简短且可能没有意义的内容很有诱惑力,但从长远来看,如果您努力保持注释有意义,它会更有用。
输入注释后,注释文本框正下方的提交暂存按钮将被启用。单击此按钮将暂存的更改提交到本地存储库。
该按钮的右边有一个下拉按钮。这是因为团队资源管理器在提交时为您提供了几个其他选择。
提交暂存的–
获取暂存的文件,并用更改更新本地存储库。
提交阶段和推送–
执行与提交阶段相同的功能。但是一旦提交完成,更改就会被推送到上游存储库。这相当于先执行一个git commit
,然后再执行一个git push
。
提交阶段和同步–
通过执行与提交阶段相同的功能开始。接下来,将上游存储库中任何未完成的提交集成到您的本地存储库中。最后,任何更改都被推回到上游存储库。等效的命令是git commit
后跟一个git pull
,然后是一个git push
。
深入了解哪个命令适合您的环境超出了本书的范围。事实上,有一些资料可以帮助您理解如何在您的工作流中使用 Git。
但是,当我们讨论拉和推的主题时,您总是可以独立于提交代码来执行这些操作。在 Visual Studio 的右下方,如图 5-22 所示,有一个区域表示您当前的分支。
图 5-22
状态栏上可用的 Git 命令
单击分支名称时,会出现一个包含多个选项的菜单。本章稍后将介绍分支和拉取请求选项。但是 Pull、Push 和 Fetch 命令与刚才描述的提交功能相关。
Pull –
从上游存储库中检索任何提交,并将它们合并到您的本地存储库中。
Push –
获取本地存储库中的任何提交,并将它们推送到上游存储库。
Fetch –
从上游存储库中检索任何提交,并让您决定是否要将它们合并到本地存储库中。这类似于拉取,但是没有自动合并到您的本地存储库中。
隐藏
当你使用 Git 工作时,不可避免的是,你最终会面临以下情况:你一直愉快地工作在你自己的分支上,做出改变,让测试通过,等等;您被要求审查存在于另一个分支上的代码(或者某人需要帮助解决某个问题,或者您想出的任何其他需要改变分支的理由);不提交代码就不能切换分支,但是您的工作还没有准备好提交。如何在不丢掉工作的情况下换到想要的分支?
答案来自于隐藏功能的形式。通过隐藏您的更改,您可以在保存它们的同时从当前分支中删除它们,以便将来可以恢复它们。一旦更改不在您当前的分支中,您就可以不受惩罚地切换分支。在您完成了这个新分支上的工作之后,您可以回到您原来的分支(或者实际上是任何其他分支),并且用您保存的变更来更新那个分支。
隐藏功能的起点是团队资源管理器中的“更改”窗格。正如你在图 5-23 中看到的,在提交暂存按钮的右边有一个存储选项。
图 5-23
在“更改”面板中隐藏菜单选项
第一个选项 Stash All 将所有的变更(不仅仅是阶段性变更…所有的变更)记录到一个 Stash 中。然后,它移除所有更改,将代码恢复到更改前的状态。如果您熟悉不同的 Git 客户端,您可能会看到这被称为放弃更改、撤销更改或恢复。第二个选项是 Stash All and Keep Staged,它记录所有的更改并保留任何已被登台的更改。未分级的更改将被反转。
一旦存储被创建,它就会出现在团队资源管理器中的 Changes 窗格的底部(图 5-24 )。
图 5-24
团队资源管理器中的隐藏
每个存储都用最初创建它的分支以及提交消息来标识。
您可以对单个存储执行四个操作。右键单击贮藏处可获得这些选项。从出现的上下文菜单中,您可以执行以下操作:
View Changes –
显示作为存储一部分的变更。它们以类似于“变更”窗格中的“暂存变更/变更”部分的格式显示。图 5-25 提供了图示。
图 5-25
存储详细信息
在这里,您可以看到已经转移的文件仍然与刚刚修改的文件分开。此外,还提供了存储库的描述、创建日期、存储库的版本,以及存储库基于哪个特定提交的指示符。表示存储基数的字符集合(在图 5-25 中,是 015f2ad9)是分支中最近完成的提交的散列的一部分。
Note
任何 Git 提交的散列都是提交的元数据的阿沙-1 散列,以及提交的内容。在 Git 中,散列被用作提交的惟一标识符。虽然完整的散列有 40 个字符长,但通常只是使用字符的子集来标识场景中的提交,如图 5-25 所示。
这些命令获取存储的内容并更新当前分支。这两个选项的区别在于分支更新后会发生什么。应用选项在更改完成后保留存储。Pop 选项会在更改完成后删除隐藏内容。
这两个选项都有两个额外的选择。图 5-26 显示了可用于应用的选项,但是 Pop 有同样的两个选择。
图 5-26
应用隐藏选项
第一个选项是分阶段应用和恢复,用存储中的所有更改更新当前分支。然而,在存储中进行的变更也将在分支中进行。第二个选项,Apply All as Unstaged,用所有的变更更新当前的分支,但是即使变更在 stash 中被登台,它们也将在分支中被登台。
Drop –
你可以使用的第四个命令实际上是从你的仓库中删除了毒品。当前分支中不会更新任何内容,该选择是永久性的。没有一种机制可以撤销一个存储的删除。
全部丢弃–
如果您的存储库中有多个存储,您还会看到一个全部丢弃选项。此选项不仅会删除当前选定的贮藏,还会删除所有其他贮藏。
使用分支和标签
前一节提到了分支,特别是作为关于隐藏的讨论的一部分,但并没有真正深入到它们是什么的任何细节,当然也没有触及如何在 Visual Studio 2019 中操纵它们。本节纠正了这种情况。
如果您熟悉 Git(或任何最常用的源代码控制系统),那么您对分支的概念并不陌生。如果您是 Git 的新手,那么考虑分支的最佳方式是一个独立的开发流。图 5-27 展示了一个简单的分支示例。
图 5-27
简单分支
图 5-27 中有两个分支:主和特征。 Master 是 Git 中默认分支的名称。考虑顶行上的每个圆圈,以指示对主分支的不同提交。在第二次提交之后,基于主服务器的当前状态,创建一个新的分支。那个分支叫做特征。现在,开发可以在主和特征上彼此独立地进行。如果事情在特征中严重出错,那么该分支可以被丢弃,而不会影响主。或者更有可能的情况是《??》中的工作是成功的。在这种情况下,功能的变化可以合并回主中,这样每个人都可以分享你的成功。
Git 的优势之一是能够快速创建新的分支,以及从一个分支移动到另一个分支。这给了开发人员很大的自由去试验不同的解决方案,安全地丢弃那些不工作的。或者你可以从处理一个特性转移到修复一个不同分支中的 bug,完成后很容易再回来。当然,Visual Studio 中的 Git 支持允许您利用所有这些能力。
创建分支
使用分支的第一步是创建它。Visual Studio 为您提供了许多不同的机制来实现这一点,具体取决于您的工作环境。功能最全的选项来自团队资源管理器。图 5-28 显示了团队资源管理器中的分支窗格。
图 5-28
团队资源管理器中的分支窗格
“分支”页面显示了本地存储库和远程存储库中定义的所有分支。该列表由活动储存库进一步分离。在图 5-28 中,有一个单独的活动存储库,称为 EssentialV2019。在该存储库的本地版本中,有两个分支,主和 159-Break _ version _ number _ into _ parts。当前检出的分支(即当前正在处理的分支)的名称是粗体的。远程存储库由 remotes/origin 节点表示,在该存储库中有许多分支。
要创建新分支,请单击分支标题下方的新分支链接。窗格的顶部展开以提供必要的选项,如图 5-29 所示。
图 5-29
“分支”窗格中的新分支信息
为了创建一个分支,需要提供两条信息。首先,在顶部的文本框中输入新分支的名称。然后,确定分支的基础。分支名称的正下方是一个下拉列表,其中包含您可以使用的所有分支,包括本地分支和远程存储库中的分支。选择所需的现有分支,然后单击“创建分支”以创建分支。您可能会注意到一个标有 Checkout branch 的复选框。如果您想在新分支创建后立即开始工作,那么请确保复选框被选中。否则,你将留在你目前的分支机构。
Note
如果使用远程分支作为基础创建分支,则远程分支将被设置为新分支的跟踪分支。这不会影响任何功能,但是当您将来需要这样做时,它会使您更容易将您的更改推回到基础分支。
创建新分支的第二种机制实际上是获得图 5-29 的不同方式。在 Visual Studio 状态栏的右侧,有当前分支的名称。当你点击它时,会出现一些额外的选项,如图 5-30 所示。
图 5-30
Visual Studio 状态栏中的分支选项
要创建新分支,请单击新建分支…菜单选项。这将启动团队资源管理器(如果尚未打开),并显示“分支”窗格,其中显示了创建新分支所需的控件。换句话说,看起来和图 5-29 一模一样。
还有一种创建新分支的方法。在“分支”窗格中,您可以右键单击任何分支,无论是远程分支还是本地分支。上下文菜单包括一个新的本地分支 from…菜单选项。当您点击该选项时,创建新分支所需的字段就会显示出来(同样,如图 5-29 ),不同之处在于您右击的分支被用作分支下拉列表的选定值。
您可能已经注意到,我们创建的唯一分支都是本地分支。虽然可以使用 Git Hub web 门户创建远程分支,但是在 Visual Studio 中没有这样做的机制。至少,不是直接的。
默认情况下,从远程分支创建本地分支会设置跟踪分支。现在,当您发出 pull、push 或 fetch 命令时,Visual Studio 会自动对跟踪分支执行该命令。并且这些操作中的每一个都是在不给你改变跟踪分支的机会的情况下执行的。诀窍是从取消设置跟踪分支开始。为此,在“分支”窗格中,右键单击本地分支并选择“取消设置上游分支”菜单选项。此时,本地分支和任何远程分支之间不再有关系。
下一步是将分支重新连接到远程分支。没有办法通过 Visual Studio 目录做到这一点。如果您没有将当前存储库与远程存储库相关联,您可以通过同步窗格来完成此操作。而是打开 Visual Studio 2019 Developer 命令提示符,导航到您的本地存储库所在的目录。然后执行命令git branch <branchName> -u origin/<remoteBranchName>,
,其中<branchName>
是本地分支的名称,<remoteBranchName>
是上游分支的名称。
切换分支
为了利用分支,您需要能够在不同的分支之间快速切换。这允许您利用 Git 分支的轻量级特性,根据工作需要切换开发上下文。
在 Visual Studio 中,有两个区域可以更改当前分支。首先,在团队资源管理器中,转到分支窗格(图 5-31 )。
图 5-31
团队资源管理器中的分支窗格
存储库下面是本地和远程分支的列表。如果右键单击一个分支,在上下文菜单中会有一个签出选项。选择该选项会导致当前分支切换到所选分支。如果您签出了一个远程分支,则会创建一个同名的本地分支,然后新分支会被设置为当前分支。并且远程分支(该分支的原始源)被设置为上游分支。您可以通过双击分支而不是使用上下文菜单来完成相同的功能。
第二个改变分支的地方是在 Visual Studio 底部的状态栏。如果点击状态栏右侧的当前分支,会出现一个菜单(图 5-32 )。
图 5-32
从状态栏切换分支
菜单顶部是本地分支机构的列表。当前签出的分支是灰色的,并且在左侧有一个复选标记。要签出另一个分支,请单击它。
合并分支
Git 中典型的开发流程包括创建一个本地分支,一直开发到完成,将您的更改提交到本地分支,然后将更新后的分支推回到远程分支。
如果您在一个有多个开发人员的系统上工作,那么在您进行开发时,其他人可能已经更新了远程分支。如果发生这种情况,流程中的实际步骤包括从远程分支提取任何更改,将远程更改与您的更改合并,然后将结果推回到远程分支。这个过程的一个关键部分是合并。
虽然如何成功执行合并的细节超出了本书的范围,但前提是简单明了的。如果同一个文件在两个分支中都被更改了,那么就比较代码的各个行。如果这种比较在两个分支中的文件内的相同位置发现变化,则识别出冲突。否则,这两项更改都将包含在合并的文件中。
虽然这种合并和冲突检测是作为 Git pull 的一部分自动发生的,但是您也可以在您选择的时间手动执行这种合并。为此,请进入团队资源管理器中的“分支”窗格。如果右键单击某个分支,其中一个选项是“从…合并”。选择此选项将在分支窗格顶部打开一个区域,如图 5-33 所示。
图 5-33
在团队资源管理器中合并分支
第一个字段是下拉列表,包含本地和远程分支的列表。在这里,您可以选择要合并的分支。第二个字段是您当前分支的名称。这只是为了提供信息。您不能对其进行更改,只能合并到您当前的分支中。此外,还有一个复选框,指示您是否希望在成功合并后立即执行提交。当这些值按照您的要求设置后,单击“合并”按钮开始该过程。
如果没有冲突,这两个分支将被合并,您可以继续您的开发。但是,如果合并检测到冲突,您必须在继续之前解决冲突或还原合并。
分支窗格中会出现一条消息,通知您存在冲突(图 5-34 )。
图 5-34
“合并分支”窗格中的冲突
在冲突消息的正下方有两个选项。如果不想继续合并,请单击中止链接。为了解决任何冲突(您可以从数字中看到此时只有一个冲突),请单击 conflicts 链接。这将显示图 5-35 中的解决冲突窗格。
图 5-35
团队资源管理器中的解决冲突窗格
这里列出了包含冲突的每个文件。在继续之前,需要适当地合并每个冲突的文件。当你点击一个文件时,显示可用于处理该文件的选项(图 5-36 )。
图 5-36
冲突解决方案选项
一旦选择了一个文件,就有许多选项可以帮助您决定如何完成合并。页面上有三个不同的链接:冲突消息下面的比较文件和编辑消息旁边的两个比较链接。这些链接中的每一个都会启动 Visual Studio 比较工具。链接之间的区别(双关语)是被比较的文件。
图 5-37
Visual Studio 中的比较工具
图 5-37 显示了 Diff 工具结果的样本视图。这个想法基本上是为了突出两个不同文件之间存在的差异。Diff 过程有一个目标和一个源。当一行发生变化时,它将在源中显示为浅红色,在目标中显示为浅绿色。如果在源和目标之间删除了一条线,则该线在源中显示为浅红色。如果在目标中添加了一行,它会以浅绿色突出显示。
前面提到的三个链接都导致相同的视图。但是他们使用不同的来源和目标。“比较文件”链接比较已经提交给正在合并的两个分支的文件。Diff 链接将当前本地文件(不一定是已提交的)与特定分支中的文件实例进行比较。
为了通过解决冲突来帮助完成合并,Visual Studio 包括一个内置的三向 Diff/Merge 工具。除非您已经修改了 Git 的设置(本章稍后讨论)以使用不同的工具,图 5-38 包含了您将看到的示例。
图 5-38
Visual Studio 中的差异/合并工具
使用 Diff/Merge 工具的目的是让您轻松定义正确的合并结果。窗格顶部有许多图标和消息来帮助您浏览文件。
首先,冲突的线条以浅橙色/棕色显示。每一行旁边都有一个复选框。要指出某一行应该包含在合并文件中,只需确保复选框被选中。您可以同时包括其中一行或两行。结果显示在底部的窗格中。底部窗格也可以直接编辑,这样您就可以添加创建正确结果所需的任何附加代码。
这两个文件之间可能还有其他不会导致冲突的差异。它们由之前在 Diff 工具中描述的相同配色方案来表示。
页面顶部会显示一条消息,指出已识别的冲突数量以及尚未解决的冲突数量。您可以手动浏览文件,查找冲突(它们在滚动条中显示为橙棕色的点)。或者您可以使用工具栏上的前四个图标导航到第一个、上一个、下一个和最后一个冲突(按顺序)。
接下来的两个图标(它们是填充了最左边或最右边形状的三个矩形)用于从左边或右边的文件中获取所有的更改。
下一个图标(看起来像图 5-38 中窗格的基本布局)用于改变窗格的组织。除了示例中的二对一布局之外,您还可以垂直或水平排列窗格。
接下来是一个图标,用于直接从 merge 启动 Diff 工具。这三个选项与“解决冲突”窗格中描述的选项相同。您可以在源和目标、源和基础(本地版本)或目标和基础之间执行差异。
最后,最后一个图标用于将焦点设置在三个窗格中的一个上。虽然您不太可能直接使用这些菜单项(只需单击窗格会更容易),但这些选项有相关的快捷键,允许您在窗格之间切换焦点,而不必将手指从按键上移开。
根据需要使用尽可能多的工具,将文件转换到一个正确的状态,以便合并两个冲突的文件。准备好文件后,单击接受合并以完成冲突解决过程。这将获取合并的文件并使其成为本地文件。如果有更多冲突的文件,请依次修复每个文件。完成后,合并会自动标识为完成。如果您在启动合并时已经指出您希望在合并完成时提交合并的文件,那么提交将会完成。更重要的是,你能够继续正常发展。
你甚至不需要使用合并工具来解决合并冲突。Git 表示在文件本身中查看源文件和目标文件所需的信息。例如,下面是产生图 5-38 中冲突的代码行:
<<<<<<< HEAD
<p>Conflicting change. This is the updated privacy policy. As you can tell by the number of characters in this page, privacy is something that we take very seriously.</p>
=======
<p>Addition change. This is the updated privacy policy. As you can tell by the number of characters in this page, privacy is something that we take very seriously.</p>
>>>>>>> 153-Update_privacy_message_on_main_screen
向左和向右指向的人字形集合是冲突的警告标志。以<<<<<<< HEAD
开始并以=======
结束的部分是当前在本地文件中的代码。以=======
开始并以>>>>>>> <branchName>
结束的部分是来自合并文件的代码。<branchName>
是您要合并的分支的名称。您可以进入您喜欢的文本编辑器,直接进行更改,然后删除用于分隔冲突部分的行。一旦进行了这些更改并保存了文件,它就不再被认为是冲突的。
重建树枝基
在 Git 中合并文件的想法是将两个文件的内容结合起来,创建一个包含两个更改的文件版本(或者基于人工干预的更改的修改)。对于大多数情况,这是获取一个分支的内容并将其添加到另一个分支的最简单的方法。
然而,Git 有第二种技术来组合两个分支:rebasing。使用 rebasing,而不是合并文件,您从一个分支获取提交,并将它们重新应用到当前分支。实际步骤如下:
-
定位两个分支(当前和传入)之间最近的共同祖先。
-
收集传入分支中每个提交中的更改所生成的文件差异。
-
将当前分支重置为最近一次提交。
-
按照发生的顺序,应用传入分支提交的每个文件差异。
-
执行快进合并。
当你完成了重新定基,你应该有一组与合并相同的文件。换句话说,从结果的角度来看,重定基础和合并是同一个来源。不同之处在于存储库中的历史记录。在合并过程中,两个分支的历史被合并。大多数 Git 历史可视化工具将合并显示为两个单独的历史路径,它们在创建源分支时分开,然后通过合并再次结合。重定基础文件的历史是线性的。您会看到直到最近一次提交之前的所有更改。然后您会看到来自文件差异的提交。结果是历史是线性的,而不是分支的。
在 Visual Studio 中,重置基础的过程与合并非常相似。它从团队资源管理器开始。在“分支”窗格中,顶部有一个标记为 Rebase 的链接。点击图标会在窗格顶部打开一个区域,如图 5-39 所示。
图 5-39
团队资源管理器中的重置基础
通过获取当前分支并将变更推送到另一个分支来执行基础变更。当您单击 Rebase 链接时,顶部的文本框包含当前的分支名称(并且您不能更改它)。该字段下方是包含本地和远程分支的下拉列表。如果您选择了一个本地分支,单击 Rebase 按钮会导致当前分支重新基于所选分支。如果您选择了一个远程分支,那么该分支将被拉入到一个本地分支中,其名称与执行 rebase 之前的名称相同。
也可以通过右键单击 full Branches 窗格中的一个分支来访问 rebase 选项。在这种情况下,您右键单击的分支将成为 rebase 的目标(除非您右键单击当前分支,在这种情况下,下拉列表为空)。
删除分支
在 Visual Studio 中,删除分支是一个简单的过程。在团队资源管理器的“分支”窗格中启动。右键单击分支并从上下文菜单中选择删除。如果该分支有尚未被推送到其上游存储库的提交,您将得到一条这样的消息。您可以选择继续(相当于- delete 选项)或取消删除。
如果右键单击远程分支,删除选项将标记为“从远程删除分支”。您将收到一条消息,要求确认删除,如果您接受,那么分支将从原点删除。
拉取请求
至此,当我们谈论将代码推送到另一个分支时,您负责整个过程–
确保传入的代码是好的,执行合并,并确保合并的代码得到测试。如果你负责这个项目,那很好。但是随着越来越多的人参与进来,通常会有一个或更多的人来担当传入代码的看门人的角色。在这种情况下,当代码被添加到项目中时,把关者负责确保质量和功能得到维护。您通常会在拥有一个或多个开发团队的开源或大型项目中看到这种结构。
在这种情况下,允许每个人推送代码可能会很危险。虽然大多数开发人员不会引起问题,但一个错误的承诺会严重影响项目的质量和声誉。为了解决这个问题,Git 支持拉请求的思想。使用 pull 请求,而不是将您的更改直接合并到一个分支中,这些更改被打包,以便可以对它们进行审查。这个包是 pull 请求,包含其他信息,比如标题、描述和相关的工作项。然后,拉取请求由有权批准拉取请求的人进行审阅。当拉请求被批准时,代码被合并到目标分支中。
Visual Studio 2019 提供对创建拉请求的支持,只是因为它在 Team Foundation Service 或 GitHub 中启动新的拉请求网页,并填入适当的信息。若要启动该页面,请在团队资源管理器的“分支”窗格中,右键单击某个分支,然后从上下文菜单中选择“创建请求”选项。当您单击状态栏右侧的分支时,可以从出现的菜单中找到相同的选项。当点击该选项时(并且您连接到位于 TFS 的存储库),以下页面(图 5-40 )将在您的默认浏览器中打开。
图 5-40
创建新的拉取请求
能够创建拉请求的标准之一是两个分支(源和目标)都需要在服务器上可用。换句话说,您不能将请求从本地分支拉至远程分支。因此,为了创建一个拉请求,您必须提交到本地分支,然后将提交推送到上游分支。当您进入新的拉取请求屏幕时,所选择的分支(或其上游伙伴)出现在左上方的下拉列表中。下拉列表的右侧是拉取请求的目标。这两个都可以在这个屏幕上修改。
在目标和源分支下面是拉请求的元数据。标题必选,强烈建议描述。描述的右下方有一个链接,用于将提交消息添加到描述中。然而,这可能还不够。在编写描述时,要考虑到将来有人会仔细阅读描述,以了解拉请求中的内容。换句话说,帮你未来的自己一个忙(因为,不可避免地,你将是阅读描述的人),尽可能清楚和完整地了解拉取请求中的内容。
在描述下面是作为拉请求的审阅者的人员列表。存储库的策略可以自动添加审阅者(个人或组),但是您也可以指定特定的人。将向这些人发送电子邮件,通知他们拉取请求已经创建。
还有一个将工作项与拉请求相关联的地方。如果您这样做,拉请求将成为工作项关联的一部分。它允许其他人查看哪些拉请求可能已经被接受,以完成开发或修复工作项中描述的问题。
元数据下面(在图 5-40 中不可见,除了标题)是每个文件中所做的更改,以及作为拉请求一部分的提交。最好检查一下这些,以确保只有您期望的更改是拉请求的一部分。
信息准备就绪后,单击 Create 创建拉请求。还有一个选项可以将拉取请求创建为草稿。这允许您稍后再回来填写缺失的信息或添加文件。需要记住的一点是,当拉请求打开时,对同一远程分支的后续提交会自动包含在拉请求中。
创建完成后,包含拉取请求信息的页面如图 5-41 所示。
图 5-41
Visual Studio Online 中现有的拉请求
审阅者和创建拉请求的人都可以看到该视图。在某些时候,评审者将批准(或拒绝)拉取请求。这可能是在提出需要解决的意见之后。但是在查看拉请求时,可以通过单击 Complete 按钮来完成。此时,作为拉请求一部分的变更将被合并到目标分支中。
查看代码的历史记录
跟踪一段时间内对文件所做的更改的能力是识别新出现的 bug 的来源的一个很好的方法。毕竟,如果代码过去可以工作,现在不行了,那么在此期间所做的任何更改都是开始寻找 bug 的好地方。
查看任何文件的历史记录的起点是解决方案资源管理器。右键单击所需文件,然后从上下文菜单中选择“查看历史记录”。这将显示历史表格,如图 5-42 所示。
图 5-42
Visual Studio 2019 中的简单历史视图
对文件所做的每个更改都会出现在此列表中,包括提交哈希、提交消息、提交日期和时间、提交者以及开发人员的图片(如果有)。这是简单的历史观。点击工具栏左侧第二个图标,查看详细历史视图(图 5-43 )。
图 5-43
Visual Studio 2019 中的详细历史视图
详细视图中可用的信息与简单视图非常相似,只是开发人员的图片不可见。让读者来决定这是多大的损失。
可以显示更完整的历史。首先,右边的最后一个图标用于显示应用于提交的所有标记。右边倒数第二个图标包括提交所来自的远程分支。另外,右边第四个按钮(带时钟的那个)显示完整的历史记录。默认情况下,合并提交不在显示范围内。完整的历史记录会将这些提交添加到列表中。图 5-44 说明了不同的远程分支标签和合并提交。
图 5-44
包含所有提交和远程分支标签的历史视图
有些操作可以在单个提交上执行,这样您就可以更近距离地看到所做的更改。要启动其中的每一个,请通过单击第一个并按住 Ctrl 键单击第二个来选择两个不同的提交。那么上下文菜单将包括以下选项:
图 5-45
团队资源管理器中的“比较提交”窗格
-
这将启动“合并分支”一节中描述的 Visual Studio Diff 工具。
-
比较提交
–
该选项在团队资源管理器中打开“比较提交”窗格(图 5-45 )。
此窗格的目的是让您在文件视图中查看正在比较的两个提交。这两个提交的详细信息占据了窗格的大部分。下面是两次提交之间修改的文件。如果双击任何一个文件,就会打开 Diff 工具,比较两个不同提交中的选定文件。
Git 设置
Git 有各种各样的设置可供操作。通常,这些设置是使用 Git 命令行配置的。但是,Visual Studio 允许设置一些基本信息和默认值,包括所有 Git 函数和每个存储库。可以通过单击团队资源管理器主页上的“设置”按钮来访问这些设置。这将显示设置窗格,如图 5-46 所示。
图 5-46
团队资源管理器中的设置窗格
在这个设置列表的底部有两个链接,带您到专门与 Git 相关的独立窗格。全局设置窗格(图 5-47 )控制每个 Git 存储库的设置。
图 5-47
团队资源管理器中的全局 Git 设置窗格
在窗格顶部,您可以为任何新的存储库设置用户名、电子邮件地址和默认位置。接下来的几个设置不太简单。
在提取期间修剪远程分支–如果设置为 True,则在执行提取时,如果本地分支的上游分支不再存在,则删除本地分支。
提取时重设基础本地分支–此设置控制提取时是否自动重设基础。如果设置为 True,则每次拉取都会产生一个基数。将此项设置为 False 意味着 pull 执行合并,而不是 rebase。Preserve 选项使 rebase 在 pull 上执行,但是本地分支上的任何合并提交都被保留。这个设置其实是有交互选项可用的,但是 Visual Studio 2019 内不支持。如果需要,可以通过 Git 命令行使用git config pull.rebase
设置来设置该选项。
加密网络提供者–指定应该使用哪个 SSL 后端与 Git 服务器通信。选项有 OpenSSL 和安全通道。它们都支持 TLS 和 SSL 协议。不同之处在于,安全通道将访问 Windows 凭据存储,这使得它成为管理用于与 g it 服务器通信的证书的企业的合适选择。
启用下载作者图像–选中时,将从 Gravatar 图像源(GitHub 的图像源)下载作者图像。
默认情况下,合并后提交更改——这个选项非常简单明了。如果选中,则在成功合并后,将立即执行提交。
启用 push-force–选中时,执行的任何推送都将使用force
选项执行。当在没有强制的情况下执行推送时,将执行检查,以查看自上次推送以来是否进行了任何提交。如果有过,你会被告知先拉再推。这将导致任何变更都被合并到您的分支中,并在您可以推送之前完成任何必要的冲突解决。使用force
选项可能是危险的,因为有可能覆盖上游分支中的更改。
“全局设置”窗格的底部是正在使用的“比较”和“合并”工具。通常,这些工具是使用git config
命令行命令设置的。但是,如果要将 Visual Studio 工具设置为默认工具,可以单击“使用 Visual Studio”链接。
存储库设置类似于全局设置。图 5-48 展示了一个典型的储存库。
图 5-48
团队资源管理器中的存储库设置
窗格的上半部分包含“全局”窗格中的一些设置。您可以覆盖用户名和电子邮件地址。您还可以在提取时设置修剪远程分支,在提取时设置重置本地分支以覆盖全局值。
在这些可重写设置的下面是。gitignore
和.gitattributes
文件。这些是包含模式集合的标准 Git 文件。对于.gitignore
,如果一个文件路径匹配模式,它将被 Git 忽略。对于.gitattributes
,如果一个文件路径与模式匹配,那么一组属性将被应用于该路径。每个文件都包含一个编辑链接,打开文件允许您直接编辑。
在窗格的底部,有一个部分定义了当前本地存储库(即上游存储库)的远程存储库。有可能有不同的存储库,每个都有一个单独的名称。默认名称为 origin,如图 5-48 所示。但是您可以添加新的远程定义,以及编辑现有的远程服务器。点击添加或编辑打开一个对话框(如图 5-49 所示),让您指定存储库的路径。
图 5-49
编辑远程存储库 URL
远程存储库的名称在第一个字段中。另外两个文本框用于获取和推送 URL。虽然它们通常是相同的 URL,但也不一定是相同的。选中推送匹配获取复选框后,将自动同步获取和推送 URL。如果它们需要不同,则取消选中该选项,并将每个 URL 设置为适当的值。
摘要
Git 已经紧密集成到 Visual Studio 2019 中。部分原因是因为 Git 在许多不同的开发环境和平台中的广泛使用。这至少部分是因为微软最近收购了 GitHub,这是一个领先的 Git 项目库,包括开源项目和个人项目。随着时间的推移,可能会有更多的 Git 函数可用。
虽然 Git 是一个协作工具,因为许多开发人员可以同时在同一个代码库上工作,但它不是 Visual Studio 中可用的全部协作。在下一章中,我们将了解 Visual Studio 2019 支持的一些交互式协作体验。
六、协作
上一章描述的 Git 功能的核心是协作的概念。你修改代码。其他开发人员对代码进行更改。这些变化被合并成一个单一的功能单元。拉请求更进一步,在被分支接受之前,潜在的提交需要被检查和评论。
但这并不是 Visual Studio 2019 中可用的协作的限制。有几个新特性将代码审查、结对开发和辅助调试提升到了新的水平。在这一章中,详细讨论了协作开发、实时共享和集成代码评审的焦点。
Visual Studio 实时共享
Visual Studio Live Share 背后的前提非常令人印象深刻。使用 Visual Studio 或 Visual Studio 代码,您可以与其他开发人员协作实时编辑和调试您的应用–
,参与者可以查看应用代码和调试信息、执行终端命令、转发本地主机请求以及通过语音呼叫进行通信。想象一下 Skype、Visual Studio 和远程调试的组合,你就有了想法,至少有了功能组合的想法。实现更加令人印象深刻。
系统需求
对于 Visual Studio 2019,主要要求是安装支持 Live Share 的工作负载。这包括 ASP.NET。NET Core、C++(具有 C++工作负载的桌面开发)、Python 和 Node.js,对于 Visual Studio 2017,需要运行 15.6 以上版本。同样,您需要安装一个名为 Visual Studio Live Share 的扩展。它可以在 Visual Studio 市场上获得。对于 Visual Studio 代码,需要相同的扩展(来自市场的 Visual Studio Live Share)。
出于完整性考虑,并不是每种语言都受到同等程度的支持。您可以将实时共享功能分为三个高级别组:
-
共享语言服务
–
参与者看到一个人所做的编辑会立即出现在他们的编辑器中。您还可以看到他们的光标位置和选择。此外,您可以跟随其他参与者(自动导航到他们正在导航的位置)或发送和接收焦点通知(要求参与者将注意力集中在您的编辑上)。 -
共享调试
–
参与者能够在同一个调试会话中工作。这包括查看状态和同步执行的能力。 -
应用共享
–
参与者共享应用的执行。例如,对于一个 web 应用,他们可以向网站发出请求,即使它运行在另一个参与者的本地主机上。对于非 web 应用,它们可以像在本地运行一样与应用进行交互。
对于给定的语言和平台(Visual Studio 与 Visual Studio 代码),提供了不同级别的支持。主要是因为 Visual Studio 代码中可用的语言范围非常广泛。对于 Visual Studio,共享语言服务支持最常用的语言(包括 C#、Python、C++、VB.NET 和 F#)。在撰写本文时,最显著的例外是 R,尽管它在 Visual Studio 代码中受支持。并且这些语言中的大部分也可用于共享调试(有趣的是,R 是可用于协同调试)。例外是那些没有真正执行的语言(CSS,HTML,XAML,Markdown,SQL)。
如果参与者使用的是 Visual Studio 代码,那么共享语言服务和共享调试所支持的语言列表会更长,也更奇特。
说到 App 分享,支持的开发平台也很广泛。支持大多数预期的环境(web 前端和后端、控制台应用)。同样,有许多环境可能会令人惊讶,包括 Kubernetes、Azure functions、Ethereum 和使用 Cordova 或 React Native 的移动开发。总而言之,这个列表足够完整,可以涵盖大多数协作情况。
连通性
实时共享支持三种连接模式:
-
主机和客户机可以通过本地网络直接通信。
-
主机和客户机之间的通信是通过 Azure 服务总线完成的。
-
Auto
–
允许直接连接,通过本地网络,或者通过 Azure 服务总线。
建立实时共享连接时,需要完成许多步骤。首先,主机建立会话。这样做时,它可以指定所有连接都必须通过两种连接模式之一进行。换句话说,主机可以通过指定直接连接类型来确保只有本地网络上的参与者才能加入。
一旦建立了主机会话,就允许来宾连接。与主机一样,它们可以选择直接、中继或自动连接模式。对于 guest 虚拟机,自动模式将通过检查是否可以直接连接到主机来启动。如果可用,则使用该连接。如果没有,则会话退回到中继模式。
所有连接都是 SSH 或 SSL 加密的,因此除了参与者之外,任何人都看不到任何通信。但是,有些端口需要开放,以便能够托管或参与。对于直接连接,使用 5990 和 5999 之间的端口。对于继电器连接,您必须能够访问*.servicebus.windows.net:443
。
安全性
为了主持或连接到实时共享会话,您必须通过 Azure Active Directory 帐户、Microsoft Live 帐户或 GitHub 帐户进行身份验证。使用内部 Active Directory 帐户或 GitHub Enterprise 帐户无法连接到 Live Share,尽管微软正在考虑将它们作为未来的增强功能。
主持实时分享会议
要开始实时共享会话,您需要执行两个步骤。首先,您需要登录到 Visual Studio。这是您第一次安装 Visual Studio 时会被问到的问题。如果您已经登录,右上角将类似于图 6-1 。
图 6-1
Visual Studio 中的登录帐户信息
您保持登录状态,因此如果您在安装后没有进行过身份验证,请不要感到惊讶。如果您还没有登录,那么在图 6-1 中的圆圈处有一个登录标签。单击该标签将启动身份验证过程。一旦完成,你就完成了分享生活的一半。
第二步,也是比较简单的一步,实际上有两个部分。从打开或创建一个解决方案开始。这样做的目的是在 Visual Studio 中打开您想要共享的解决方案。一旦解决方案准备就绪,点击实时共享标签,如图 6-2 所示。
图 6-2
实时共享链接
如果您没有看到这个实时共享链接,可能是因为您没有安装包含它的任何工作负载。您可以这样做(该列表位于本章开头),或者直接从 Visual Studio Marketplace 安装。
单击实时共享开始共享您的环境。第一次这样做时,您可能会看到许多对话框,要求您配置 Windows 环境。如果您在自动模式下使用实时共享,并且 Windows 防火墙中未启用适当的端口范围,则会出现图 6-3 。
图 6-3
实时共享防火墙访问对话框
如果您点击 Ok,那么您将几乎立即看到如图 6-4 所示的对话框。
图 6-4
用于实时共享的 Windows 防火墙警报
此对话框由 Windows 防火墙显示,它表示由名称、发行者和路径详细信息描述的应用正在请求访问。在这种情况下,代理用于托管实时共享。单击“允许访问”会打开必要的端口。
如果您使用不同的防火墙或安全包,您在图 6-3 后看到的对话框可能会有所不同。或者您可能要求管理员通过全局设置打开端口。请记住,出现这些对话框是因为实时共享模式是自动的。可以通过 Live Share Settings(实时共享设置)屏幕更改默认设置,本节稍后将对此进行介绍。
配置端口访问后,实时共享会话将开始。设置可能需要几分钟时间,这取决于您的计算机和网络连接的速度。但是一旦它开始运行,你的 Visual Studio 将看起来类似于图 6-5 。
图 6-5
运行实时共享的 Visual Studio
第二次及以后共享时,您将看不到“入门”窗口。相反,只显示“实时共享”窗格(在右侧)和邀请链接消息(黄色,紧靠工具栏下方)。但是,您可以通过单击邀请链接消息中的“更多信息”标签来取回它。
除了显示入门页面,邀请链接消息也是在列表共享会话开始时获取您可能感兴趣的信息的快捷方式。默认情况下,可用于加入会话的 URL 会粘贴到剪贴板。这样,您可以不费吹灰之力就将链接发送给任何可能感兴趣的人。在链接消息中,您可以使用“再次复制”链接将链接重新添加到剪贴板。您也可以使用“设为只读”链接将会话转换为只读。
但是邀请链接消息只是一种方便。功能的真正来源在实时共享窗格中。图 6-6 显示了与一个参与者(即除主持人之外的一个参与者)的活动会话的实时共享窗格。
图 6-6
活动托管会话的实时共享窗格
窗格的主体是会话中的活动参与者、服务器和终端的列表。每个标题下列出了每种类型组件的实例。然而,当谈到您可以采取的行动时,大多数功能都在工具栏中。除了两个例外,它们将在接下来的章节中讨论。
两个例外是最右边的图标(红色方块)和分隔线右边的图标(看起来像复制图标和链接图标的组合)。方形图标用于终止实时共享会话。复制链接图标用于将用于连接到实时共享会话的 URL 的副本放置到剪贴板上。然后,可以通过任何方式(电子邮件、Skype、团队、Slack 等)将该 URL 发送给任何有兴趣加入该会话的人。)你更喜欢。
尽管并非所有功能都可用,但实时共享窗格对参与会话的每个人都可见。
作为实时共享会话的主持人,每当被邀请人加入您的会话时,您都会收到通知。您会在 Visual Studio 的右下方看到通知(图 6-7 )。
图 6-7
你收到通知了
通知本身(图 6-8 )显示了加入者的姓名和电子邮件地址。
图 6-8
当某人加入实时共享会话时出现的通知
“实时共享”部分的其余部分介绍了会话活动时可用的不同协作工具。
实时编辑
人们可以很容易地将实时编辑(或协同编辑)描述为实时共享的基本功能。前提很简单。您可以近乎实时地看到会话中每个人的更改、选择和光标定位。当有人打开文件时,该文件将在您的 Visual Studio 实例中打开。当有人做出更改时,该更改会出现在您的编辑器中。同样,您也可以获得参与者的姓名。图 6-9 提供了一个 Visual Studio 的两个实例的例子。
图 6-9
在多个 Visual Studio 实例中进行实时编辑
尽管图 6-9 中的两个 Visual Studio 实例都是由同一个人(可能是作者)运行的,但其目的是演示每个人在实时编辑时看到的内容。
对于 top 实例,光标位于以"Logging"
开始的行的开头。这在底部实例中由一个看起来像胖 t 形的蓝色指示器表示。当您将鼠标悬停在指示器上时,光标所在的参与者的姓名会显示为工具提示。
在底部实例中,光标位于最后一行的末尾。您可以看到在顶部实例中用 T 指示器表示。
通过这个简单的界面,每个参与者都可以看到其他人在做什么。除了光标定位之外,一个参与者所做的选择也会显示给其他参与者,如图 6-10 所示。
图 6-10
协作选择
与选择一样,一个参与者所做的任何编辑对所有其他参与者几乎实时可见。如果你在一个高速网络上工作,“接近实时”是足够接近的,就好像你的同事在你的屏幕上编辑的同时在他的机器上打字。
专注并跟随
Live Share 有一个关注一个参与者的概念。如果您跟随一个参与者,这意味着无论那个人在 Visual Studio 中做什么,您的 Visual Studio 实例都会跟随。这包括编辑、光标放置、选择和导航到其他文件。
图 6-11
关注实时共享中的参与者
图 6-11 展示了两种不同的方式来查看您当前关注的人。在 Visual Studio 右上角的工具栏中,有一个圆圈集合,每个参与者一个。它们包含参与者的姓名首字母,但是如果您将鼠标悬停在它们上面,则可以看到全名。
BJ 参与者周围的双圆圈表示您当前正在关注此人。您可以随时通过点按圆圈或点按另一个参与者的圆圈来停止关注。
与工具栏一样,实时共享窗格也显示您当前关注的人。其他每个用户都可以在参与者节点下的列表中看到。每个名字旁边都有一个圆圈。如果圆圈是空的,那么你没有跟随那个人。如果圆圈有一个中心点(如图 6-11 所示),那么你正在跟随那个人。您可以通过单击其他参与者来更改您关注的人。或者,您可以通过点按某人的姓名来停止关注他。还有上下文菜单选项来关注和取消关注个人。
如果您正在进行实时共享会话,有时您可能希望其他参与者关注您正在做的事情。这是通过一个叫做聚焦的特性来实现的。您向每个参与者发送通知,请求他们跟随您。
您可以通过两种不同的技术发送焦点通知。首先,在 Visual Studio 右上角的工具栏中,有一个标记为“共享”(如果您是主持人)或“加入”(如果您是参与者)的下拉列表。此下拉列表包含许多实时共享命令的执行点。在这种情况下,从下拉列表中选择焦点参与者。这将生成一个发送给每个参与者的通知(见图 6-12 )。
图 6-12
焦点通知
除了通知之外,参与者还会被自动设置为关注您。自然地,他们总是可以选择不跟随,使用前面段落中提到的技巧。
联合调试
Live Share 提供的更有用的特性之一是协作参与调试会话的能力。启动联合调试会话就像在 Visual Studio 中启动调试会话一样简单。一旦宿主的调试器附加到进程,每个参与者的 Visual Studio 实例都将连接到调试会话。从参与者的角度来看,他们看起来和感觉起来都像是在本地调试。
当遇到断点时,每个参与者的实例也会停止。同样,这看起来像一个典型的调试会话。事实上,参与者可以像在本地运行调试会话一样探索与调试会话相关联的值。考虑图 6-13 中显示的两个 Visual Studio 实例。
图 6-13
联合调试时的断点和显示值
托管会话是出现在图 6-13 顶部的实例。但是,在底部的实例中,将鼠标悬停在 ViewData 变量上会显示包含当前属性值的工具提示。
Note
对于参与者来说,调试会话中显示的属性值是不可修改的(与在本地运行时能够修改它们相反)。
在调试会话中,参与者可以设置断点、查看正在使用的变量值、查看调用堆栈以及导航到进程中的任何其他文件。换句话说,您可以自由探索不同的选项来帮助确定被检查问题的原因。
自动连接到主机的调试会话。也就是说,一旦为调试设置了主机,参与者就可以轻松地连接起来。有时候这种行为可能会适得其反。可以配置主机调试会话开始后发生的事情。单击工具➤选项菜单项,然后导航至实时共享(图 6-14 )。
图 6-14
实时股票期权
调试会话加入行为选项有自动(默认)、手动(因此您必须使用下一段中描述的技术加入会话)和提示。
您可能没有连接到宿主的调试会话,因为您选择了“手动”选项,在出现提示时回答“否”,或者只是停止了调试会话(这将阻止您跟踪该会话,但该会话将对其他所有人继续进行)。如果您希望加入正在运行的调试会话,可以采用与启动本地调试会话相同的方式。在工具栏中,运行选项包括一个下拉菜单,为您提供了许多启动选项。图 6-15 举例说明了您在实时共享会话中的情况。
图 6-15
实时共享时运行选项
选择指示您正在附加到 GUID 的项目将使您加入实时共享调试会话。该 GUID 与链接中显示的 GUID 相同,该链接用于连接到实时共享会话。
共享服务器
现代应用可能是由不同服务器组成的复杂网络。认证服务器、微服务端点和基于 REST 的端点都可以是正在运行的应用的一部分。当让其他人参与调试过程时,允许实时共享参与者访问不同的端点会很有帮助。这样,他们就可以以类似于本地调试时的方式与应用进行交互。
当您为联合调试会话启动 web 应用时,web 服务器会自动与其他参与者共享。您可以在实时共享窗格中看到共享的服务器(图 6-16 )。
图 6-16
共享服务器的实时共享窗格
在本例中,名为 SampleWebApplication 的 web 应用在端口 44399 上可用。这意味着,如果参与者使用浏览器导航到http://localhost:44399
,他们将访问运行在实时共享主机上的服务器。请注意,这也意味着它们会遇到在该服务器的执行路径中定义的任何断点。如果他们正在联合调试,他们会看到断点命中,但如果没有,参与者可能会等待很长时间的响应。
但是共享 web 应用端口并不总是足够的。主持人可以专门共享额外的服务器供参与者交互。这是通过使用管理共享服务器命令来完成的。您可以单击实时共享窗格工具栏中的图标(图 6-16 中右数第二个)或使用 Visual Studio 工具栏中的共享下拉菜单。共享的本地服务器如图 6-17 所示。
图 6-17
共享本地服务器对话框
此对话框用于管理主机上运行的服务器,这些服务器可供实时共享参与者访问。在捕获图 6-17 的图像时,SampleWebApplication 服务器正在运行。通过单击“添加”按钮,可以添加不同的服务器。您需要提供服务器的端口号和名称。添加服务器后,参与者将可以在实时共享窗格中看到该服务器。他们将能够通过浏览器或代码或任何他们喜欢的方法来访问它。
在此对话框中,您还可以删除共享服务器(通过选择然后单击删除),在浏览器窗口中打开特定服务器,或将服务器的 URL 复制到剪贴板。
共享终端
有时,命令行功能可能需要由实时共享参与者使用或向他们演示。为了支持这一点,实时共享包括与参与者共享终端会话的能力。这意味着他们可以看到主机在终端窗口中输入的命令及其结果。这也意味着他们可以自己执行命令。
在这两种情况下,共享终端会话都是从主机环境中的两个位置之一开始的。在实时共享窗格中,单击共享终端图标(从右数第三个,见图 6-18 )并从两个选项中选择一个。
图 6-18
在“实时共享”面板中共享终端
或者,Visual Studio 工具栏中的共享图标包括图 6-18 中可见的两个选项。
你有两个选择。在第一个只读终端中,您与每个参与者共享终端窗口以及所有内容。但是您没有共享参与者执行命令的能力。使用第二个选项,读写终端,您允许参与者执行他们自己的命令,完成该功能的所有潜在安全隐患。
无论选择哪种方式,共享终端都会打开一个单独的终端窗口。至少,这是 Visual Studio 的默认行为。由于 Visual Studio 不包括现成的集成终端窗口,因此需要一个单独的窗口。但是,您会在工具栏下方看到一个通知(如图 6-19 所示)。
图 6-19
终端共享通知
通知消息中的一个链接 Install integrated terminal 会将您引导到 Visual Studio Marketplace,在那里您可以安装 Whack Whack 扩展,这是一个用于 Visual Studio 的集成终端窗口。
实时共享的安全性
作为联合调试功能的一部分,实时共享参与者可以访问命令和即时窗口。从联合调试的角度来看,这提供了更接近本地调试的丰富调试体验。绝对是加分项。从安全角度来看,有点不一样。当您在联合调试会话中使用这些窗口时,命令正在主机的计算机上执行。这种访问的安全后果应该显而易见。参与者可能有权使用会话主机的授权来执行他们想要的任何命令。这包括潜在的破坏性命令,如清除整个文件系统。因此,信任您邀请参加实时共享会话的人非常重要。
在实时共享会话中使用共享终端时,也会出现同样的安全问题。如果你想确保参与者不能在你的机器上执行命令,那就只共享一个只读终端。否则,您必须相信参与者在连接时不会恶意行为。
同样,在实时共享会话中进行编辑时,参与者可以看到共享项目的整个文件树。参与者不需要跟着你,允许他们随意在树中导航。如果文件中嵌入了秘密(如服务器或数据库密码),参与者就可能发现它们。
就文件访问而言,降低一些风险是可能的。.vsls.json
文件包含用于控制参与者对文件的访问权限的设置。下面是一个.vsls.json
文件的例子:
{
"$schema": "http://json.schemastore.org/vsls",
"gitignore":"none",
"excludeFiles":[
"*.cer",
"*.config",
".gitignore"
],
"hideFiles": [
"bin",
"obj"
]
}
文件中有三个属性用于控制访问:
-
exclude filles
–
定义了不允许参与者打开的文件和文件夹列表。这种限制包括您可能希望能够访问这些文件的情况,例如当您在调试会话期间单步执行某个文件时。这样做的目的是包含参与者永远看不到的文件。 -
hideFiles
–
定义了在树中不可见的文件和文件夹列表。但是,如果文件是通过导航出现的(比如找到一个定义),或者是在调试过程中出现的,参与者将能够导航到这些文件。 -
默认情况下,
.gitignore
文件中的文件被视为隐藏。但是,您可以使用该属性来更改该行为。可能的值如下:-
none
–``.gitignore
内容对项目树中的可见内容没有影响。 -
隐藏
–``.gitignore
内容被视为hideFiles
属性的一部分。 -
排除
–``.gitignore
内容被视为excludeFiles
属性的一部分。
-
对gitignore
属性使用 exclude 选项有一个潜在的问题。通常,node_modules
目录包含在.gitignore
文件中。但是在试图隔离行为时,调试到node_modules
中的文件中可能是有用的。为了缓解这个问题,Live Share 支持否定.gitignore
中定义的可见性的能力。例如,下面的.vsls.json
文件仍然允许您调试到node_module
文件中,即使它们已经被排除在.gitignore
文件之外:
{
"$schema": "http://json.schemastore.org/vsls",
"gitignore":"exclude",
"excludeFiles":[
"!node_modules"
],
"hideFiles": [
"node_modules"
]
}
还要注意在hideFiles
属性中使用了node_modules
。排除和隐藏逻辑是彼此独立处理的。因此,这个设置将允许node_modules
文件从解决方案树中隐藏(以减少混乱),但仍然允许文件被查看或进入。
摘要
协作无疑是 Visual Studio 2019 新增功能的一个重点。特别是在解决特别有害的 bug 时,现场分享有很大的帮助潜力。在这个问题上有多只眼睛是好的。当你调试得更好的时候,让别人监督你。允许参与者在调试的同时戳戳你的代码,这提供了高度的灵活性,这正是解决挑战性问题所需要的。
然而,联合调试选项并不是 Visual Studio 2019 中添加的唯一调试功能。在下一章中,我们将会看到一些已经添加的其他调试功能,所有这些功能都是为了使问题的跟踪变得更加容易。
七、调试和分析
作为一名开发人员,您将花费大量时间调试代码。当然,不是你的代码。你的代码完全被单元测试覆盖,没有任何错误。而是别人的代码。那些没有你多年经验的人,或者没有你打造防弹解决方案的技能的人。或者第三方或开源代码。有时候里面也有 bug。
希望开头那段的挖苦性质是显而易见的。但这一点仍然有效。您将花费大量时间调试您的应用。Visual Studio 提供了大量工具来帮助完成这一过程。因为这是一个过程,至少在某些时候,需要你能得到的所有帮助。
本章不打算讨论在 Visual Studio 中调试应用的基础知识。假设已经很好地理解了设置断点、查看变量的当前值以及使用局部变量、监视和快速监视功能的技术。这些对 Visual Studio 2019 来说都不是新的,已经在开发人员的军火库中存在多年了。相反,重点将放在 Visual Studio 2019 中已经引入或增强的功能上,或者只是有用但未得到充分重视的功能上。
断点
在介绍完之后,开始谈论断点可能有点奇怪。毕竟,断点是调试技术的元老。它们在应用中指定一个应该暂停执行的位置,并给你机会检查运行时的当前状态。这是最先教给任何开发人员的事情之一。但是使用断点的某些部分没有被充分利用。Visual Studio 2019 更令人兴奋的新增功能之一恰好针对这一古老的功能。
正如刚才提到的,断点用于在特定的代码行暂停应用。但是,默认行为是每当命中该行代码时暂停。在某些情况下,这很好。对于其他人来说,则不然。您可能希望对给定断点暂停执行的时间有更多的控制。
条件断点
您可以通过断点设置窗格向断点添加条件。通过右键单击断点指示器(位于编辑器的左侧)时出现的上下文菜单上的设置选项,可以访问此窗格。在图 7-1 中看到的上下文菜单,包括许多对更高级断点功能有用的选项。
图 7-1
断点的上下文菜单
点击条件选项显示窗格,如图 7-2 所示。
图 7-2
用于定义条件的断点设置窗格
有许多选项可用于确定应用于断点的条件类型。首先,有一个包含所用条件类型的下拉列表。有三种选择。
条件表达式
前提是定义条件的那一行右边的大文本框将包含一个表达式。中间的字段包含一个下拉菜单,用于控制表达式的使用方式。下拉列表中有两个选项。对于Is true
选项,如果表达式的计算结果为真,那么执行将在断点处停止。对于When changed
选项,执行会在第一次遇到设置了断点的行时暂停。计算表达式,并记住值。随后每次命中断点时,都使用当前变量再次计算表达式。如果结果与前一个值不同,执行将在断点处停止。
Note
如果您正在调试本机代码,则“更改时间”选项的行为会略有不同。在这种情况下,调试器不认为第一次求值是值的变化。结果是第一次命中断点时,执行不会停止。
表达式使用的语言取决于设置断点的文件的语言。C#文件将使用 C#表达式。JavaScript 文件将使用 JavaScript 表达式。对于其他情况,确定文件类型和表达式语言之间的关系是留给读者的练习。
虽然表达式具有 IntelliSense,但它不立即进行语法检查。而且它肯定不能判断你的表达式是否会产生运行时异常。在这两种情况下,执行在断点处第一次停止。也就是说,在语法错误的情况下,第一次遇到断点,对于运行时异常,第一次引发异常。
当您有对象集合时,使用条件表达式可能会令人沮丧。除了创建与对象的唯一属性相关联的附加条件之外,没有明显的方法来区分集合中的不同对象。Visual Studio 2019 提供了一种机制,允许您生成一个对象 ID,然后可以在断点表达式中使用。在“局部变量”窗口中,右键单击所需的对象,并从上下文菜单中选择“生成对象 ID”(图 7-3 )。
图 7-3
局部变量的上下文菜单
一个新的变量现在被添加到“局部变量”窗口中(图 7-4 )。
图 7-4
对象 ID 跟踪变量
变量名的格式是$n
,其中n
是一个序列号。要在条件断点中使用这个对象标识符,可以添加表达式item == $n
,其中$n
是对象标识符。
尽管这种选择可能有用,但也有一些缺点。首先,对象标识符不会超过当前调试会话。您需要为每个调试会话创建一个对象标识符。标识符的数字部分有可能是相同的(序列号在每个调试会话中都被重置为 1),但这是您应该确保的。
此外,原始对象和跟踪对象之间的关系是弱引用。这意味着跟踪对象的存在不会阻止原始对象被垃圾回收。根据您正在调试的具体情况,这可能是一个重大问题。
点击计数
有些时候,条件就是不合适。相反,您希望在一行执行了一定次数后中断执行。如果您的代码在循环中执行,或者被应用的其他部分多次访问,这种情况很常见。
定义命中次数条件是通过同一个断点设置窗格完成的。不同的是,第一个下拉菜单设置为点击次数(图 7-5 )。
图 7-5
断点的命中次数设置
点击次数的表达式是数字的。事实上,不允许非数字输入。如何处理该数字取决于中间下拉列表中的值。可用的选项如下:
-
Equals (=)
–
当断点达到表达式中找到的次数时,执行停止。 -
是
–
的倍数当断点被命中的次数是表达式中数值的偶数倍时,执行停止。 -
大于或等于(> =)
–
当断点达到表达式中找到的次数时,执行停止。之后每次执行都会暂停。
过滤器
应用早就达到了需要运行多个应用才能正常工作的地步。Visual Studio 的优势之一是能够作为调试会话的一部分启动多个应用。然而,这可能会给调试这样的解决方案带来一些问题。当执行在断点处暂停时,所有的进程都会暂停,而不仅仅是断点所在的进程。
有一个设置可以改变这种行为。在“工具➤选项”对话框中,导航到“调试➤常规”节点。在列表顶部附近,您会发现一个名为“当一个进程中断时,中断所有进程”的选项。如果您取消选中该选项,那么执行将只在包含断点的进程中暂停。
虽然这很好,但并不总是足够的。创建多线程应用变得越来越普遍。并且这些线程在单个进程中运行。如果能够定位一个断点,使执行只在单个线程中暂停,而不是每次在所有线程中遇到断点时都暂停,这将非常有用。筛选条件是为多线程和多进程目标设计的。
图 7-6 显示了过滤条件的断点设置窗格。
图 7-6
断点的筛选器设置
前两个字段没有选项。第三个字段是表达式。它也是有限的。虽然表达式可以有 ANDs)、or(| |)、NOTs(!),和括号,可以测试的值仅限于线程、进程和机器值。具体来说,可以使用 ThreadId、ThreadName、ProcessId、ProcessName 和 MachineName。
附加设置
您可能已经注意到,在“条件类型”下拉列表下面有一个“添加条件”链接。一旦您填充了任何条件,您就能够添加额外的条件,注意每种类型只能添加一个条件。换句话说,当命中次数为 5 并且进程名为“demo.exe”时,可以有一个断点,在该断点处执行停止。但是不能有条件为“命中次数为 5,命中次数为 7”的断点。
除了多个条件之外,您还可以精确确定断点的位置。如果在一行代码中有多个语句,这很有用。在窗格的顶部,有一个设置断点的位置。点击数值可以直接编辑数值,如图 7-7 所示。
图 7-7
设置断点的位置
在这里,您可以指定源代码文件中的行号以及该行中的字符位置。如果当前源代码不同于用于编译正在运行的可执行文件的源代码,那么会有一个复选框指示您是否愿意允许断点处于活动状态。
动作断点
当在调试会话期间遇到断点时,默认操作是停止当前线程。事实上,默认情况下是停止所有线程,尽管正如上一节提到的,这是一个配置选项。但是,如果您试图调试一个正在处理争用情况的多线程应用,这可能会是一个问题。或者如果您只是不需要暂停应用,而只需要在断点命中时从调试器中获取一些信息。
这些情况可以通过操作断点(也称为跟踪点)来解决。创建操作断点与创建常规断点是一样的。事实上,正是您对断点的设置所做的事情使它成为一个操作断点。与条件断点一样,右键单击断点指示器(在编辑器的左侧),然后从上下文菜单中选择“操作”。断点设置窗格出现,这次操作是可见的,如图 7-8 所示。
图 7-8
断点窗格中的操作设置
有两个字段与操作设置相关联。第一个是断点命中时设置到输出窗口的消息说明。有几个适用的格式元素(它们将在下面的段落中描述),但这是操作断点的基本目的。这样,它类似于代码中的调试或跟踪语句。区别在于动作断点是动态设置的,因此不需要重新编译应用。
第二个字段是一个复选框,指示当断点被命中时程序的执行是否应该继续。默认情况下,应用不会暂停执行。在这种情况下,它与常规断点完全相反,并且这种行为在本节开头描述的多线程场景中运行良好。但是,如果您仍然希望暂停执行,请清除此复选框。
要显示的消息不是表达式。也就是说,期望不是定义一个字符串值,用连接符号完成。相反,这种格式更接近于您在String.Format
字符串中使用的模板,使用特殊的变量作为固有值,使用花括号符号作为变量值。例如,考虑以下情况:
$FUNCTION: The value of x.y is {x.y}
当遇到这个动作断点时,我们将从当前函数的名称(即$FUNCTION
值)开始,接着是字符串The value of x.y is
,然后是x
对象上y
属性的值。因此,消息由内置变量、文本和局部变量值的花括号符号的组合组成。
内置变量列表可通过 IntelliSense 获得(在图 7-8 中可见)。每个的含义如下:
-
$ADDRESS
–
当前指令 -
$CALLER
–
调用当前方法的函数的名称 -
$CALLSTACK
–
当前调用堆栈 -
$FILEPOS
–
代码文件中的当前位置 -
$FUNCTION
–
当前函数的名称 -
$PID
–
当前进程 ID -
$PNAME
–
当前进程的名称 -
$TICK
–
当前的滴答计数 -
$TID
–
当前线程 ID -
$TNAME
–
当前线程的名称
动作断点(与常规断点相反)的存在由不同的断点指示符来指示。编辑器的左边出现了一个红色的菱形,而不是红色的圆圈,尽管只有勾选了Continue code execution
复选框时才会出现这种情况。如果未选中,则红圈照常出现。
函数断点
无论是否添加条件,手动设置断点的一个基本前提是,您可以访问要应用断点的源代码。您可以打开文件,找到有问题的行,并应用断点。但这并不总是行得通的。Visual Studio 2019 提供了几个不同的函数,旨在帮助解决这些情况。
当您知道要暂停执行的函数的名称,但不知道文件在源代码中的位置时,可以使用函数断点。或者,如果您有一个函数的多个重载,使用函数断点比在每个重载方法中放置断点更容易。
使用调试菜单创建函数断点,特别是选择调试➤新断点➤函数断点。出现如图 7-9 所示的对话框。
图 7-9
函数断点对话框
在对话框的下半部分,有两个复选框,分别标记为条件和操作。这些用于设置您希望应用于断点的任何条件,或者在到达断点时应该发生的任何操作。这些操作与常规断点对话框中的条件和操作相同。本章前面的“条件断点”和“操作断点”部分描述了用这些复选框启动的选项。
将函数断点与其他断点区别开来的是如何定义何时暂停执行。这是在函数名文本框中完成的。最简单的方法是,输入函数的名称。该名称必须是完全限定的,因此,例如,它看起来像下面这样:
SampleApplication.Models.Order.Add()
在这个例子中,方法的名称空间是SampleApplication.Models
。包含方法的类的名字是Order
,方法的名字是Add
。还要注意没有参数的括号。这个符号将匹配对Add
的每个调用,而不管使用的参数是什么。换句话说,它将在Add
方法的每个重载实例上中断。
函数名的目标是提供尽可能多的特殊性或可变性,以便在适当的时候打破。例如,尽管前面的例子将在所有的Add
方法上中断,不管签名是什么,但是您可以通过提供参数和数据类型来更准确地使用断点。
SampleApplication.Models.Order.Add(Customer c)
现在,只有当类型为Customer
的单个参数被传递给Add
方法时,执行才会暂停。
您可以指定该方法所在的模块,而不是为该方法使用命名空间。例如,当从 Order.dll 模块调用 Add 方法时,下面的代码将暂停执行。
Order.dll!Add
最后,如果在本机 C++中调试,可以在用于标识断点位置的信息中包含偏移量。
{Add, , Order.dll}+2
此符号在 Order.dll 模块中从 Add 方法开始的第二行设置一个断点。
与函数名相关的另一个字段是语言下拉列表。这用于影响您用来声明函数名的语法。前面显示的向方法传递参数的示例使用了 C#语法,这意味着需要将 Language 属性设置为 C#。下拉列表包含许多不同的选项,包括 JavaScript、TypeScript、F#和 Basic。用于定义函数名的语法需要与所选择的语言相匹配。
数据断点
Visual Studio 2019 的一个新功能是数据断点的概念。其思想是,当变量值改变时,执行暂停,而不是在一行代码处中断。这是一个非常强大的想法,因为解决 bug 的关键通常是识别应用中变量值被修改的地方。虽然 C++开发人员已经可以使用这一功能,但长期以来,它一直是托管代码开发人员的常规要求。
首先,坏消息。目前,创建数据断点仅在中可用。网芯 3.0 或更高版本。从好的方面来看。NET Core 3.0 支持使用 WPF 或 Windows 窗体的 Windows 桌面应用,因此您对可以使用的应用类型的限制已经大大减少。但如果你不坚持。NET Core 3.0,那么这个功能对你是不可用的。
若要在托管代码中设置数据断点,需要运行应用。然后在“局部变量”、“自动”或“监视”窗口中,右键单击要监视的属性,并从上下文菜单中选择“值更改时中断”。图 7-10 显示了该序列的作用。
图 7-10
设置数据断点
一旦设置了断点,窗口中的变量旁边就会出现一个红色的圆圈。
在执行过程中,如果变量的值发生变化,则执行会暂停,并出现一条消息,指出断点的原因。图 7-11 说明了该信息。
图 7-11
为数据断点显示的消息
此时,您的执行已经暂停,您可以访问所有典型的调试信息和功能。
Note
提醒一下,当变量值改变时,数据断点被触发。如果您将断点与一个对象相关联,那就很好了。但是在一个对象上设置断点不会在该对象的属性被修改时暂停执行。为此,您需要在 Locals/Auto/Watch 窗口中展开对象,并在该对象中选择要监视的属性。为了强调这一点,数据断点是实例敏感的。如果您有同一个类别的两个执行个体,当第二个执行个体的相同属性被修改时,在其中一个执行个体的属性上设定的资料中断点不会导致执行暂停。
使用调用堆栈定义断点
对于大型应用,调试的挑战之一是在正确的位置设置断点。在执行过程中过早地设置它,您将花费很长时间来使用单步执行和单步执行函数。如果设置得太晚,那么,一切都太晚了。这实际上是司法使用条件断点如此有价值的原因之一。
但是 Visual Studio 确实提供了另一种定义断点的方法,这种方法非常有用,尤其是在试图确定意外异常的来源时。当执行因任何原因暂停时,调用堆栈是可用的。虽然调用堆栈中的所有级别都不容易调试,但您的代码在堆栈中肯定是可见的。在堆栈中的任何级别,您都可以右击并在上下文菜单中选择插入断点。这将在代码中由调用堆栈指示的位置放置一个断点。
现在可能需要添加额外的条件来避免多次命中断点,但是您可以在调用点检查局部变量,看看最合适的选择是什么。但最重要的是,您不需要浏览代码来找出它是如何到达异常位置的。调用堆栈已经给出了路径。
远程调试
在麻烦的生产应用中,远程调试的诱惑始终存在。有多少次,您在本地开发和测试了一个应用,甚至是在一个试运行环境中,结果却是在它被部署到生产环境中后遇到了全新的问题。
通过使用一个小的可执行文件,您可以像调试本地应用一样调试远程应用,也就是说,断点和单步执行功能是可用的,查看变量值的能力也是可用的。关键是那个小的可执行文件和使用哪个文件背后的细节。
第一步是下载适当的可执行文件。起点是下载 Visual Studio 2019 的远程工具。你可以在 https://visualstudio.microsoft.com/downloads/#remote-tools-for-visual-studio-2019
找到下载链接。在图 7-12 中可以看到页面本身的一部分。
图 7-12
Visual Studio 2019 远程工具的下载页面
诀窍是下载正确的工具集。ARM64、x64 和 X86 的描述右侧有一组单选按钮。此选项应设置为目标计算机的体系结构,而不是运行 Visual Studio 的计算机的体系结构。
远程工具由远程调试监视器和远程调试器配置向导组成。在最简单的部署中,您可以将远程调试监视器(msvsmon.exe)复制到目标机器上并执行它。要知道,每次想要初始化调试会话时,都需要执行监视器。配置向导的目的是帮助您将远程调试监视器设置为 Windows 服务,从而允许更经常地进行远程调试,而无需开发人员或 IT 人员付出额外的努力。
当您执行远程调试监视器时,拥有对计算机的管理员访问权限是很重要的。这可以通过以管理员身份登录或使用应用上下文菜单中的“以管理员身份运行”选项来关闭。当应用启动时,界面如图 7-13 所示。
图 7-13
远程调试配置
远程调试监视器要求安装 Window Web 服务 API 并配置 Windows 防火墙,以允许远程访问 Visual Studio 用来与监视器通信的端口。配置远程机器的必要选项可通过该对话框获得,该对话框仅在当前配置不允许远程调试时出现。您唯一的选择是应该允许哪些网络进行远程调试。作为标准的安全实践,建议只选择那些您真正需要的选项。虽然检查所有这些工作,但它确实有可能为运行它的机器打开一个攻击媒介。
当监视器启动时,它会显示一个包含状态信息的屏幕。图 7-14 显示了启动时的对话框。
图 7-14
Visual Studio 2019 远程调试器状态信息
一旦远程调试器开始运行,Visual Studio 2019 就该开始行动了。现在,您连接到远程调试器所采取的步骤基于您正在调试的应用的类型。
ASP.NET 应用
对于 Web 应用,无论是 ASP.NET 还是 ASP.NET 核心,都可以通过使用“调试➤附加到进程”菜单来附加到远程调试监视器。在出现的对话框中(图 7-15 ,指定远程机器的名称,以及监视器监听的端口。
图 7-15
附加到远程进程进行调试
默认情况下,对于 Visual Studio 2019,该端口为 4024。列表中可能已经包含了您正在寻找的进程,但是如果没有,请检查显示所有用户的进程,将每个正在运行的进程都包含在列表中。连接到所需的进程(通常是一个w3wp.exe
进程),调试会话开始。
桌面应用
对于桌面应用,远程调试的配置在项目的属性中进行。打开 Properties 窗格(通过右击项目并选择 Properties)并导航到 Debug 部分,如图 7-16 所示。
图 7-16
桌面应用的调试窗格
选中“使用远程计算机”复选框,然后在右侧的文本框中填入远程计算机的名称和端口号。在开始调试之前,还需要满足几个其他条件。首先,Working directory
字段需要为空,并且必须取消选中Enable native code debugging
复选框。工作目录需要是空的,因为您将在远程机器上运行应用,所以在本地指定工作目录是无效的。并且远程调试当前不支持本机代码调试。
最后一个主要要求是,您要调试的应用的调试版本需要位于远程计算机上,与本地应用的调试实例位于同一路径。也就是说,路径中的所有文件夹都需要匹配,包括…/bin/Debug 文件夹(默认情况下)。通常,工作流是构建应用,然后使用您喜欢的方法将其复制到远程机器,然后启动调试会话。
Azure 应用
如果您已经将应用部署到 Azure 中,您仍然能够远程调试它。同样,路径取决于部署的类型。如果将应用加载到虚拟机(VM)中,远程调试的步骤与 ASP.NET 或桌面应用相同,具体取决于 VM 上运行的应用。然而,如果您部署到 Azure 应用服务中,步骤会有所不同。首先打开服务器浏览器并展开 Azure 节点(图 7-17 )。
图 7-17
服务器资源管理器中的 Azure 节点
当您单击应用服务节点下的打开云浏览器选项时,它会启动云浏览器。在该窗格中找到的树包括所有 Azure 订阅,您可以使用 Visual Studio 中使用的当前凭据访问这些订阅。如果您尚未登录,系统会提示您提供 Azure 凭据,然后再继续。图 7-18 展示了应用服务节点展开后的云浏览器。
图 7-18
云浏览器中的应用服务节点
右键单击要调试的应用服务。在出现的上下文菜单中(这可能需要几秒钟),选择“附加调试器”。这将您连接到远程调试器,方式与前面描述的 ASP.NET 应用部分相同。
快照调试
调试应用的另一个圣杯是致力于解决生产中出现的问题。很少有开发者没有遇到过那种“但是它在我的机器上工作”的感觉。虽然越来越多地使用模拟生产的测试和试运行环境降低了频率,但仍有在早期环境中工作的应用在生产中出现问题的情况。当人们真的在使用一个网站时,设置断点和单步调试代码的想法,伴随着所有其他线程的暂停,是行不通的。
快照调试器的目的是帮助解决这种情况。在应用执行期间的适当时刻,拍摄应用状态的快照。这个过程大约需要 10 –
20 毫秒,所以对性能有一定的影响。但是对整体用户体验的影响应该可以忽略不计。
在 Visual Studio 2019 中,您需要安装快照调试器组件。这是 ASP.NET 和 web 开发工作量的一部分。此外,你的应用需要运行在许多不同的环境中的一个:Azure 应用服务、Azure 虚拟机或运行在 Kubernetes 服务中的 ASP.NET 应用,在所有情况下,至少运行在 ASP.NET 应用上。NET Framework 4.6.1 或。网芯 2.0。
通常,执行快照调试的流程是打开要调试的解决方案。然后通过为应用指定端点来附加到快照调试器。此时,您正在调试应用,但不是以通常的方式。您可以指定对齐点,而不是指定断点。这个过程类似于设置断点。然而,不是暂停执行并允许您查看本地状态,而是制作本地状态的副本并继续执行。您仍然可以查看本地状态,但是应用不会暂停…这在您处理生产时非常重要。
现在,让我们更详细地介绍一下这些步骤。首先,打开已部署应用的解决方案。重要的是源代码与部署的版本相匹配。否则,您将会遇到与使用过时的源代码调试本地项目时相同的问题,也就是说,您的快照点将会受到影响。事实上,不匹配的源代码是尝试快照调试时出现问题的主要原因,所以如果您发现自己无法跟上,那么这应该是您首先要考虑的地方。
阅读解决方案后,选择“调试➤连接快照调试器”菜单项。出现如图 7-19 所示的对话框。
图 7-19。
附加到快照调试器
该对话框会记住您最后输入的内容,因此第一次附加时,两个下拉列表都是空的。在这两种情况下,下拉菜单都会弹出如图 7-20 所示的对话框。
图 7-20
选择 Azure 资源
下拉值的目的是让您确定要调试的 azure 资源以及将用于包含快照信息的存储帐户。对于这两个下拉列表,您选择 Azure 订阅,然后选择应用服务、虚拟机或 Kubernetes 服务(对于 Azure 资源值)或存储帐户(对于商店帐户值)。
当两个值都指定后,点击图 7-19 中的连接按钮,等待调试会话开始。如果这是你第一次在选择的资源上使用快照调试,你会看到一个如图 7-21 所示的对话框,询问你是否允许安装几个扩展。
图 7-21
授予安装扩展的权限
虽然运行快照调试器需要这些扩展,但请注意安装这些扩展将会重新启动您的站点。重新启动可能会影响应用的状态。一旦安装了扩展,您将需要完成再次连接到快照调试器的步骤。毕竟,目标资源已经重新启动。
一旦连接到快照调试器,下一步就是设置快照点。在您想要放置对齐点的代码行左侧的装订线中单击。会显示一个六边形指示器来显示对齐点的位置。一旦快照点就位,您可以右键单击它以提供更多详细信息,例如拍摄快照点的条件。图 7-22 不仅显示了卡点指示器,还显示了可用的选项。
图 7-22
已经设置了快照点
除了断点中可用的条件和操作,还有一个快照选项。您可以确定捕获的快照的最大数量。限制这些信息的原因是,每个快照都会占用您的 Azure 存储帐户中的资源。创建的快照越多,使用的存储就越大,因此产生的成本也就越高。通过限制快照的数量,您可以确保拥有数百个用户或将您的快照点置于循环中不会过度使用存储帐户。
识别快照点后,您可以通过单击工具栏中的“Start Collection”选项开始收集快照。或者使用“调试➤开始收集”菜单项。Visual Studio 会将快照点部署到资源。为了生成快照,您需要在 web 应用中安排放置快照的代码行,或者等待用户这样做。一旦快照命中,您将看到快照的详细信息出现在诊断工具窗口的快照调试器窗格中(图 7-23 )。
图 7-23
连接到快照调试器时拍摄的快照
有关快照的详细信息可以通过单击快照来查看。但是,如果您想要查看快照的详细信息,包括状态,请单击相应快照底部的查看快照链接。或者您也可以双击快照。
此时,快照的内容被下载到您的本地机器上。这需要多长时间取决于您的网络速度,但有可能会有几分钟的延迟。
一旦快照可用,您将被置于代码中拍摄快照的位置。此时的视图看起来非常像典型的调试会话(参见图 7-24 )。
图 7-24
快照调试视图
你现在看到的是 Visual Studio 中的历史调试。您可以访问拍摄快照时范围内的值。例如,“局部变量”窗口包含所有局部变量的值。您可以展开任何对象来查看属性值,以及任何其他相关的对象。“调用堆栈”窗口可用于显示代码到达这一点所采用的路径。你可以通过悬停在变量上来查看变量的值。
当然,你不能做的是单步调试代码,看看接下来会发生什么,或者改变变量的值来影响程序流程。这是您看到的历史信息,生成快照的请求早已完成。但是请记住,快照调试的目的是让您在生产过程中看到应用的状态。快照调试器绝对符合这个标准。
默认情况下,对于您设置的任何快照点,只拍摄一个快照。如前所述,您可以通过快照点设置窗格更改快照的数量(图 7-22 )。但是快照将只包含第一次点击快照点时的信息。如果要为快照收集另一个数据快照,请单击工具栏中出现的“更新集合”按钮,或者使用“调试➤更新集合”菜单项。
智能跟踪
与从生产环境中获取调试信息相关的挑战已经在快照调试器一节中提到。在那一节中,描述了一种适用于部署到各种 Azure 环境中的 ASP.NET 应用的技术。但是没有人会感到惊讶,Azure 并不是唯一一个。NET 应用已部署。IntelliTrace 的目的至少部分在于允许在服务器上捕获应用的状态(生产或非生产),然后由查看原始源代码的开发人员检查。
IntelliTrace 并不局限于在生产环境中使用。当测试人员发现的 bug 不能在开发环境中重现时,这是非常方便的。通过 IntelliTrace 捕获的信息可以附加到 bug 上,然后由开发人员在以后打开。
首先,IntelliTrace 收集器需要安装在运行应用的环境中。IntelliTrace 适用于在 7.0 到 16.0 版本的 Internet 信息服务(IIS)下运行的 ASP.NET 应用。但它也可用于从 SharePoint 2010 和 2013 应用、Windows Presentation Foundation(WPF)应用和 Windows Forms (WinForms)应用中捕获信息。收集器可以在微软下载中心找到( https://visualstudio.microsoft.com/downloads/#intellitrace-standalone-collector-for-visual-studio-2019
)。该下载是一个可执行文件。这个文件需要在收集信息的机器上执行。这可能是 ASP.NET 应用或 SharePoint 应用的 web 服务器,也可能是 WPF 或 WinForms 应用的用户机器。当它被执行时,它会创建一个IntelliTraceCollection.cab
文件。一旦。cab 文件是可用的,命令expand /f:∗ <pathToCabFile>/IntelliTraceCollection.cab
将用必要的文件填充目录。<pathToCabFile>
是生成.cab
文件的路径,或者是相对于当前目录,或者是完全限定路径。
您还需要创建一个目录,在其中可以创建 IntelliTrace 文件。它与IntelliTraceCollection.cab
文件被展开的目录是同一个目录没有问题,但这不是必需的。然后,您需要确保运行该应用的用户对 IntelliTrace 文件目录拥有完全权限。
如果您计划在 web 或 SharePoint 应用上使用 IntelliTrace,有一些 PowerShell 脚本非常有用。使用 Install-Module cmdlet 安装展开的Microsoft.VisualStudio.IntelliTrace.PowerShell.dll
文件。执行此操作的命令如下:
Install-Module -Name Microsoft.VisualStudio.IntelliTrace.PowerShell
现在您已经准备好捕获 IntelliTrace 文件了。无论您使用的是 web 应用还是客户端可执行文件,概念都是相同的。您将指定放置跟踪文件的位置,并确定 IntelliTrace 要使用的收集计划。收集计划是一个 XML 文件,它指定 IntelliTrace 运行时要捕获的事件。在可以使用的扩展文件中有许多标准收集计划。
对于 WPF 或 WinForms 应用,IntelliTrace 收集器不仅可用于开始捕获跟踪文件,还可用于启动应用本身。命令行的基本结构如下:
IntelliTraceSC.exe launch
/cp:<collectionPath>
/f:<pathToTraceFileDirectory>
<pathToExecutableBeingTested>
需要注意的是,跟踪文件的位置路径必须是完全限定的路径。相对路径不行。
对于 web 或 SharePoint 应用,有一个 PowerShell cmdlet,尽管提供的信息基本相同。
Start-IntelliTraceCollection "<applicationPool>"
<collectionPath> <pathToTraceFileDirectory>
应用池是运行 web 应用的应用池的名称。
现在复制你遇到的问题。当您运行应用时,会生成跟踪信息,但是您不一定能够看到跟踪文件(具有.itrace
扩展名)大小的增加。您正在跟踪的应用需要在生成.itrace
文件之前完成。对于 WPF 或 WinForms 应用,这只是一个终止应用的问题。对于 web 或 SharePoint 应用,运行以下命令停止收集信息:
Stop-IntelliTraceCollection "<applicationPool>"
所有这些活动的结果是一个.itrace
文件。开发人员要使用它,需要将其移动到安装了 Visual Studio 2019 Enterprise 的机器上。一旦有了它,就可以打开它,为开发人员提供类似于快照调试器所提供的历史调试体验,其中有一个主要的变化。
IntelliTrace 和快照调试器都提供称为历史调试的功能。这意味着,即使调试会话是在不同的环境中执行的,它的信息对 Visual Studio 也是可用的。不同之处在于,快照调试是在单个快照点捕获的信息,而通过 IntelliTrace 捕获的信息允许您继续进行更传统的调试会话。
当你打开一个.itrace
文件时,你会看到一个类似于图 7-25 的概述。
图 7-25
iTrace 文件摘要窗格
可以看到许多可扩展的部分。你可能还能看到一些其他的。例如,如果应用中出现了异常,或者应用的某些部分的执行时间过长,那么将会有针对这些异常的可见部分。对于每一个部分,您都可以选择一项并单击一个按钮来开始调试。这将打开 IntelliTrace 事件或项目的详细信息。
调试事件时会发生什么取决于事件。如果您调试异常或性能冲突,并且触发事件在您的代码中引发,您将被带到代码中发生该事件的位置。此时,您可以使用调用堆栈、局部变量或自动窗口来查看变量的当前值。这就是历史调试带给你的。
但是,IntelliTrace 允许您继续执行。IntelliTrace 更像是一系列离散事件,而不是一个时间点。因此,一旦您完成了对一个事件的检查,您就可以进入下一个事件。现在,应用的状态变为捕获流中的下一个 IntelliTrace 事件。这样,你就可以跟踪应用的流程,从一个事件到另一个事件,向前和向后,寻找你试图解决的任何问题的原因。
诊断工具窗口
可通过调试➤窗口➤显示诊断工具菜单项访问的诊断工具窗口是用于帮助进行性能分析的工具的中心位置。虽然它们可能不会在日常生活中使用,但是当你需要它们的时候,它们在你的工具箱中是非常有用的。
通过诊断工具获得的性能信息用于监控 CPU 和内存的使用情况。虽然它们在可视信息方面是不同的,但它们是联系在一起的,因为它们是同时收集的,并且可以一起可视化。
在“诊断工具”窗口可用之前,需要将其打开。这是通过工具➤选项对话框完成的。导航到“调试➤常规”节点,并确保选中“调试时启用诊断工具”选项。
启用诊断工具后,当您调试应用时,“诊断工具”窗口将打开,并开始收集和显示信息。图 7-26 是一个应用运行一段时间后,这个窗口的样子。
图 7-26
诊断工具窗口
在图 7-26 中,窗口顶部有三个可展开的部分。它们针对事件(特别是 IntelliTrace 事件)、正在运行的进程所使用的内存量以及解决方案中所有进程的 CPU 使用情况。在最顶端,有一个调试会话的时间表。在深入了解一些细节时,您可以使用此时间表来选择会话的子集。
事件
“事件”选项卡出现在窗口的顶部。与时间线类似,它旨在帮助您确定要关注的诊断部分。事件的主要特征是沿时间轴以不同间隔出现的菱形。每个形状对应于一个被跟踪的特定事件。要查看事件的详细信息,单击窗口顶部的事件选项卡(图 7-27 )。
图 7-27
诊断工具窗口中的事件选项卡
从列表中选择一个特定事件会显示该事件的更多详细信息。在列表的顶部,您可以按类别或来源(即事件发生的线程)过滤事件。或者您可以在右边的框中键入文本,它将只显示名称或描述包含匹配项的事件。
内存使用
图 7-26 中间的图表记录了一段时间内应用的内存使用情况。有两个指标标志着某些与记忆有关的事件何时发生。第一个是较小的橙色箭头。这表示在时间线内垃圾收集发生的时间。第二个是蓝色三角形,表示拍摄快照的时间。
虽然内存使用图表很好,但在确定应用中可能存在的任何内存问题的来源时,它并不是非常有用。在诊断工具窗口的底部,有一个内存使用选项卡。图 7-28 是该选项卡的一个例子。
图 7-28
内存使用选项卡
最初,这个选项卡是空的。使用它的关键是“拍摄快照”按钮。此按钮的目的是在特定时间点拍摄应用正在使用的内存的快照。它收集在堆中找到的所有信息,并显示对象的计数和堆的大小。您可以在应用运行过程中拍摄多个快照。这允许您比较一段时间内内存的大小和内容,这通常是识别内存使用来源的核心。
正如你所想象的,收集信息确实需要一点时间。您甚至可能会对应用中存在的对象数量感到惊讶。在 web 应用(用于生成图形的应用类型)的情况下,它不仅包括您已经创建的所有对象,还包括提供 web 页面所需的对象。因此,对于 MVC 应用,它包括会话信息、路由信息和用于构建呈现页面的对象。换句话说,有很多对象,虽然它们是为应用服务而创建的,但并不是显式创建的。
在图 7-28 中,您可以看到在流程的不同点拍摄了三个快照。对于每一张照片,您都可以看到拍摄快照的时间点。并且有一个对象计数和堆大小如何从一个快照变化到另一个快照的可视化表示。要深入查看特定快照的内容,请单击对象计数或堆大小值。或者选择一个快照,然后单击工具栏中的查看堆图标。详情见图 7-29 窗口。
图 7-29
内存快照的内容
快照的详细信息分为两部分。顶部是组成快照的对象类型列表。每种类型的对象都包含在列表中。对于每种类型,您可以看到存在多少个对象以及对象的累积大小。实际上有两个大小的列。这两个值的区别在于 Size 列是特定对象的字节大小。包含大小列不仅是对象中的字节数,也是对象引用的子对象中的所有字节数。
图 7-29 的下半部分将所选对象类型的计数分为不同的派生类型。图 7-29 显示应用中的大部分数组列表对象实际上是 ConfigurationValue 对象。
如果要查看特定类型的各个对象的更多详细信息,请在鼠标悬停在特定行上时,单击显示在对象类型名称右侧的图标。该图标在图 7-29 中可见,在数组列表类型旁边。具体如图 7-30 所示。
图 7-30
检查特定对象类型的实例
在图 7-30 中,您可以看到所选对象类型的不同实例列表。对于每个实例,都有大小和包含大小,以字节为单位。在屏幕底部,您可以看到与图 7-29 中相同的衍生类型扩展。
拍摄快照的目的之一是比较内存随时间的增长情况。好吧,有可能记忆力会随着时间而下降。但这种情况发生的可能性有多大?
在用于查看不同对象类型的原始窗口的顶部,有一个包含当前流程的其他快照的下拉列表。通过选择其中一个快照,您可以比较两者之间对象的变化。该信息的可视化与图 7-29 相似,但也包括变更。如图 7-31 所示。
图 7-31
两个快照的比较
现在,这些列显示了快照之间在计数、大小和包含大小方面的差异。屏幕的下半部分包括对象类型及其派生类型的相同细分,其中有一列显示快照之间的引用计数差异以及绝对计数。
CPU 使用情况
图 7-26 中的下图以百分比的形式跟踪了应用运行期间的 CPU 使用率。如果您有多个 CPU,将在所有可用的 CPU 中计算百分比。有关 CPU 使用的更多详细信息,点击 CPU 使用选项卡以显示类似于图 7-32 所示的区域,但没有功能信息。
图 7-32
诊断工具中的 CPU 使用选项卡
为了查看图 7-32 中可见的信息,应用必须暂停。窗格的初始状态包括一个 Break All 链接,单击该链接可以暂停所有线程。或者应用可以被您放置的任何断点暂停。当应用中断时,将收集到该点的 CPU 使用信息,片刻之后,显示如图 7-32 所示。
“诊断工具”窗口中还有另外两个元素会影响 CPU 使用率信息的显示。第一个是在 CPU 使用选项卡上。工具栏左侧是一个黑色或红色的圆圈。此图标用于开始或停止收集 CPU 信息。除非开启了收集过程,否则您将看不到任何详细信息。
第二个元素实际上是贯穿所有网格的东西,实际上在本节前面已经提到过。可以通过按住鼠标并沿着窗口顶部的 CPU 使用图表拖动鼠标来选择时间范围。然后,选择的时间范围成为生成 CPU 使用信息的界限。如果在指定的时间范围内未收集到 CPU 信息,则“CPU 使用情况”选项卡上会显示一条消息。否则,计算并显示使用信息。
如图 7-32 所示,使用信息显示了应用中不同组件使用的 CPU 数量。从图中可以看出,大部分时间都花在了外部代码上,这对于 web 应用来说并不罕见。但是您也可以看到几个控制器和一个利用 CPU 的模型。
但这些信息还不够详细,不足以采取行动。要获得特定组件中 CPU 使用情况的更多详细信息,请双击它。这将以如图 7-33 所示的形式显示详细信息。
图 7-33
CPU 使用详细信息
在图 7-33 的顶部,你可以看到三个区域。左边是调用所选类的函数。右边是从所选类中调用的函数。中间区域包含所选类中使用了 CPU 的函数。对于每个函数,您可以看到 CPU 使用百分比。图 7-33 的底部将当前类的 CPU 使用率分解到行级别。
请记住,这个区域的目标是允许您在应用中导航,搜索 CPU 使用的来源。通过双击区域的所需部分来完成导航。例如,如果您想查看 AosWebManager 的用法。Lines 类中,您可以双击被调用函数区域的下半部分。这将修改 CPU 使用情况详细信息窗口中显示的数据,以便当前函数是 AosWebManager.Models.Lines .并且其他两个区域(调用函数和被调用函数)被调整为匹配。这项技术使您能够快速上下移动呼叫树,寻找潜在的问题。
摘要
调试是许多开发人员花费大量时间的地方。所以他们的开发环境提供的任何帮助都是受欢迎的。多年来,Visual Studio 一直处于调试辅助技术的前沿,最新版本也不例外。可用的工具不仅有助于解决一些更普通的情况,它们还可以让开发人员处理更具挑战性的场景,如识别生产环境中的问题,所有这些都有助于使 Visual Studio 像现在这样受欢迎。
但是诊断和调试并不是开发人员的唯一兴趣。事实上,Visual Studio 努力确保开发人员,不管他们使用什么语言,都能得到改进。在下一章中,我们将探讨使用 ASP.NET、Node.js、C++以及其他语言和平台的程序员可以使用的附加功能。
八、特定于语言的改进
虽然 Visual Studio 2019 中的许多改进在不同的语言和平台上都很有用,但总有一些更改是特定于个别语言的。它们满足了特定开发人员的特定需求。或者,这种语言的底层结构使得在不同平台上复制这种特性很难顺利实现。随着时间的推移,这些改进可能会出现在其他环境中,但是最初,它们只在某些工作负载中可用。
一个很好的例子是在第七章“调试和分析”中讨论的数据断点 C++开发人员可以使用数据断点为许多版本创建本机代码应用。支持数据断点的功能从未在。NET 框架。只有在 Visual Studio 2019 中,它们才可以被 C#应用访问,甚至只有在。NET 核心平台。
本着这种心态,本章将介绍 Visual Studio 2019 中的一些增强功能,这些功能更紧密地专注于一种语言或平台。即使您不使用这些环境进行开发,有时知道它们的存在也是有用的。至少,它可能会迫使你向微软提出建议,让他们花费必要的资源将该功能带给更多的用户。
总体改进
在我们开始介绍为支持个别语言而添加的功能之前,让我们花一些时间来介绍一些跨语言的功能,这些功能并不完全适合任何其他章节。
新闻部意识
众所周知,使用多台显示器的开发人员比只使用一台显示器的开发人员更有效率。无需切换应用即可查看代码、运行应用和浏览的能力惊人地强大。然而,在现实世界中,它并不是没有令人头痛的问题。拥有相同的显示器固然很好,但并不总是可能的。
分辨率、缩放设置或高于 100%的差异通常足以降低文本的清晰度。在这些情况下,经常会遇到模糊不清的文本。典型的情况是文本在一台显示器上看起来很棒,但在第二台显示器上就不那么清晰了。或者也许它只是在所有的监视器上看起来很糟糕。
Visual Studio 已经成为许多版本的 DPI(每英寸点数)感知应用。它被设置为系统感知模式。这意味着 Visual Studio 在确定如何呈现各种可视元素(包括文本)时将使用系统级分辨率信息。如前所述,当处理多个监视器时,这是一个问题。当远程处理到具有不同显示配置的机器时,这也是一个问题。
Visual Studio 2019 引入了每显示器 DPI 感知功能(PMA)。它要求。安装. NET Framework 4.8,并在 Windows 10(2018 年 4 月更新或更新版本)上运行。如果可用,它会自动激活。不同之处在于,它不是将单个设置(系统设置)应用于所有监视器,而是使用每个监视器的设置来确定元素的渲染方式。
虽然您不太可能想要禁用此功能,但是您可以选择。如图 8-1 所示,在选项对话框的环境➤常规部分有一个设置。
图 8-1
用于 PMA 支持的环境设置
如果您注意到在不同的监视器上看时,Visual Studio 中的文本或图标很模糊,那么查看此设置的主要好处可能就来了。如果相关的选项(标有优化不同像素密度屏幕的渲染的明显名称)是灰色的,这意味着您的系统不符合。NET Framework 4.8 或 Windows 10 要求。升级到这两个版本应该可以解决这个问题。
。NET Core 3.0 支持
2019 年,微软宣布发布。网芯 3.0。虽然这个名字看起来并不重要,但就所提供的功能而言,这是一个分水岭。NET 开发人员。第一次。NET Core 支持许多旧技术,包括 Windows 窗体(WinForms)和 Windows Presentation Foundation(WPF)。这对开发人员来说意味着您可以将旧的应用移植到。NET 核心,并让它使用最新的 API 集工作。
现在,并非所有平台都支持所有技术。WPF 和 WinForms 只能在 Windows 上使用。移植旧的应用需要注意一些问题。向后兼容性支持不是 100%。而微软内部也在努力打造类似的 surface。NET Core 2.0(及更高版本)和。NET 框架,有些差异不容易弥合。为了帮助确定可能存在的问题,微软提供了一个. NET 可移植性分析器,可以在 https://docs.microsoft.com/en-us/dotnet/standard/analyzers/portability-analyzer
找到。
除了支持 WinForms 和 WPF,还有一个新的窗体设计器可供这两者使用。然而,他们不是同时被释放的。WPF 设计器包含在 Visual Studio 2019 版本 16.3 中,因此您可能需要升级到 Visual Studio 的更高版本才能看到它。新的 Windows 窗体设计器的预览版在 16.4 版中提供。但是,默认情况下不启用。要使用新的设计器,您需要转到选项对话框的环境➤预览功能部分(图 8-2 )并手动启用它。
图 8-2
为启用预览 Windows 窗体设计器。净核心
C++改进
在 Visual Studio 2019 中,C++领域有许多增量改进,旨在帮助开发人员更新和调试他们的应用。有趣的是,这些改进说明了两种语言之间的交叉授粉。例如,数据断点对于 C#开发人员来说是新的,但在 C++中已经存在一段时间了。另一方面,C#有一个快速的动作,为几个版本添加对缺失包的引用。这相当于在 C++中将缺失的包添加到 CMake 中。
CMake 支持
CMake 是一个管理构建过程的开源工具。自 Visual Studio 2017 以来,它一直受 Visual Studio 支持,最新版本在许多方面对此进行了补充。
然而,你注意到的第一个变化可能会有点令人不安。CMake 菜单在 Visual Studio 2019 中不再是顶级菜单。但是不要害怕。并不是菜单项消失了。只是通过与 Visual Studio 的集成,这些项目已经移到了更合适的项目、构建、调试和测试菜单中。这种重组使普通 Visual Studio 用户更容易找到他们正在寻找的功能。直观地说,在 Build 菜单下找到 CMake Build 选项比在 CMake 菜单下更有意义。
CMake 设置编辑器
CMake 进程由 CMakeSettings.json 文件控制。这只是一个 JSON 格式的文本文件,包含用于控制 CMake 构建如何运行的属性。因为它只是一个 JSON 文件,所以可以使用任何文本编辑器进行编辑,包括在 Visual Studio 中。不过,根据开发者的反馈,Visual Studio 2019 包含了一个新的 CMake 设置编辑器,如图 8-3 所示。
图 8-3
CMake 设置编辑器
该编辑器可通过出现在现有配置列表底部的管理配置选项获得(见图 8-4 )。
图 8-4
访问 CMake 设置编辑器
编辑器的左侧是为您的项目定义的配置列表。列表顶部的按钮用于添加、删除和克隆配置。当您添加配置时,您会看到安装在您机器上的 CMake 配置模板列表(图 8-5 ,尽管您的列表可能会有所不同)。
图 8-5
添加 CMake 配置
屏幕右侧是管理实际设置的地方。有四个主要部分:常规、命令参数、CMake 变量和缓存以及高级。所提供的实际值取决于上下文(这超出了本书的范围)。例如,Linux 设置不同于 Windows 设置。但是任何开发人员都应该熟悉编辑这些值的机制。此外,默认情况下,高级部分是隐藏的。要查看和修改高级值,请单击编辑器底部的显示高级设置链接。最后,如果您想直接修改 JSON 文件,在编辑器的右上角,有一个编辑 JSON 链接,它将在文本编辑器中打开设置文件。
CMake 警告集成
当涉及到与 CMake 一起生成的警告时,Visual Studio 2019 包括几个不同的集成点。有些旨在帮助开发人员识别以前可能难以发现的问题。例如,不一致或不兼容的 CMake 设置(如使用 32 位生成器和 64 位配置)会生成警告,这些警告可在“错误列表”窗口中找到。
除了这种集成,您还可以配置生成的错误消息的级别。默认情况下,只显示错误。警告最终被禁止。但是,在 Visual Studio 2019 中,您可以对该行为进行更多的控制。图 8-6 显示了通过工具➤选项菜单可用的 CMake 设置页面。
图 8-6
通过选项对话框进行设置
在图 8-6 的右侧中间,有一个启用详细 CMake 输出的选项。如果启用此选项,将不会禁止显示警告。如果您需要更深入地研究 CMake 问题,可以选择启用内部诊断日志来进行调试。这使得 CMake 本身生成的消息可以选择记录到文件中,或者显示在输出窗口的单独窗格中。
仅我的代码支持
“仅我的代码”是一项调试功能,在 C#和 Visual Basic 的许多版本中都是 Visual Studio 的一部分。前提是,在单步调试代码时,调试器将跳过您没有编写的代码。这将包括系统调用或某些类型的生成代码。这个想法是,在调试应用时执行的自动单步执行只会在您编写的代码处停止。
关于默认添加一个类似“仅我的代码”的特性,有趣的事情之一是总会有一群人需要能够关闭它。正如你所料(或者只是认为这是一个幸运的巧合),有一个设置可以做到这一点。在图 8-7 中,可以看到调试选项卡。
图 8-7
启用“仅我的代码”设置
突出显示的设置用于启用或禁用“仅我的代码”。作为一个警告,这个设置不是特定于语言的。如果您禁用了“仅我的代码”并开始调试 C#应用,您可能会发现自己步入了意想不到的函数。
控件
如果你与 XAML 合作,无论是作为 WPF 开发人员还是因为你正在编写 UWP(通用 Windows 平台)应用,那么 Visual Studio 2019 中的添加和改进都非常令人兴奋。主要重点是改进您如何创建和调试 XAML,但还有其他一些您会发现非常有用的花絮。
热重装
您可能知道此功能是“XAML 编辑并继续”,但在此版本的 Visual Studio 中,它已被重命名为“XAML 热重新加载”。更改名称的理由是这个名称更接近于 web 应用中常见的热重装功能。使用 XAML,当重载发生时,应用不会暂停,正如“编辑并继续”这个名字所暗示的那样。相反,它只是对呈现的表单的即时自动更新。
热重载的目标是解决 XAML 设计者的一个长期问题——使用应用的运行时上下文调试应用的外观。在热重新加载之前,您需要查看应用,决定要更改什么,停止应用,进行更改,然后重新运行应用。如果需要多个步骤才能回到应用中的目标点,那就太糟糕了。
使用热重装,您可以在调试模式下运行应用时更改应用的 XAML。更改会立即被检测到,甚至不需要保存文件。然后为新的 XAML 触发组件的呈现,并且应用的外观会立即修改。
热重装有一些限制。从平台的角度来看,WPF 应用需要运行。NET Framework 4.6 或更高版本以及。网芯 3。而且需要 Windows 7 以上。对于 UWP 应用,您需要安装 Windows 10 或更高版本,SKD 版本为 14393 或更高版本。
但是局限性并不仅限于平台。存在一些功能性挑战,但数量惊人地少。大多数问题都可以通过暂停应用来解决。
例如,您不能在热重装中修改事件处理程序属性。也不能在应用运行时向其添加控件、类或其他文件。这包括对 NuGet 包的任何管理。而且,虽然它只适用于 UWP 应用,但您不能修改 x:Uid 指令。
从概念上讲,所有这些都是有意义的。在每种情况下,您实际上并没有改变应用的外观,而是改变了应用可执行文件的一些组件。因此,如果您需要进行类似的更改,暂停或重启应用是必要的。
从 Visual Studio 2019 开始,支持一个强大的功能。这就是调整 x:Bind 标记扩展的值的能力。这意味着作为热重新加载的一部分,您可以更改 XAML 中属性值的来源。
由于存在一些潜在的障碍,微软在应用内工具栏中添加了一个通知,该通知在调试 XAML 应用时可用。图 8-8 显示了当热重装可用时工具栏的样子。
图 8-8
应用内 XAML 工具栏
您可以通过单击工具栏右侧的 v 形图标来显示或隐藏消息。
最后一个考虑事项–
只有当您在调试模式下从 Visual Studio(或者 Visual Studio 代码)中启动应用时,热重载才可用。如果使用附加到进程功能将调试器附加到已经运行的应用,则热重新加载功能将不可用。
应用内工具栏
因为我们已经看到了应用内工具栏,所以有一些小的调整来提高它的可用性。
现在,您可以在应用的顶部重新定位工具栏。工具栏的左侧有一个抓手,可以让你在窗口顶部拖动工具栏,如图 8-9 所示。
图 8-9
应用内工具栏重新定位
此外,您可能会注意到应用内工具栏的样式略有不同。在 Visual Studio 的早期版本中,工具栏总是深色的。它已经过修改,因此可以选择您在 Visual Studio 中使用的主题。
从功能的角度来看,有一个变化可能需要一些时间来适应。左侧第二个图标用于选择应用中的元素,并在实时可视化树窗格中选择相应的元素。以前,您可以继续选择元素,并让该行为继续下去(即,每个选择的元素修改活动视觉树)。然而,这令人困惑,因为它不同于各种浏览器中的开发工具的工作方式。应用内工具栏已经更改,因此一旦您选择了一个元素,后续的点击将不会选择其他元素。
只是我的 XAML
Visual Studio 2019 在调试会话期间有一个名为实时视觉树的窗口可用。它包含应用的整个视觉树,允许您深入甚至单个控件,以查看功能和视觉效果是如何实现的。尽管毫无疑问,实时视觉树具有很大的价值,但它一直受到一个重大问题的困扰。它会很快变得非常混乱。考虑图 8-10 中的实时视觉树。
图 8-10
具有单个按钮的表单的实时可视化树
该树是从一个包含一个按钮的表单中生成的(如图 8-9 所示)。树的问题是它不能代表开发人员如何可视化表单。他们认为这是一个带有按钮的表单,而不是一个带有网格等内容呈现器的装饰器。从大多数开发人员的角度来看,动态视觉树应该只包含他们添加的组件。额外的元素虽然完全准确,却妨碍了对树的处理。
为了改善这种情况,Visual Studio 2019 引入了“仅我的 XAML”功能。类似于调试中的“仅我的代码”,当“仅我的 XAML”打开时(这是默认设置),实时视觉树仅显示开发人员添加的组件。图 8-11 显示了启用“仅我的 XAML”时同一表单的实时视觉树。
图 8-11
打开“仅我的 XAML”的实时视觉树
虽然不那么杂乱的视图肯定有好处,但有时您可能需要所有的细节。您可以使用“实时可视化树”窗格工具栏中最右侧的图标在启用和禁用“仅我的 XAML”之间切换。或者,如果你喜欢一直关闭它,在工具➤选项对话框的调试窗格中有一个设置(见图 8-12 )可以用来禁用它。
图 8-12
选项对话框仅显示我的 XAML 设置
。净核心
。NET Core 3.0 对于 Windows 应用的开发人员来说是一件大事,不仅仅是因为版本号比 2.0 大。3.0 版本完成了过去 5 年的旅程。当然,现在您可以构建在 Windows 上运行的服务器应用。而且这些应用将在 MacOS、众多版本的 Linux、iPhone 和物联网设备上运行。
客户端对的支持。网芯 3.0 同样令人印象深刻。你可以用。NET Core 3.0(只能运行在 Windows 平台上,不能运行在 Linux 或 MacOS 上)来构建 Windows 窗体和 WPF 应用。这意味着您可以将旧的桌面应用迁移到现在,并且知道它们在将来也会得到支持。除了对德高望重者的支持,还有对尖端技术的支持。。NET Code 3.0 提供了使用 Blazor 编写客户端 web 应用的能力。
WPF 表单设计器
而最初的情绪时,听到有一个设计师可申请 WPF 在。NET Core 可能是怀疑论,现实几乎是令人失望的。图 8-12 显示了的 WPF 表单设计器。NET Core 3.0,以及工具箱。3
图 8-13
WPF 表单设计器。网络核心 3.0
失望的原因?窗体设计器的外观和行为与 WPF 窗体设计器在 Visual Studio 的最后几个版本中的外观和行为非常相似,也就是说,它将会让 WPF 开发人员感到舒适和熟悉。
为了了解设计者,有一个针对 WPF 应用的项目模板。NET 核心。以这种方式创建新项目时,WPF 窗体设计器可作为默认设计器使用。因此,您可以双击 XAML 文件并打开设计器,就像您一直在做的那样。
WPF 支持的更有趣的作品。NET 核心正在从现有的 WPF 应用迁移到。NET 核心。有一些基本的差异需要解决,但是这样做的步骤有很好的文档记录,GitHub 上有一个示例项目(Bean Trader,在 https://github.com/dotnet/windows-desktop/tree/master/Samples/BeanTrader
找到)。
一般来说,迁移问题分为两大类。首先,添加引用包的方法需要更改为使用 NuGet。更重要的是,NuGet 引用需要作为<PackageReference>
元素包含在.csproj
文件中,而不是有一个单独的packages.config
文件。虽然这不是一个要求,但是使用针对。NET 标准,而不是。NET 框架。这不是必要条件的原因是。NET Framework 开发工作已经指向目标。净标准。结果是,。NET Framework 4.7.2 具有坚实的。NET 标准 2.0 支持。并且该支撑在两者之间包括足够的重叠。网芯和。NET Framework 表面上看,许多包无需修改就可以工作。然而,如果有问题,你不会发现它,直到遇到运行时异常。
第二类也涉及到。网芯和。NET 框架。您的应用可能正在使用在任何或所有平台上都不可用的功能。NET 核心支持。如前所述,微软提供了一个. NET 可移植性分析器( https://docs.microsoft.com/en-us/dotnet/standard/analyzers/portability-analyzer
)。该工具帮助您识别可能出现兼容性问题的区域,并能够识别。您的应用使用的. NET 核心。这在您确定能够支持您的应用的平台时非常有用。
Windows 窗体设计器
使用 Windows 窗体的流程。NET 核心设计器几乎等同于 WPF 应用。不同之处在于,虽然安装了设计器,但它仍处于预览模式。因此,默认情况下它是不启用的。相反,第一次通过双击 Windows 窗体组件启动设计器时,您会在顶部看到一个黄色通知栏,要求您启用设计器。通过工具➤选项对话框中的预览特征选项卡启用设计器(见图 8-14 )。请注意,在设计器可用之前,您需要重新启动 Visual Studio。
图 8-14
启用 Windows 窗体。网络核心设计者
Note
Visual Studio 2019 的早期版本(16.5 之前)具有 Windows 窗体。NET Core Designer 作为一个单独的 VSIX (Visual Studio 扩展)包,需要在使用前下载并安装。在 Visual Studio 2019 的未来版本中,在设计器不再处于预览状态后,Windows 窗体设计器很可能会默认启用。
但是一旦启用了设计器,你会发现体验和你习惯的几乎一样。图 8-15 显示了 Windows 窗体。NET 核心设计师。
图 8-15
Windows 窗体。网络核心设计者
转换为的过程。NET 核心版的 Windows 窗体类似于 WPF。一样的。在 WPF 部分描述的. NET 可移植性分析器工具对于识别潜在的问题是有用的。如果你使用的是 NuGet 包,那么把它们升级到目标包是个好主意。NET 标准,而不是。NET 框架。
Windows 窗体应用最有可能出现问题的一个方面是。NET API。这只是 Windows 窗体时代的一个结果。为了帮助缓解这些情况,微软提供了一个 Windows 兼容包。这是一个添加了许多 API 的库,这些 API 可用于 Windows 窗体应用,但在。NET 核心。当您考虑到软件包中大约有 20,000 个 API 时,您会很快意识到从 Windows 窗体移植时它是多么有用。您可以在 https://docs.microsoft.com/en-us/dotnet/core/porting/windows-compat-pack
找到关于该软件包的更多信息,包括如何将其添加到您的应用中。
计算机编程语言
Visual Studio 中对 Python 开发的支持是通过一组名为 Python Tools for Visual Studio(PTVS)的工具提供的。这些工具都是开源的,源代码可以在 GitHub ( https://microsoft.github.io/PTVS/
)上找到。但是,它们受 Microsoft 支持,并且可以通过将 Python 开发工作负载包含在 Visual Studio 安装程序中而包含在 Visual Studio 中。它也包含在数据科学和应用工作负载中。
Visual Studio 2019 中可用的 PTVS 版本的改进主要围绕单元测试功能。包含了对 pytest 单元测试框架的支持,以及使用从文件夹打开功能创建单元测试的能力。
设置使用 pytest 只是在早期版本的 PTVS 中如何设置 unittest 框架的一个扩展。右键单击项目名称,并从上下文菜单中选择属性。这将显示项目的属性。导航到左侧的测试选项卡。出现的下拉菜单(如图 8-16 所示)包括 unittest 和 pytest。
图 8-16
为 Python 项目设置单元测试框架
现在。在执行测试发现过程时,将使用 pytest 的 ini 配置。如果您打开 Test Explorer 窗口,您将看到通过使用作为配置文件一部分的模式确定的测试列表。请记住,发现过程可能需要一分钟才能完成。
Visual Studio 2019 支持 Python 开发的打开文件夹场景。在这种情况下,您不是打开一个项目文件,而是将一个目录的内容作为整个 Python 项目使用。这是一种非常流行的使用 Visual Studio 来处理不是最初在 Visual Studio 中创建的现有 Python 项目的方式。
当使用打开文件夹方法时,您仍然可以确定要使用的测试框架。在 PythonSettings.json 文件中,可以添加许多 Visual Studio 将用来配置测试发现和执行的属性。以下是使用 pytest 的 PythonSettings.json 文件的示例:
{
"TestFramework": "pytest",
"UnitTestRootDirectory": "tests",
"UnitTestPattern": "test_*.py",
"SearchPaths": [ ".\\src" ]
}
属性标识了单元测试框架。UnitTestRootDirectory
指定了包含测试的目录的名称。该目录将被扫描以查找匹配UnitTestPattern
模式的文件。最后,如果您的源代码文件不在包含tests
目录的同一个文件夹中,您需要指定包含源代码的路径。
摘要
Visual Studio 的体系结构是这样的,来自微软内部和外部的许多不同的团队都能够对环境的改进做出贡献。向更模块化结构的转变还允许不同的团队按照他们自己的节奏发布功能,而不是依赖于 Visual Studio 新版本的发布时间。因此,您会发现 Visual Studio 中新增的功能列表很难跟上。本章主要关注范围内的主要变化,但这不是一个广泛的列表,因为即使在本书出版时,不同的工具团队仍在继续发布新的功能。
本章有意遗漏的领域是与云计算机和容器相关的 Visual Studio 2019 的更改。这是因为它们中的每一个都足够重要,值得拥有自己的一章。这些变化将在接下来的两章中介绍。
九、Azure 工具
以这样或那样的方式,云已经成为大多数软件开发的中心。无论您是编写桌面应用、创建网站,还是使用移动设备,都可能有一个组件利用云,或者在适当的情况下能够利用云。
Visual Studio 已经发展到接受这种趋势。你可以在本书前面描述的实时分享功能中看到一些。但它也全心全意地支持使用 Microsoft Azure 作为云开发工作的基础。本章的重点是描述如何提供这种支持。
本章不会花时间详细描述微软 Azure 是什么。它包含了太多不同的片段,几个段落无法做到公正。很有可能在你写下这些段落和阅读它们之间,事情会有所改变。这就是云世界的发展速度。因此,Azure 的任何细节将只在所涉及的 Visual Studio 组件的上下文中提及。
云浏览器
Visual Studio 中 Azure 体验的核心是 Azure 云浏览器。通过云浏览器,您可以跨不同的资源组和订阅查看您的 Azure 资源。除了查看它们的属性,还可以对它们执行一些操作(列表取决于资源),包括开发人员识别和解决问题所需的诊断类型。
当您通过 Visual Studio 安装程序安装 Azure 工作负载时,云浏览器默认可用。安装后,它将作为云浏览器选项出现在“视图”菜单上。选中该选项后,出现云浏览器窗口,如图 9-1 所示。
图 9-1
云浏览器
图 9-1 显示了云浏览器的基本外观。窗口顶部是当前用户可用的 Azure 订阅列表。窗口底部的端口当前是空的,用于查看和修改单个资源的属性,以及针对资源启动不同的操作。
默认情况下,用于登录 Visual Studio 的凭据用于检索 Azure 资源。但是你不限于使用那个账户,甚至不限于使用一个账户。若要添加帐户,请点按面板顶部工具栏中的帐户图标(形状像人的头部和肩部的图标)。这将打开一个区域,在这里您可以看到与您的 Visual Studio 实例的 Azure 相关联的不同 Microsoft 帐户(参见图 9-2 )。
图 9-2
在云浏览器中选择订阅
对于每个帐户,您可以看到与它们相关联的不同订阅。通过选中和取消选中相应的复选框,您可以指定这些订阅的资源在 Cloud Explorer 中是否可用。在列表的底部,有一个用于管理通过此窗格可见的帐户的链接。如果要添加或删除账户,点击链接显示如图 9-3 所示的对话框。
图 9-3
将 Microsoft 帐户与 Visual Studio 关联
这是用于定义 Visual Studio 帐户设置的同一个屏幕。在对话框的右下角,有一个关联帐户的列表。您可以添加帐户、移除帐户或对帐户应用过滤器。在这三个函数中,只有应用过滤器需要进一步解释。
可以将多个 Microsoft 帐户链接在一起,这样即使使用不同的电子邮件地址,它们也会被视为单一登录。这样,您只需登录一次,就可以访问每个关联帐户。通过应用过滤器,你可以看到链接帐户的列表,并选择在访问 Azure 时要使用的帐户。图 9-4 显示了点击应用过滤器时出现的对话框。
图 9-4
对 Microsoft 帐户应用筛选器
如果有 Azure 订阅与任何选中的 Microsoft 帐户相关联,它们将出现在如图 9-2 所示的列表中。取消选中电子邮件地址将从列表中删除任何关联的订阅。
资源
一旦确定了帐户并选择了所需的订阅,您就可以返回到图 9-1 中的基本云浏览器。每个订阅都在树视图中可用。通过展开订阅,您可以看到按资源类型或资源组收集到组中的资源。图 9-5 和 9-6 显示了同一订阅的两个视图。
图 9-6
云浏览器的资源组视图
图 9-5
云浏览器中的资源类型视图
资源类型视图根据资源类型将订阅中的资源划分为预定义的分组。如果您想要查看订阅中的所有数据库或虚拟机,它们将在您展开相应的节点时出现。
“资源组”视图将资源分成您定义的组。Azure 中资源组的概念是由管理员放入桶中的资源集合。也许资源是按部门、项目或地点分组的。选择权由管理员决定。
属性和操作
对于任何单个资源,都有许多关于资源的属性,以及可以在资源上执行的操作,这些都可以通过云资源管理器获得。这些显示在窗格底部的选项卡区域中。
要使用这个区域,首先要在树中导航,找到想要查看的资源。当您选择它时,适当的属性会显示在窗格的底部,如图 9-7 所示。
图 9-7
云浏览器中的属性窗格
显示的属性是只读的。如果你想对它们进行修改,那么你必须进入 Azure 门户网站。属性列表取决于资源的类型。有些人除了名字和类型之外什么也没有。其他人提供了更多的细节。
在操作窗格上也是如此,如图 9-8 所示。
图 9-8
云浏览器中的操作窗格
在操作窗格中,有一个可以对选定资源执行的操作列表。与属性相比,可能的操作列表会因所选资源而异。至少,每个资源都支持在门户中打开操作。这将启动 Azure 门户网站,自动将您置于所选资源中。此外,如果所选资源有任何子资源,将从这里进行搜索并执行刷新操作。所有这些都可以在图 9-8 中看到,都是与一个 App 服务相关的动作。然而,从图 9-8 中,您还可以看到其他操作,例如启动和下载发布配置文件。这些是特定于应用服务的,如果你转移到不同类型的资源,它们就会消失。
Note
如果在云浏览器中右键单击某个资源,也可以使用操作窗格中可见的选项。
物联网中心
云浏览器的一个新功能是支持物联网。Azure 支持创建物联网中心。对于物联网开发来说,物联网中枢是一种中央服务,通过它可以执行通信、身份验证和监控。如果您将您的物联网环境想象为一个工厂车间(见图 9-9 ),其中有数百个设备发送遥测数据并执行操作,那么物联网中枢就是该网络的中心,将这些设备相互连接(如果需要)并与其他服务连接。
图 9-9
工厂中的物联网
就云浏览器而言,在许多方面,物联网中心就像任何其他 Azure 资源一样。它在资源树中可见,并且具有可用的属性和操作。图 9-10 显示了它在树和动作列表中的位置。
图 9-10
云浏览器中的物联网中心操作
列表中的操作开始让人了解云浏览器具有的与物联网设备相关的功能。您可以在集线器中创建设备,可以是常规设备,也可以是边缘设备。这两种设备的区别在于它们的功能。常规物联网设备是连接到无线网络的非传统计算机(即,不是笔记本电脑、台式机、平板电脑或手机),能够发送数据,有时还能接收命令和执行操作。
边缘设备是具有允许其作为常规设备的监控能力的设备。它与常规设备通信,收集数据,然后将数据传回集线器。通常,它能够运行程序(通常使用 Docker 之类的容器基础设施部署),这使它成为半自治的。
除了通过云浏览器创建设备,您还能够为物联网中心生成共享访问签名(SAS)令牌。物联网集线器和设备使用此令牌来提供基于声明的身份验证机制。
云浏览器和物联网设备提供的更有趣的功能之一始于监控作为物联网中心一部分的事件端点的能力。事件端点是相关设备(常规设备和边缘设备)发送任何数据的地方。通过开始监视事件端点,您可以看到输出窗口中显示的所有传入消息。
这种监控功能很有用,但并不止于此。如果您在云浏览器中选择一个设备,操作列表会发生变化,如图 9-11 所示。
图 9-11
常规物联网设备的操作
与物联网中心一样,您可以监控特定设备的传入事件。此选项可用是因为常规物联网设备可能拥有相关的子设备。因此该选项监视来自这些子设备的任何传入请求。
当谈到物联网集线器和设备之间的消息传递时,有四种操作可用。您可以开始或停止监控 C2D 消息。在此操作中,“C”代表云,“D”代表“设备”因此,你所监控的是从云(物联网中心)传递到设备的信息。如果您想要查看 D2C 消息(即设备到云),您可以监控物联网中心的事件端点消息。
但是监控并不是 Cloud Explorer 所提供功能的极限。您还可以在物联网集线器和设备之间手动发送消息,反之亦然。“将 D2C 消息发送到物联网集线器”操作会打开一个对话框,允许您输入将发送到集线器的消息,就像该消息是由设备生成的一样。“将 C2D 消息发送到设备”操作允许您输入一条消息,该消息将被传输到设备,就像来自物联网集线器一样。通过所有这些不同的选项,您可以非常灵活地诊断或纠正物联网基础设施中出现的任何问题。
对于您可能需要对物联网设备进行的任何诊断工作,还有一些其他措施可以提供帮助。有一个操作(调用设备直接方法)允许您将消息直接发送到特定的设备。当动作被触发时,系统会提示您输入方法名和 JSON 格式的有效负载。直接方法的想法是使用 HTTPS 将请求发送到设备的 URL。该请求包括指定的方法名和有效负载。预计设备将响应请求,但不要求有任何有效载荷。请求的结果出现在输出窗口中。
编辑设备 Twin 操作允许您在 IoT Hub 中编辑描述设备的 JSON 文件。这个 JSON 文件被称为 device twin,因为它描述了设备的属性和功能。它还包含设备的状态信息,因此可以使用设备提供的数据定期更新。
“创建模块”操作允许您为选定的 IoT 设备创建模块。物联网世界中的模块是一个独立的命名空间,通过它可以访问设备。然后,命名空间可以被赋予不同的访问规则,以便它们可以彼此独立地受到保护。远程维护的自动售货机就是一个例子。虽然自动售货机被认为是单个设备,但机器中可能有多个传感器。例如,一个传感器可能指示机器中仍有多少物品,而另一个传感器可能指示剩余的零钱数量。每组数据可能由不同的部门负责。在这种情况下,设备将包含两个模块,一个公开与清单相关的消息,另一个公开与变更相关的消息。
触发创建模块操作会向物联网设备添加子项。您输入模块的名称。如果您右键单击该模块(或查看其操作窗格),您可以编辑该模块的模块孪生。类似于 Device Twin,这是一个基于云的 JSON 文件,描述了模块的元数据、状态和功能。
最后,为设备操作生成 SAS 令牌。这是为物联网集线器创建 SAS 令牌的相应操作。该操作会生成 SAS 令牌,设备可以使用该令牌与物联网集线器进行通信。完成生成所需的输入是令牌的生命周期(以小时为单位)。该值决定了在需要生成另一个令牌之前,该令牌将持续有效多长时间。
Azure 函数
Azure 函数的官方定义(改写自 Azure 文档)是一种无服务器的计算服务,它运行事件触发的代码,而不需要提供支持基础设施。这个定义中有很多概念,所以让我们用几个段落来分解细节。
Azure 函数的根本目标是运行一段代码。它与您可能运行的其他代码的不同之处在于,它被设计成无需预先部署服务器就能执行这段代码,因此有了“无服务器”这个形容词。当您部署 Azure 功能时,您指定的是正在执行的代码,而不是它将在其中执行的环境。当它需要运行时,会在函数执行前动态构建环境。
代码本身可以用多种不同的语言编写:C#、Java、JavaScript、Python 或 PowerShell。支持的不仅仅是语言。您可以绑定不同类型的资源(队列、数据库、SendGrid 等。)以声明方式添加到您的函数中。对于 C#,属性用于将函数连接到资源。对于其他语言,绑定在一个function.json
文件中声明。
该功能的执行是通过几种不同的机制启动的。其中一个更简单的是计时器。在这种情况下,该功能按照常规的预定时间表执行。但该函数也可以基于外部事件开始运行,比如队列中消息的到达、HTTP 请求或 Azure 事件网格中引发的事件。通过这一系列的触发器,Azure 功能的使用数量非常多。所以我们来看看 Visual Studio 在这个过程中是如何使用的。
创建 Azure 函数
Visual Studio 2019 提供了一个项目模板,可以作为你的 Azure 函数的起点。只要您在 Visual Studio 实例中包含了 Azure 开发工作负载,就可以使用该模板。还要注意,为了部署 Azure 功能,你需要一个 Azure 订阅。除了用 HTTP 请求触发的 Azure 函数之外,您还需要有与函数相关联的 blob 存储。但是不要让需求压倒一切,尤其是在成本方面。有一些免费资源足以让你的第一个 Azure 功能运行起来。
Azure 函数的起点是使用内置模板创建一个新项目。使用文件➤新➤项目菜单项启动对话框(见图 9-12 )。
图 9-12
Azure 函数的新项目对话框
在对话框右侧显示的已安装模板列表中,找到 Azure Functions 模板。在图 9-12 中,顶部的搜索框已经被使用。选择模板,然后单击下一步。这将打开用于命名新项目和指定位置的对话框(图 9-13 )。
图 9-13
新建项目配置对话框
提供必要的配置信息后,单击“创建”按钮。这将打开用于选择您正在创建的 Azure 函数类型的对话框。图 9-14 显示了该对话框。正是在这个对话框中,你开始有特定于 Azure 功能的选择。
图 9-14
创建新的 Azure 函数应用对话框
在创建 Azure Function 项目之前,您需要指定四个不同的属性。它们是平台、模板、存储帐户和授权。接下来的几节将更详细地介绍。
平台
Azure 函数支持三种不同的运行时环境,这些环境或多或少与不同版本的发布时间相对应。做出选择。对于 Azure 函数 v1。NET Framework 4.6 是受支持的运行时。对于 v2,增加了对的支持。网芯 2.2。最新的版本是 v3。支持网芯 3.0。
不同的版本也有不同的语言支持。Azure Functions v1.0 版仅支持 C#、F#和 JavaScript。在 v2 中,增加了 Java、PowerShell、Python 和 TypeScript。
还有一个变化会影响不同版本 Azure Functions runtime 的开发。随着从。NET 框架到。NET Core,一些不同的触发器和绑定(除了 HTTP 和 Timer)需要在绑定可用之前安装一个扩展。如果您使用下一节中描述的模板之一,就会安装该扩展。但是,如果您选择空模板,您将需要手动添加扩展。这可以通过一个 NuGet 包来完成,所以机制是熟悉的。
模板
在 Visual Studio 中创建 Azure Function 应用的优势之一是可以帮助您入门的模板。不一定需要模板。有一个可用的空模板,您可以手动添加必要的配置和扩展。但是如果你在开始之前知道你的函数将要使用的触发器,那么这个模板会让事情变得更简单。
触发器和绑定都是 Azure 函数的一部分,尽管只有触发器是模板的一部分。触发器是启动功能的事件。请记住,Azure 函数背后的思想是,它们是为了响应某些外部事件而执行的。触发器类型决定了生成外部事件的底层技术。绑定还指定了一种技术。不同之处在于,绑定可以是函数的输入或输出(或两者都是),而不是启动事件。
例如,考虑一个想要检索图像缩略图的传入 HTTP 请求。Azure 函数将由 HTTP 请求触发,使用 blob 存储绑定来获取请求的图像,然后输出包含图像缩略图版本的 HTTP 响应。触发技术是 HTTP。绑定是 blob 存储(输入)和 HTTP(输出)。
提供了许多内置模板。您可以在图 9-14 中看到运行时选择下方的部分列表。在下文中可以找到更完整的列表和说明。请注意,每个模板中的触发器只是一个起点。您可以在开发过程中添加或删除触发器和绑定。
Blob 触发器
当添加新的 blob 或更改现有 blob 时,Blog 触发器启动该函数。该函数被绑定到一个 Blob 存储帐户,该帐户是监视更改的存储库。当触发触发器时,新的或更新的 blob 作为参数提供给函数。
Cosmos DB 触发器
Cosmos DB 是一种 NoSQL 风格的数据存储技术。它允许将项目存储在容器中,其中“项目”是一段与模式无关的数据,类似于 MongoDB 中的文档。Cosmos DB 中的容器类似于数据库表,尽管更准确地说它是文档所在的名称空间。
每当修改或添加特定容器中的一个项目时,Cosmos DB 触发器都会调用该函数。触发器中包括修改或添加的项目。
事件网格触发器
Azure Event Grid 是一项允许开发人员轻松构建基于事件的消息应用的服务。它使用发布和订阅模型工作,方式与 Azure Service Bus 相同。但是,它不是传递消息,而是发送事件通知。是的,通知可以包括数据。但是这些数据只是事件的一部分,不会在事件引发后持续存在。在这方面,它不同于 Azure 服务总线。有了事件网格,发送者就不需要知道谁将处理事件或者事件是否会被处理。
每当相应的发布者引发事件时,事件网格触发器就会执行该函数。在这方面,事件网格触发器在事件网格支持的发布/下标模型中充当订阅者。
事件中心触发器
事件网格名称的相似性掩盖了它的不同之处。Event Hub 是一个 Azure 服务,支持数据流的捕获和处理。这是一个大数据管道,传递信息,如遥测或状态,可能来自多个并发源。通过这种方式,它将自己与事件网格和服务总线的离散负载区分开来。当你设计一个带有事件中心触发器的 Azure 函数时,你需要意识到每秒钟可能会发生成千上万个触发器。
当通过事件流接收到事件时,触发事件中心触发器。通常有与事件相关的数据,但它只是作为字符串值键入。
Http 触发器
当通过 HTTP 管道收到请求时,将触发 Http 触发器。这允许使用任何能够提交 HTTP 请求的技术来启动 Azure 函数,公平地说,这几乎是所有技术。
对于触发器的发送者,响应要么是 HTTP 200 状态代码(对于 Azure 函数 v1),要么是 HTTP 204 状态代码(对于 Azure 函数 v2 和 v3)。无论如何,没有迹象表明由事件触发的功能是成功还是失败。如果这是您的应用的要求,那么您需要向该函数添加一个 HTTP 输出绑定。
物联网集线器触发器
物联网中心触发器与事件中心触发器非常相似。物联网集线器负责发送与一个或多个物联网设备相关的事件数据流。随着每个事件的到来,Azure 功能被启动。与物联网中心事件相关的任何数据都以字符串数据类型传递给该函数。
队列触发器
队列存储是 Azure 中的一种存储类型,它模仿队列的功能。发送方发送的消息被接收到队列存储中。这些消息一直保留在队列存储中,直到被队列消息的接收者删除。
有了队列触发器,Azure 函数的执行就变成了排队消息的接收者。当消息到达队列存储时,函数被调用。排队的消息作为参数提供给函数。
服务总线触发器
Azure 服务总线是最古老的 Azure 技术之一。它支持企业服务总线的传统概念。消息以先进先出的方式接收和处理,并具有有保证的传递、事务支持和过滤功能。
服务总线触发器在消息到达服务总线时启动 Azure 功能。触发器既可以针对整个服务总线队列进行操作,也可以针对该队列中的一个主题进行操作。对于后一种情况,您将选择服务总线主题触发器,而不是服务总线触发器作为模板。
定时器触发器
虽然这个模板在列表中按字母顺序排在最后,但它在许多应用中是一个非常常见的请求。前提很简单。你希望有一个功能,你希望定期运行–
可能是每小时,每天,甚至更复杂的情况。但是在适当的时候,你希望函数被执行。定时器触发启用这种能力。
关于定时器触发器有趣的部分是如何指定时间表。Azure 函数使用 NCRONTAB 格式来计时。这种格式类似于 CRON 格式,具有允许识别精确到秒的执行时间的扩展(CRON 将执行限制到分钟)。
存储帐户
除了 Http 触发器之外,所有触发器模板都需要存储帐户。在创建对话框中,窗口右上角有一个下拉菜单,您可以在其中指定将要使用的存储帐户。
下拉列表中有三个选项:无、存储模拟器和浏览。对于大多数触发器(即除 Http 之外的所有触发器),您可以选择 Storage Emulator 作为起点。这允许你开发你的 Azure 函数,而不必首先识别存储帐户。相反,在开发应用时,它使用 Visual Studio 中的存储模拟器功能。但是,在部署完整的应用之前,您需要选择一个存储帐户。如果您没有选择,部署时会自动创建一个存储帐户。
如果您已经知道将在部署时使用哪个存储帐户,请选择浏览选项。这将打开一个对话框(如图 9-15 所示),可用于选择要使用的存储帐户。
图 9-15
选择现有存储帐户
批准
创建 Azure 函数项目时最后一个可用的选项是为 Azure 函数设置授权。用于设置值的下拉列表位于存储帐户的正下方。可用选项包括功能、匿名和管理。
这些选项中最简单的是匿名。使用该选项,根本不执行任何授权。任何请求都被接受,不做进一步的检查。
Function 选项利用作为请求的一部分发送的密钥来限制谁有权访问 Azure 函数。有两种类型的键可用:主机和功能键。你的 Azure Function app 公开的所有函数都使用主机密钥。一个功能键专门绑定到一个功能,这意味着您需要不同的功能键,每个公开的功能一个。
Note
认识到使用功能键并不是访问 Azure 功能的完全安全的方式是非常重要的。用于访问功能的密钥不一定是加密的。例如,如果您使用 Http 触发器,则可以在 URL 的末尾或标头中作为不记名 cookie 传递密钥。该密钥没有加密,因此,如果请求被捕获,恶意用户可能会查看并保存该密钥,并在将来使用它来发送请求。
最后一个选项是 Admin。这类似于 Function 选项,因为请求时需要传递一个键。不同之处在于,唯一允许的密钥类型是主机密钥。如果传递了功能键,则返回 HTTP 状态 403(未授权)。
Function 和 Admin 选项都要求任何传入的请求中都要有一个键。密钥的生成是通过 Azure 门户完成的。不提供使用云浏览器生成密钥的功能。
项目文件
一旦指定了 Azure 函数需要的所有信息,单击 Create 创建项目。只有几个文件包含在生成的项目中。有一个 Function1.cs 文件包含一个示例 Azure 函数。这是一个静态类,有一个 Run 方法,用各种属性修饰。通常,该类将被删除或重命名,以便为函数提供更具描述性的名称。
项目中还有另外两个重要的文件。它们都是 JSON 文件,包含函数使用的配置信息。
-
hosts.json
–
包含应用于函数应用中定义的所有函数的元数据。这包括实例模型(无论是否是单例模型)、日志记录细节、用于调用函数的触发器以及输入和输出绑定等细节。 -
local.settings.json
–
包含函数在本地运行时使用的设置。这包括连接字符串、应用设置(典型的键/值对)和宿主详细信息。该文件仅用于本地执行。当您部署函数应用时,您需要配置此文件中包含的设置,以便在 Azure 中使用。这将在下一节“部署 Azure 功能”中讨论
部署 Azure 功能
一旦你完成了功能的开发和测试,就该把它部署到 Azure 了。对于 Visual Studio,部署是通过使用发布选项启动的。如果右键单击该项目,可以单击“发布”菜单项。或者您可以使用“生成”菜单中的“发布”项。无论您使用哪种方式,第一次发布项目时,都会出现如图 9-16 所示的对话框。
图 9-16
选择 Azure 发布目标
在实际部署函数应用之前,需要创建发布概要文件。接下来的几个对话框是用来创建概要文件的。如果您已经有一个可用的配置文件,您可以通过单击左下角的 Import Profile 按钮来使用它。
在继续之前,您需要注意,当您选择目标时,图 9-16 中可见的目标列表可能不会全部出现。可能的目标列表取决于您导入的概要文件,以及您用来创建应用的模板。例如,如果您使用常规的。NET 框架应用,那么在几个段落中描述的 Azure 功能计划可能不会出现。
在左边,您从五个不同的发布目标中选择一个。最简单的是列表底部的文件夹选项。如果部署到文件夹,则会创建一个 ZIP 部署包。这个包是一个 ZIP 文件,它的结构是 Azure 可以识别的。它允许您创建一个部署包并将其交给某人,然后此人可以将其部署到他们自己的 Azure 订阅中。
其他四个选项分为两个独立的组。前两个是 Azure Functions 消费计划和 Azure Functions Premium 计划。对于这两个计划,您的函数主机是根据传入请求的数量动态添加和删除的。因此,从这个角度来看,您的功能可以根据需求扩大和缩小。这是你应该从 Azure 函数的无服务器环境中期待的。
这种差异与环境的环境准备度有关。对于消费计划,您为正在使用的资源付费。一旦你的函数用完了它们,你就不再为它们付费了。这包括内存和计算时间。但是当发出请求时,可能会有短暂的延迟。如果前一个函数已经完成,则内存和计算资源已经被释放。它们需要被重新分配以处理请求。这需要一小段非零的时间。
另一方面,premium 计划总是有一个 warm 实例备用。因此,无论何时收到请求,都会有内存和计算资源准备好处理它。自然,这是有代价的。除了运行该功能所产生的成本之外,您还需要为待机模式下维护的资源付费。
一般来说,你的选择取决于对你的职能的要求。如果对你的功能有持续的需求,那么溢价计划是有意义的。它确保了您的函数对每个请求的响应时间始终是良好的。如果对你的功能的需求是不稳定的或集中的,那么消费计划更有意义。响应时间可能不一致,但是您不会为只是偶尔需要的资源付费。
其他两个发布目标(Azure App Service Plan 和 Azure App Service Plan Linux)都涉及到将您的功能部署到应用服务虚拟机(VM)上。这可能是一个已经在你的订阅中托管一个或多个网站的应用服务。因此,通过这个计划,您可以选择您需要的大小(内核和内存的数量)和隔离(共享或专用)。而且两个方案之间,区别在于底层服务器操作系统是 Windows 还是 Linux。
对于除文件夹之外的所有发布目标,您可以选择创建新的应用服务或使用现有的应用服务。如果您选择使用现有服务,单击创建配置文件会显示一个类似于图 9-14 的对话框,您可以在其中浏览您的订阅以选择所需的应用服务。如果你想为你的 Azure 功能创建一个新的应用服务,点击创建配置文件会显示如图 9-17 所示的对话框。
图 9-17
为部署创建新的应用服务
此对话框中的可用信息用于配置将要创建的应用服务。需要明确的是,即使您当前正在创建用于部署的配置文件(而不是实际部署应用),如果您单击 Create 按钮,将会创建一个新的应用服务。换句话说,部署过程不会创建应用服务。你实际上正在创造它。
一旦您完成了概要文件的创建,或者如果这不是第一次发布函数应用,下一步就是实际触发部署。图 9-18 显示了用于开始部署到 Azure 的屏幕。
图 9-18
发布 Azure 函数
对话框的顶部是一个下拉列表,包含项目的发布概要文件列表。您在本节前面经历的步骤创建了一个概要文件(在一个.pubxml
文件中找到)。但是没有什么可以阻止您为一个项目拥有多个概要文件。发布过程非常简单,只需选择所需的配置文件并单击发布即可。下拉列表下方的区域是所选配置文件的设置摘要,这样您就可以在触发部署之前知道自己在做什么。
在对话框的右侧,有几个链接指向您可以采取的快速操作。顶部的链接“在云浏览器中管理”会打开云浏览器窗格(图 9-19 )。
图 9-19
云浏览器中的 Azure 功能应用服务
在 Cloud Explorer 中,您可以查看应用服务的部署槽,以及上传和下载与服务相关的文件(至少是文本文件,如hosts.json
和任何日志文件)。
第二个选项,编辑 Azure 应用服务设置,在本章前面提到过。local.settings.json
文件包含功能应用在本地运行时使用的设置。此点击允许您配置当您的函数应用在 Azure 上运行时将使用的设置。点击链接显示如图 9-20 所示的对话框。
图 9-20
定义 Azure 功能应用设置
为了了解对话框正在做什么,local.settings.json
文件的相关部分如下所示:
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
您可以看到该应用有两种设置。这两种设置出现在图 9-20 中,本地和远程值均可用。对于存储设置,指示在开发过程中应使用本地存储模拟器的值已被发布配置文件中指定的存储的连接字符串所替换。对于其他设置,复制了本地值。您可以更改已定义设置的值,包括附加设置(使用添加设置)链接,以及删除设置(使用每个设置右侧的 X。
除了在local.settings.json
文件中明确定义的设置之外,还有其他可以访问的环境值。由于它们可以被访问,所以当使用相同的机制将函数应用部署到 Azure 时,您可以指定所使用的值。
摘要
Azure 是微软领域中的一项主要技术,所以在 Visual Studio 中对它有相当多的支持是不足为奇的,无论是开发还是管理部署的应用。
除了 Azure 之外,还有另一项技术正在大举进军开发和部署领域:容器。容器不仅允许您将应用捆绑在一起,还允许您将与应用相关的基础设施捆绑在一个包中。然后,可以在小型、可任意使用的虚拟机上部署和执行该包。下一章将更加详细地介绍容器背后的概念,更重要的是,微软如何通过 Visual Studio 2019 支持它们。
十、容器和编排
观看新技术在开发生态系统中移动总是令人着迷的。如果你够老,你可能还记得从单一桌面应用到客户机/服务器架构的转变。然后一切都必须在网络上运行。然后回到面向服务的架构。然后再放到网上。
正如您所看到的,随着技术赶上前面迭代中的缺陷,似乎有一个有规律的架构模式周期。目前,选择的模式是将应用部署为微服务的集合。这些微服务可以打包到容器中,容器本身可以以可伸缩的方式部署到云环境中。管理容器部署的技术称为编排。
在本章中,我们将了解 Visual Studio 2019 为容器的创建提供的支持,以及通过编排对容器的管理。底层的容器技术将是 Docker,而 Kubernetes 的编排功能将被涵盖。
容器和码头工人
可视化用于部署软件的容器的最好方法之一是将其与原始容器–
进行类比,容器用于将货物从一个地方运送到另一个地方。最初,在船上运输货物时,货物是根据最适合货物本身的箱子包装的。小产品装在小盒子里。大型产品装在大箱子里。当试图将不同系列的产品装到一艘船上时,问题就出现了。想象一下,当每一件货物都是不同的尺寸和形状时,包装一艘船的货舱是一个多么大的挑战。这就像在现实生活中玩俄罗斯方块。而且往往会浪费很多空间。
容器使包装和运输货物的过程标准化。货船只接受容器,而不接受单独的包裹。形状规则。大小一致。易于将一个堆叠在另一个之上。一点也不浪费空间。通过增加一些标准功能,甚至可以让同一个容器从船到火车到卡车,一次也不打开。容器的发展彻底改变了这个行业。
对于软件部署,容器扮演着与运输容器相似的角色。如果您考虑部署一个应用需要什么,您将开始看到相似之处。软件通常有一系列的依赖关系,不管是显式的还是非显式的,当它被部署到操作系统、数据库、连接字符串、队列和其他资源时都必须考虑到。选项多且多样,这是问题的根源。当您创建包来部署应用时,您需要了解所有不同的依赖项以及如何解决它们。适应这一点的过程最终会使部署变得困难。
但是,如果软件被部署在一个已经包含所有依赖项的容器中,那会怎么样呢?因此,容器包括兼容的操作系统、应用所需的数据库以及应用所需的所有其他资源。像这样交付的应用只需要安装到网络环境中。希望访问该功能的其他应用将发送请求(通常,尽管不是必须,通过 HTTP)并接收结果。
自然,软件容器中还涉及到其他的复杂性。当涉及到如何共享数据或文件系统时,问题就出现了。但这个概念已经足够丰富,可以在 Visual Studio 2019 中讨论容器支持。但是仍然有一个问题,Docker 是什么,它如何融入这个世界。
Docker 是什么?
在容器领域,Docker 已经牢牢确立了技术领导者的地位。通过与领先的 Linux 和 Windows 供应商(包括微软)合作,他们开发了允许在内部和云平台上定义和部署容器的工具。
Docker 容器可以在 Linux、OSX 或 Windows 上本机运行,尽管限制是 Windows 容器(即使用 Windows 作为底层操作系统的容器)只能部署到 Windows 机器上,本机运行或作为虚拟机(VM)运行,而 Linux 容器只能部署到 Linux 机器或支持 Hyper-V Linux VM 的 Windows 机器上。
创建 Docker 容器的过程包括两个步骤。第一个是创建应用或服务,开发人员对此非常了解。为了在 Visual Studio 中创建一个可以 Dockerized 的应用(耶…那真的是一个词),你被限制在 ASP.NET,ASP.NET 核心,。NET 框架,以及。NET 核心应用。这是一把足够大的伞,大多数应用都可以放在它下面。这并不是说所有的应用都可以有效地容器化,只是说底层技术足够灵活,可以覆盖大多数情况。
创建 Docker 容器的第二步是打包应用。这相当于为应用创建一个部署。应用及其所有依赖项都放在一个容器映像中。该映像是应用部署的静态表示。当一个容器被创建时,图像实际上被实例化了。
为了创建 Docker 容器,您需要安装 Docker 桌面。它可以从 www.docker.com/products/docker-desktop
下载,并且是从 Visual Studio 中利用 Docker 所必需的。
使用 Docker 时,有几个额外的术语有助于理解:
-
Dockerfile
–
一定程度上,这是 Docker 的秘制酱。Dockerfile 是一个文本文件,类似于描述如何创建图像的批处理脚本。这包括如何安装所有需要的程序,需要复制哪些文件以及复制到哪里,以及正确配置运行时环境所需的任何其他命令。 -
存储库
–
先前创建的容器图像的集合。通常,每个图像都有一些共同点(属于同一个项目,同一图像的不同版本,等等。),但这不是硬性要求。 -
注册表是一种允许其他人访问存储库的服务。有一些公共注册中心,最常见的是 Docker Hub。但也有可能托管私有注册中心,无论是在企业环境中还是通过 Docker Hub 提供的付费服务。
-
Docker Compose
–
一个用来定义 Docker 图像集合的工具,这些图像组成了一个更复杂的应用。Compose 由命令行界面和基于 YAML (YAML 不是标记语言)的文件格式组成。通过 YAML 文件,您可以获取先前定义的容器图像,并描述如何将不同的图像组合起来以创建更大的应用。然后,可以使用该文件通过一个命令实例化图像。
Docker 和 Visual Studio
Visual Studio 中有两种级别的 Docker 支持。首先,如果您只想创建一个容器,Visual Studio 允许您在创建项目时或之后添加对 Docker 的支持。第二层涉及编排容器,该术语用于描述在 Docker Compose 支持的单个应用中使用多个容器。Visual Studio 支持使用 Docker Compose 或 Kubernetes 进行编排。本节的其余部分将介绍第一级支持。为编排提供的支持将在后面的章节中介绍。
如果您正在创建一个新项目,那么当您选择想要使用的特定模板时,就可以添加对 Docker 的支持了。图 10-1 显示了选择 ASP.NET 核心 Web 应用模板后出现的对话框。它在风格上是相似的,更重要的是,在 Docker 选项的位置上,与其他应用类型的对话框相似。
图 10-1
ASP.NET 核心网站模板选择
在右侧的“高级”部分,有一个标记为“启用 Docker 支持”的复选框。选中时,生成的项目将包含支持 Docker 所需的文件。此外,包含 Docker 文件运行时平台的下拉菜单也被启用。选项有 Linux 和 Windows,您可以选择最适合您的应用部署计划的操作系统。一旦创建了应用,就很有可能改变这个选择。如果您正在创建. NET Framework,则不会出现运行时下拉列表,因为它们只能在 Windows 下运行。
如果您有一个想要添加 Docker 支持的现有项目,请在解决方案资源管理器中右键单击该项目并选择添加➤ Docker 支持(如图 10-2 所示)。
图 10-2
向现有项目添加 Docker 支持
如果您要添加对. NET 核心或 ASP.NET 核心应用的支持,系统会提示您指定预期的运行时是 Linux 还是 Windows。为了。NET Framework 项目,则不会出现此提示。
Docker 支持的添加导致了两个文件被添加到项目中,以及一个 NuGet 引用。NuGet 引用是 Microsoft . visual studio . azure . containers . tools . targets。此引用是支持在项目中启用容器所必需的。这两个文件是 Dockerfile 和. dockerignore 文件。
默认 Dockerfile 是一个简单的文件,用于定义基本运行时,然后提供如何构建和发布应用的指令。以下是 docker 文件,它是在您创建 ASP.NET 核心项目时最初添加的。不同的项目类型会有不同的初始 Dockerfile,但基本内容和目的是相同的。
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-nanoserver-1903 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-nanoserver-1903 AS build
WORKDIR /src
COPY ["AspNetCoreSample.csproj", ""]
RUN dotnet restore "./AspNetCoreSample.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "AspNetCoreSample.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "AspNetCoreSample.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AspNetCoreSample.dll"]
应用的基本运行时在第一个语句块中声明。nanoserver-1903 参考指定将使用 Windows Server 版本 1903。如果您想查看公共注册表中可用的其他标签,您可以在 https://hub.docker.com/_/microsoft-windows/
找到该列表(查看 Nano Server 和 Windows Server 代码实例的相关存储库)。找到可以用作容器基础的 Linux 服务器列表要稍微困难一些,因为您需要知道您要部署到的 Linux 版本(例如,Ubuntu、Debian 等。).
第一个脚本块还指定了要公开的端口 80 和 443 (HTTP 和 HTTPS)。
脚本的第二部分定义了构建时发生的事情(即,当执行dotnet build
命令时)。这里的步骤包括执行恢复,更新项目使用的任何包,将项目文件复制到构建位置,然后直接针对项目执行构建。复制步骤很重要,因为它使用.dockerignore
文件来决定哪些文件应该或不应该被复制。
脚本的第三部分定义了 Docker 的发布功能。在这种情况下,它只是对项目文件调用发布命令。
最后,第四个脚本块执行最后一步,使用基本运行时映像和发布的应用的组合来创建静态容器映像。
如前所述,还有一个.dockerignore
文件被添加到项目中。该文件包含一组模式,用于指定在 Docker 容器创建过程中不应该复制的文件。默认情况下,这包括像node_modules, bin
和obj
这样的文件夹和像.gitignore
这样的文件以及各种设置文件。当然,该文件完全可以由您定制,因此您可以确保只发布 Docker 容器所需的内容。
在 Docker 中运行
当您将 Docker 支持添加到项目中时,Visual Studio 中还会添加一个其他元素。Visual Studio 顶部的工具栏现在包括 Docker 作为可能的调试目标(见图 10-3 )。
图 10-3
在 Docker 中启动应用
添加此选项意味着当您调试应用时,您可以选择在 Docker 环境中运行应用,而不是在本地 IIS 或 IIS Express 等环境中运行。
将 Docker 设置为调试平台后,运行调试会话首先要创建一个 Docker 容器映像,并将该映像加载到 Docker 中以供执行。
为了成功完成这些步骤,需要满足一些标准。首先,您需要让 Docker 已经在您的机器上运行。安装它是不够的。它需要运行。现在,可以配置 Docker 在你启动机器时自动启动。但是如果没有该设置,您需要手动启动它。
其次,您的本地 Docker 需要配置为使用正确类型的运行时容器。一旦 Docker 运行,你可以通过右击系统托盘中的图标并选择适当的命令(切换到 Windows/Linux 容器)来改变容器类型,如图 10-4 所示。
图 10-4
切换 Docker 容器类型
一旦容器类型设置正确(完成更改需要一些时间),您就可以正常运行应用了。
第一次通过 Docker 运行项目时,可能会提示您信任 SSL 认证。你会看到一个如图 10-5 所示的对话框。
图 10-5
信任 SSL 证书
当您选择信任该证书时,可能还会提示您安装自签名证书。一旦您信任并安装了认证,过一会儿,您将看到一个浏览器页面,您的应用在其中运行(无论如何是一个 web 应用)。您可以调试您的应用,就像您在本地运行它一样。
容器视图
当容器运行时,您可以通过容器窗格查看有关它的信息。您可以通过查看➤其他窗口➤容器菜单项查看该窗口。图 10-6 显示了当前运行单个容器的容器窗格。
图 10-6
容器窗格
“容器”窗格有两个主要功能。第一个是可视化关于特定容器的信息。在图 10-6 中,所选容器有四个信息标签。出现的选项卡“环境”显示容器的配置信息。其他选项卡显示暴露的端口、应用生成的日志以及属于容器映像一部分的文件。
标签中的信息应该是只读的。是的,当容器中运行的应用生成消息时,日志文件会更新,但是您无法通过选项卡控制容器的执行。但是,通过容器窗格,您可以执行影响应用的命令。
在左侧容器列表上方的工具栏中,您可以使用左侧的两个图标(三角形和正方形)来启动和停止容器。还有一个从 Docker 中移除容器的图标(X)。但是,如果您希望能够在容器中执行命令,终端图标(左起第四个,看起来像一个盒子中的命令提示符)符合要求。
当您点按终端图标时,会打开一个命令行窗口。它看起来像任何其他命令行窗口,除了这个是连接到运行的容器。当您执行命令时,它们是针对容器实例执行的。而且,由于它是命令行,您可以根据需要运行各种命令来查看状态或修改图像。
但是,请记住,您所做的任何更改都只适用于当前正在执行的映像。当您关闭并重新启动映像时,所做的更改将会消失。如果您想让它们永久存在,您需要修改 docker 文件,以便正确配置您的映像。
管弦乐编曲
现在已经介绍了容器的概念,让我们添加一些如何管理容器的内容。如果您开发了一个使用单个容器的应用,那么从部署、生命周期和资源的角度来管理它是相当容易的。即使您的应用由运行在十几个容器上的三个或四个服务组成,管理起来也不会太困难。然而,一旦您进入运行数百个服务的数百个容器,那么处理容器的创建、销毁和管理对于手动执行来说将是难以承受的。因此,编排工具应运而生。
编排的思想是要有一个工具来处理容器的细节:当它们被创建和销毁时,在哪里被创建,应该在每个容器附近创建哪些容器集,需要什么外部资源和应该在哪里创建资源,以及需要什么网络连接。
在编排工具中,这些细节在一个单独的文件中定义,通常是 JSON 或 YAML 格式的。该定义由工具处理,以加载映像、挂载任何所需的资源,并将它们彼此连接起来。定义文件通常由负责团队进行版本控制,以便应用可以在投入生产之前部署到不同的环境中,例如试运行或质量控制。
在复杂的环境中,容器被部署到主机上。当需要容器的新实例时,通常是当容器映像被修改时,编排工具会寻找最合适的主机进行部署。用于确定什么是“合适”的标准包含在编排定义文件中。它可以包括地理位置、主机功能(CPU、内存)或管理员定义的标记等标准。
在 Visual Studio 2019 中,支持两种不同的编排工具:Kubernetes 和 Docker Compose。在接下来的部分中,我们将了解可用支持的性质。
忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈
Kubernetes 是来自谷歌的工具。15 年来,谷歌一直在运行容器化的工作负载,以支持其许多不同的功能和项目。与此同时,它开发了许多专门用于帮助管理不同工作负载的工具。用于管理这些工作负载的内部工具被称为 Borg,它是 Kubernetes 的前身。Kubernetes 的许多想法都源于博格。但是,更重要的是,许多在 Borg 中根深蒂固、无法重构的痛点已经在 Kubernetes 中得到解决。
正如您所料,当您在 Visual Studio 中使用 Kubernetes 时,需要了解其中的一些术语:
-
集群是存储资源和计算节点(主机)的集合,构成了 Kubernetes 中的一个管理单元。每个集群至少有一个 Kubernetes 主服务器,负责跨不同节点调度和部署应用实例。
-
pod 是 Kubernetes 中最低级别的调度单元,pod 由一个或多个部署到一台主机上的容器组成。并且 pod 中的每个容器都能够与其他容器共享资源。每个 pod 还分配有一个 IP 地址,确保应用可以定义必要的端口号进行通信,而不必担心冲突。
-
Kubelet
–
在 Kubernetes 环境中的每个计算节点上运行的服务代理。代理负责管理和报告节点上运行的容器的状态。当 Kubernetes 主节点发出指令时,实际上是 Kubelet 代理在节点上执行操作。 -
部署
–
正如编排描述中提到的,有一个文件定义了组合在一起的容器。在 Kubernetes 中,该文件是 YAML 格式的,称为部署。当 Kubernetes 主服务器调度部署时,它会将 YAML 文件提交给 Kubelet,以便实际创建各种容器实例和资源。 -
作为 pod 定义的一部分,您还可以声明使用了一定数量的相同容器集。每个集合称为一个副本,目的是任何传入的请求都可以由任何单独的副本提供服务。副本的概念用于帮助确保维持正常运行时间。
-
StatefulSet
–
部署旨在成为无状态的应用。正是这种无状态性使得复制品的概念很容易实现。然而,有些情况下应用需要有状态,对于这些情况,Kubernetes 中有 StatefulSet 的概念。数据库服务器是可能需要 StatefulSet 的一个很好的例子。
对 Kubernetes 的支持始于 Visual Studio,它增加了对编排的支持。在解决方案资源管理器中,右键单击该项目,然后从上下文菜单中选择“添加➤容器业务流程支持”。此时会出现一个对话框(图 10-7 ),让您确定希望包含在项目中的编排工具的类型。
图 10-7
添加容器编排支持
要添加 Kubernetes 支持,请选择 Kubernetes/Helm 选项,然后单击确定。这个过程向您的项目添加一个或多个文件和几个引用。如果您之前没有添加对 Docker 的支持,那么将添加与 Docker 相关联的文件和引用。对于 Kubernetes,添加了描述编排的 YAML 文件,以及一个名为 charts 的文件夹,其中包含 Helm 模板文件。这个文件的名字是azds.yaml
(很快会有更多关于 Azure Dev Spaces –
的介绍),它定义了项目参与的编排。请记住,默认文件包含一个非常简单的编排,它只包含一个容器(在这个项目的 docker 文件中定义的容器)。对于更复杂的应用,YAML 编排文件可能会变得相当大。
您可能已经注意到,当您添加容器编排支持时,所选择的选项包括 Helm 和 Kubernetes。简单地说,Helm 是 Kubernetes 集群的包管理器。但是公平地说,它不仅仅是一个包管理器。部分原因是在 Kubernetes 中管理包是一项复杂的工作。
考虑下面的场景。您有一个包含许多不同微服务项目的应用。该应用有一个 YAML 配置,可以很好地将应用部署并运行在一个登台环境中。但是生产环境就是有点不一样。用于访问数据库的 URL 在生产中与在暂存中是不同的(当然)。因此,您编写一个脚本,作为持续部署过程的一部分,在管道中的准备和生产步骤之间修改 YAML 文件中的 URL。
这个解决方案运行良好。对于一个应用。或者甚至两三个。但是您部署的应用越多,这种自己动手的方法就越低效。对暂存和生产 YAML 文件进行版本控制变得很困难,因为还有另一个脚本在执行之前修改生产。还有一个长期的问题,那就是试图理解试运行环境和生产环境之间的差异。仅仅比较 YAML 的档案是不够的。您还需要查看转换脚本以获得完整的理解。
这就是头盔发挥作用的地方。除了为您的部署管理包之外,它还提供了关于您的环境中当前部署的服务和配置的单一真实来源。它还提供了一种模板机制来解决环境之间的差异。
从架构的角度来看,Helm 只是一个命令行工具。现在,作为一个命令行工具,很容易为它开发脚本。脚本的文本格式意味着根据需要修改脚本很简单。但是,除了命令行之外,还有一个名为tiller
的服务器组件。分蘖在每一个 Kubernetes 集群上运行。它接受来自 Helm 的命令,并在集群上执行这些命令。这允许一个集中的命令窗口来影响远程 Kubernetes 集群上的软件部署。
一个舵包叫做海图。当您将 Kubernetes/Helm 支持添加到项目中时,会创建一个名为 charts 的目录。在该文件夹中,创建了一个以项目名称命名的子目录。在该文件夹中,创建了许多文件,其结构为 Helm 所认可。这包括以下内容:
-
templates
–
一个包含 YAML 文件集合的文件夹,这些文件在创建 Kubernetes 清单时充当模板。作为 Helm 构建过程的一部分,模板与 values.yaml 文件中的值相结合。 -
。helmignore
–
一个包含模式的文件,用于识别 Helm 应该忽略的文件。 -
Chart.yaml
–
这个文件包含关于特定图表的元数据。 -
values.yaml
–
包含模板使用的各种值,以便生成特定于该应用的清单。
至此,您已经具备了利用 Helm 和 Kubernetes 来打包和部署应用所需的所有条件。正如你所想象的,这些工具比我们在本章中讨论的要复杂得多。如何使用这些工具的细节已经超出了本书的范围。并且,至少目前,生成必要的文件是 Visual Studio 提供的支持级别。
Azure 开发空间
Azure 中提供的功能之一是 Azure Kubernetes 服务(AKS)。这是一个托管的 Kubernetes 服务,允许您在 Azure 中开发和运行复杂的 Kubernetes 编排。该服务提供一个 Kubernetes 主服务器,为您处理健康监控和维护任务。由您来创建应用并将它们部署到节点上。
Azure Dev Spaces 是对 AKS 的扩展,允许 Kubernetes 集群的快速迭代开发和部署。目标是帮助开发人员创建在 Kubernetes 中使用的应用,并将它们部署到集群中,以便可以与其他已经部署的服务一起测试它们。
azds.yaml
文件的目的是定义如何将应用部署到 AKS 中。该文件的格式类似于 Python,因为内容的嵌套是由行的缩进决定的。默认文件对于单个项目部署来说通常是足够的,只要项目的功能不是过于复杂。换句话说,项目需要的外部资源越多,默认资源就越有可能不够好。
默认文件包括构建和安装部分。构建部分控制用于构建项目的过程。install 部分描述了发布的详细信息,包括包含设置和机密(如连接字符串)的文件、要使用的映像以及任何公开的端点等详细信息。
为了利用 Helm 和 Azure Dev Spaces,需要一些命令行脚本。您需要在 Azure 订阅中创建一个 AKS 实例,然后使用 Helm 将您的应用安装到 AKS 集群上。如果需要对应用进行修改,可以使用adzs
命令将更新后的包上传到 Kubernetes 集群。所涉及步骤的详细描述可以在 https://docs.microsoft.com/en-us/azure/dev-spaces/quickstart-team-development
找到。
复合坞站
当您添加容器编排支持时(图 10-7 ,您可以选择 Docker Compose 而不是 Kubernetes 作为您的编排技术。像 Kubernetes 一样,Docker Compose 用于定义和执行多容器应用。有一个 YAML 文件,其中包含对所使用的不同容器的描述。有一个命令(docker-compose
)将启动您的 YAML 文件中描述的所有容器。
那么 Docker Compose 和 Kubernetes 有什么区别呢?复杂性和特性。Kubernetes 是一个企业级容器编排工具。它处理跨多个主机运行的多个容器。它还提供了管理功能,包括自动扩展和重启。
Docker Compose 要简单得多。它的真正目的是允许您在一台主机上启动多个容器,而不需要单独启动每个容器。您可以启动和停止服务,查看正在运行的容器的状态,重建和更新容器,以及显示来自不同服务的日志。
在 Visual Studio 中,通过在添加 orchestrion 支持时选择选项,可以添加对 Docker Compose 支持。选中时,Visual Studio 会添加一个docker-compose.yml
文件。如果您的项目还没有添加 Docker 支持,这些文件和引用也会被添加。以下是 Docker 撰写文件的内容:
version: '3.4'
services:
aspnetcoresample:
image: ${DOCKER_REGISTRY-}aspnetcoresample
build:
context: .
dockerfile: Dockerfile
只有一个服务,因为这个文件与一个项目相关。对 Docker 容器使用的基本映像进行了描述。还有一节描述了如何构建将被部署到容器中的应用。
除了docker-compose.yml
文件,还创建了一个docker-compose.override.yml
文件。该文件的目的是允许添加额外的配置细节。不同之处在于覆盖文件的内容会覆盖基本配置文件中的任何设置。并且你有不同的覆盖文件,典型的名字表明了它的用途(例如,docker-compose.test.yml
)。
默认情况下,docker-compose
命令读取基本配置,然后使用覆盖文件中的内容来取代任何配置设置。如果您想使用一个特定的覆盖文件,您可以在命令行中为docker-compose
包含该文件的名称。
添加 Docker Compose orchestration 支持的第一个应用将成为在 Visual Studio 中运行或调试应用时启动的应用。这相当于为解决方案指定启动项目。你有能力改变这种行为。如果右键单击添加支持时添加的 docker-compose 文件夹,并选择属性选项,将出现图 10-8 中的对话框。
图 10-8
Docker 合成属性页
这些是用于从 Visual Studio 中运行 Docker 撰写配置的设置。每个设置的含义如下:
-
启动操作
–
当应用成功编译和部署后,Visual Studio 应该做什么?默认情况下,它将启动一个浏览器,传入服务 URL。但是,您也可以不采取任何措施。在这种情况下,容器将会运行,但是没有要处理的默认请求。 -
服务名
–
启动时初始请求被发送到的服务的名称。该名称用作下一个属性的一部分。 -
服务 URL
–
用于访问该服务的 URL。默认值是一个模板脚本,其中包含取自配置文件的值。默认情况下,它将使用端口 80 和 HTTP 模式访问服务名。 -
目标 OS
–
容器将运行的操作系统。
摘要
在接下来的 5 年里,容器和编排将在开发者社区中扮演重要角色。这一直是非常关注扩展问题的公司关注的焦点。利用容器的微服务架构在很多情况下都很有用。
但是,即使您目前没有使用容器,考虑它们的功能也是有好处的。即使在最简单的情况下,只需几个脚本命令就可以创建自包含的应用并将它们部署到各种各样的环境中,这一事实非常强大。更不用说,如果由于意外的硬件或软件问题,您需要将应用转移到不同的服务器上,能够快速启动容器对于您的 IT 员工来说是一种新鲜空气。
重要的是,Visual Studio 提供了一组支持服务,可以帮助解决所有的用例,并最终提供使 Visual Studio 对如此大比例的开发人员社区如此重要的功能。