原文:zh.annas-archive.org/md5/2134ab55bdcded8205d2f4b0f494825e

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:提高 C# 代码库的代码维护性

在本章中,我们将介绍以下内容:

  • 配置 Visual Studio 2017 内置的 C# 代码风格规则

  • 使用 .editorconfig 文件配置代码风格规则

  • 使用公共 API 分析器进行 API 表面维护

  • 使用第三方 StyleCop 分析器进行代码风格规则

简介

在当前开源项目时代,众多来自不同组织和世界各地的不同贡献者,维护任何仓库的一个主要要求是在代码库中强制执行代码风格指南。历史上,这通常通过详尽的文档和代码审查来实现,以捕捉任何违反这些编码指南的行为。然而,这种方法有其缺陷,需要大量的人力和时间来维护文档和执行详尽的代码审查。

利用 Visual Studio 2017 内置的自动代码风格和命名规则,用户可以自定义和配置单个规则的执行级别,并对违规行为提供视觉提示,例如编辑器中的建议或波浪线,以及错误列表中的诊断信息,并设置适当的严重性(错误/警告/信息性消息)。此外,规则还包含一个代码修复功能,可以自动修复文档、项目或解决方案中一个或多个违规实例。在 Visual Studio 2017 的新 EditorConfig 支持下,这些规则配置可以在每个文件夹级别通过 .editorconfig 文件进行强制执行和自定义。此外,.editorconfig 文件可以与源代码一起提交到仓库,以确保所有为仓库做出贡献的用户都遵守这些规则。

EditorConfig (editorconfig.org/) 是一种开源文件格式,帮助开发者配置和强制执行格式化和代码风格约定,以实现代码库的一致性和可读性。EditorConfig 文件易于提交到源代码控制,并在仓库和项目级别应用。EditorConfig 约定覆盖个人设置中的等效约定,使得代码库的约定优先于单个开发者的约定。

在本章中,我们将向您介绍这些代码风格规则,展示如何在 Visual Studio 2017 IDE 中配置它们,并将这些设置保存到 EditorConfig 文件中。此外,我们还将向您介绍一个非常流行的第三方 Roslyn 分析器,即 PublicAPI 分析器,它允许通过检入到仓库中的附加非源文本文件来跟踪 .NET 程序集的公共 API 表面,并在出现破坏性 API 变更或未在公共 API 文件中记录的新公共 API 添加时提供诊断和代码修复。我们还将指导您如何为 .NET 项目配置 StyleCop 分析器,这是一个流行的第三方代码风格分析器,也是内置 Visual Studio 代码风格规则的替代方案,用于强制执行代码风格。

配置 Visual Studio 2017 中内置的 C# 代码风格规则

在本节中,我们将向您介绍 Visual Studio 2017 中内置的重要代码风格规则类别,并展示如何在 Visual Studio 中配置它们。

准备中

您需要在您的机器上安装 Visual Studio 2017 才能执行本章中的配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装 Visual Studio 2017 的免费社区版本。

如何操作…

  1. 启动 Visual Studio,导航到文件 | 新建 | 项目…,创建一个新的 C# 类库项目,并用 ClassLibrary/Class1.cs 中的代码替换 Class1.cs 中的代码。

  2. 点击工具 | 选项以打开工具选项对话框,并导航到 C# 代码风格选项(文本编辑器 | C# | 代码风格 | 一般):

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/b102d1de-6050-423d-b786-8ebc55eef267.png

  1. 将 ‘this.’ 预设的严重性更改为建议,预设的类型偏好更改为警告,以及将 ‘var’ 偏好更改为错误。将 ‘var’ 偏好的偏好从“首选显式类型”更改为“首选 ‘var’”:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/f2a6f176-6866-4490-bb87-50b245e1f7d7.png

  1. 将代码块偏好的严重性更改为警告,并将方法偏好更改为首选表达式体:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/8abec331-d7e1-4301-bf53-90099ccd3425.png

  1. 确保所有剩余的代码风格规则(表达式偏好、变量偏好和 ‘null’ 检查)的严重性均为建议。

  2. 在错误列表中验证以下代码风格诊断:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/cac29f6b-689b-4c7b-a00b-6c56c36f69e4.png

  1. 双击第一个 IDE0007 错误并验证是否提供了使用 ‘var’ 而不是显式类型的代码修复的灯泡提示。验证按 Enter 键可以修复代码,并将诊断从错误列表中删除。对剩余的 IDE0007 诊断重复此练习:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9e0cd8f4-28af-4b99-9305-1193c44b409d.png

  1. 现在,双击第一个 IDE0012 警告,并验证是否提供了一个用于简化名称 ‘System.Int32’ 的灯泡提示。这次,单击“文档中所有出现的修复”超链接,并验证是否出现了一个预览更改对话框,该对话框使用单个批量修复来修复文档中所有 IDE0012 的实例:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/ab570d39-fbd0-4da5-955b-477bf176a71e.png

  1. 对错误列表中剩余的每个诊断应用代码修复,并验证代码现在是否完全干净:
using System;

class Class1
{
  // Field and Property - prefer predefined type
  private int field1;
  private int Property1 => int.MaxValue;

  private void Method_DoNotPreferThis()
  {
    Console.WriteLine(field1);
    Console.WriteLine(Property1);
    Method_PreferExpressionBody();
  }

  private int Method_PreferVar()
  {
    var i = 0;
    var c = new Class1();
    var c2 = Method_PreferExpressionBody();
    return i;
  }

  private void Method_PreferBraces(bool flag)
  {
    if (flag)
    {
      Console.WriteLine(flag);
    }
  }

  private Class1 Method_PreferExpressionBody() => Method_PreferPatternMatching(null);

  private Class1 Method_PreferPatternMatching(object o)
  {
    if (o is Class1 c)
    {
      return c;
    }

    return new Class1();
  }

  private int Method_PreferInlineVariableDeclaration(string s)
  {
    if (int.TryParse(s, out var i))
    {
      return i;
    }

    return -1;
  }

  private void Method_NullCheckingPreferences(object o)
  {
    var str = o?.ToString();
    var o3 = o ?? new object();
  }
}

  1. 将以下新方法添加到 Class1 中,并验证是否为新增代码中的代码风格违规抛出了 IDE0007(使用 ‘var’ 而不是显式类型):
private void Method_New_ViolateVarPreference()
{
  Class1 c = new Class1();
}

它是如何工作的…

代码风格规则内置在 Visual Studio 2017 中,并分为以下广泛类别:

  • ‘this.’ 偏好

  • 预定义类型偏好

  • ‘var’ 偏好

  • 代码块偏好

  • 表达式偏好

  • 变量偏好

  • ‘null’ 检查

每个类别都有一组一个或多个规则,每个规则有两个字段:

  • 偏好:一个字符串,用于标识规则的偏好。通常,它有两个可能的值,一个表示规则应该被优先考虑,另一个表示规则不应该被优先考虑。例如,对于 ‘this.’ 偏好规则,可能的值包括:

    • 不偏好 ‘this.’:这强制标记带有 'this.' 前缀的成员访问的代码。

    • 偏好 ‘this.’:这确保了没有 ‘this.’ 前缀的成员访问的代码会被标记。

  • 严重性:一个枚举,用于标识规则的严重性。它有以下可能的值和视觉效果:

    • 错误:规则的违规会在错误列表中产生错误诊断,并在代码编辑器中产生红色波浪线。

    • 警告:规则的违规会在错误列表中产生警告诊断,并在代码编辑器中产生绿色波浪线。

    • 建议:规则的违规会在错误列表中产生信息性消息诊断,并在代码编辑器中违反语法的第一个几个字符下方产生灰色点。

    • 无:规则在编辑器中不受强制,错误列表中没有诊断,编辑器中也没有视觉指示器。

用户可以使用“工具 | 选项”对话框根据其要求配置每个规则的偏好和严重性。关闭并重新打开源文档会导致配置更改生效,违规将在错误列表和视觉指示器(波浪线/点)中报告。每个规则都附带代码修复和 FixAll 支持,以修复文档、项目或解决方案中一个或多个违规的实例。

对于内置代码风格规则报告的诊断仅在 Visual Studio 2017 的实时代码编辑期间生成——它们不会中断构建,也不会在命令行构建期间生成。这种行为在未来版本的 Visual Studio 中可能会改变,也可能不会改变。

还有更多…

代码样式规则首选项与用户配置文件设置一起保存,并在同一 Visual Studio 安装的 Visual Studio 会话之间持久化。这意味着在 Visual Studio 中打开的任何项目都将具有相同的代码样式强制执行。然而,在具有不同用户配置文件的不同 Visual Studio 安装或不同机器上打开的相同源将不会具有相同的代码样式强制执行。为了在所有用户之间启用相同的代码样式强制执行,用户需要将代码样式设置持久化到.editorconfig文件中,并将其与源文件一起提交到仓库。有关详细信息,请参阅本章中的使用.editorconfig 文件进行按文件夹配置代码样式规则配方。

在文本编辑器 | C# | 代码样式 | 命名规则下,考虑探索工具 | 选项…对话框中的命名规则。这些规则允许用户强制执行关于每种不同符号应该如何命名的指南。例如,接口名称应该以大写字母"I"开头,类型名称应该是 Pascal 大小写,等等。

使用.editorconfig文件配置代码样式规则

在本节中,我们将向您展示如何使用 EditorConfig 文件配置 Visual Studio 2017 内置的代码样式规则,以及如何在不同的文件夹级别覆盖这些设置。这些 EditorConfig 文件可以与源文件一起提交到仓库中,这确保了代码样式设置对所有仓库贡献者都是持久和强制执行的。

准备工作

您需要在您的机器上安装 Visual Studio 2017 才能执行本章中的配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15安装免费的 Visual Studio 2017 社区版。

vsixgallery.com/extension/1209461d-57f8-46a4-814a-dbe5fecef941/的 Visual Studio 扩展库中安装EditorConfig Language Service VSIX,以在 Visual Studio 中获得.editorconfig文件的智能感知和自动完成功能。

如何操作…

  1. 启动 Visual Studio,点击文件 | 新建 | 项目…,创建一个新的 C#类库项目,并用ClassLibrary/Class1.cs中的代码替换Class1.cs中的代码。

  2. 在项目中添加一个名为.editorconfig的新文本文件,内容如下:

# top-most EditorConfig file
root = true

# rules for all .cs files.
[*.cs]

# 'this.' preferences
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion

# predefined type preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning

# 'var' preferences
csharp_style_var_for_built_in_types = true:error
csharp_style_var_when_type_is_apparent = true:error
csharp_style_var_elsewhere = true:error

# code block preferences
csharp_new_line_before_open_brace = all
csharp_style_expression_bodied_methods = true:warning
csharp_style_expression_bodied_constructors = false:warning
csharp_style_expression_bodied_operators = false:warning
csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning

# expression preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion

# variable preferences
csharp_style_inlined_variable_declaration = true:suggestion

# 'null' checking
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion

  1. 在错误列表中验证以下代码样式诊断:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/d3b69fa0-88c8-42ed-a5fd-ba1ab89b5cb1.png

  1. 双击第一个 IDE0007 错误,并验证是否提供了一个代码修复选项,使用var而不是显式类型。验证按Enter键可以修复代码,并将诊断从错误列表中删除。对剩余的 IDE0007 诊断重复此练习。

  2. 然后,双击第一个 IDE0012 警告,并验证是否提供了一个灯泡图标来简化名称’System.Int32’。这次,点击“在文档中修复所有实例”的超链接,并验证是否出现了一个预览更改对话框,该对话框通过单个批量修复解决了文档中所有 IDE0012 的实例。

  3. 对错误列表中剩余的每个诊断项应用代码修复,并验证代码现在是否完全干净:

using System;

class Class1
{
 // Field and Property - prefer predefined type
 private int field1;
 private int Property1 => int.MaxValue;

 private void Method_DoNotPreferThis()
 {
  Console.WriteLine(field1);
  Console.WriteLine(Property1);
  Method_PreferExpressionBody();
 }

 private int Method_PreferVar()
 {
  var i = 0;
  var c = new Class1();
  var c2 = Method_PreferExpressionBody();
  return i;
 }

 private void Method_PreferBraces(bool flag)
 {
  if (flag)
  {
   Console.WriteLine(flag);
  }
 }

 private Class1 Method_PreferExpressionBody() => Method_PreferPatternMatching(null);

 private Class1 Method_PreferPatternMatching(object o)
 {
  if (o is Class1 c)
  {
   return c;
  }

  return new Class1();
 }

 private int Method_PreferInlineVariableDeclaration(string s)
 {
  if (int.TryParse(s, out var i))
  {
   return i;
  }

  return -1;
 }

 private void Method_NullCheckingPreferences(object o)
 {
  var str = o?.ToString();
  var o3 = o ?? new object();
 }
}

  1. 在项目的根目录中添加一个新文件夹,例如NewFolder,并在该文件夹中添加一个新类,例如Class2.cs,然后验证 IDE0007(使用var代替显式类型)是否因新添加的代码中的代码风格违规而引发。
private void Method_New_ViolateVarPreference()
{
 Class1 c = new Class1();
}

  1. NewFolder中添加一个名为.editorconfig的新文本文件,其内容如下:
# rules for all .cs files in this folder.
[*.cs]

# override 'var' preferences
csharp_style_var_for_built_in_types = false:error
csharp_style_var_when_type_is_apparent = false:error
csharp_style_var_elsewhere = false:error

  1. 关闭并重新打开Class2.cs,并验证 IDE0007 不再被报告。

它是如何工作的…

参考本章中食谱的*如何工作…*部分,配置 Visual Studio 2017 中内置的 C#代码风格规则,以了解 Visual Studio 2017 中的不同内置代码风格规则,以及与这些规则关联的偏好和严重性设置,以及它们如何映射到编辑器配置条目。例如,考虑以下条目:

dotnet_style_qualification_for_field = false:suggestion

dotnet_style_qualification_for_field是规则名称,偏好设置为false,严重性设置为suggestion。这些规则及其设置在每个文件夹级别强制执行,任何文件夹级别的 EditorConfig 文件都会覆盖祖先目录中 EditorConfig 文件的设置,直到达到根文件路径或找到具有root=true的 EditorConfig 文件。我们建议您参考以下文章,以获得对 EditorConfig 和 Visual Studio 2017 中相关支持的详细理解:

使用公共 API 分析器进行 API 表面维护

DeclarePublicAPIAnalyzer分析器是在(github.com/dotnet/roslyn-analyzers)仓库中开发的流行第三方分析器,并作为 NuGet 包发布在www.nuget.org/packages/Roslyn.Diagnostics.Analyzers。此分析器通过提供与项目源文件一起存在的可读和可审查的文本文件来帮助跟踪项目的公共表面区域,并提供作为源的 API 文档。例如,考虑以下具有公共和非公共符号的源文件:

public class Class1
{
  public int Field1;

  public object Property1 => null;

  public void Method1() { }

  public void Method1(int x) { }

  private void Method2() { }
}

此类型的附加 API 表面文本文件将如下所示:

Class1
Class1.Class1() -> void
Class1.Field1 -> int
Class1.Method1() -> void
Class1.Method1(int x) -> void
Class1.Property1.get -> object

每个公共符号都有一个条目:类型Class1,其构造函数及其成员Field1, Method1重载,以及Property1获取器。条目包含整个符号签名,包括返回类型和参数。

使用此 NuGet 包,用户可以在任何时间点跟踪已发布和未发布的公共 API 表面,当公共 API 表面发生变化时,获取实时和构建中断诊断,并应用代码修复来更新这些附加文件以匹配本地的 API 更改。当实际的代码更改很大且分散在代码库中时,这允许进行更丰富和更集中的 API 审查,但只需查看单个文件中的核心签名更改即可审查 API 更改。

DeclarePublicAPIAnalyzer主要编写用于跟踪位于github.com/dotnet/roslyn的 Roslyn 源库的公共 API 表面,并且在所有 Roslyn 贡献者中仍然非常受欢迎。分析器最终被转换为一个通用开源分析器,可以从NuGet.org安装到任何.NET 项目中。

在本节中,我们将向您展示如何为 C#项目安装和配置公共 API 分析器,带您了解跟踪公共 API 表面的附加文本文件,向您展示分析器报告的 API 更改诊断,并最终向您展示如何应用代码修复来修复一个或多个这些诊断的实例以更新 API 表面文本文件。

准备工作

您需要在您的机器上安装 Visual Studio 2017 以执行本章中的配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15安装 Visual Studio 2017 的免费社区版本。

如何操作…

  1. 启动 Visual Studio,点击文件 | 新建 | 项目…,创建一个新的 C#类库项目,并将Class1.cs中的代码替换为ClassLibrary/Class1.cs(也在本食谱的简介部分中提到)中的代码样本。

  2. 安装Roslyn.Diagnostics.Analyzers NuGet 包版本1.2.0-beta2。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅配方通过 NuGet 包管理器搜索和安装分析器,在第二章,在.NET 项目中使用诊断分析器

  3. RS0016RS0017的严重性从警告升级到错误。有关分析器严重性配置的指导,请参阅配方,在 Visual Studio 解决方案资源管理器中查看和配置分析器,在第二章,在.NET 项目中使用诊断分析器

  4. 将两个新的文本文件PublicAPI.Shipped.txtPublicAPI.Unshipped.txt添加到项目中。

  5. 在解决方案资源管理器中选择两个文本文件,使用属性窗口将它们的构建操作从内容更改为 AdditionalFiles,并保存项目:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/fa5b4e08-f2ed-4279-9d0b-d125fdd2209f.png

  1. 验证编辑器中的波浪线和错误列表中的六个RS0016错误(*x*不是已声明的 API 的一部分),每个公共符号一个:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/997e7f84-5a52-4a60-89cf-7ae5c942be8e.png

  1. 将光标移至字段符号Field1,并按Ctrl + 点号(.)以获取自动修复诊断并将符号添加到未发布的公共 API 文本文件的代码修复:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9e203eb7-23f2-467a-bbd2-e3ba6549d37f.png

  1. 通过按Enter键应用代码修复,并验证条目Class1.Field1 -> int已添加到PublicAPI.Unshipped.txt,并且Field1的诊断和波浪线不再存在。

  2. 将光标移至Class1的类型声明处,再次按Ctrl + 点号以获取代码修复,但这次应用 FixAll 代码修复以批量修复整个文档中的所有 RS0016 实例,并将所有公共符号添加到未发布的公共 API 文本文件中。有关应用 FixAll 代码修复的指导,请参阅配方,在不同范围内应用批量代码修复(FixAll):文档、项目和解决方案,在第三章,编写 IDE 代码修复、重构和 Intellisense 完成提供者

  3. 剪切PublicAPI.Unshipped.txt中的全部内容,并将其粘贴到PublicAPI.Shipped.txt中。

  4. Class1.cs中,尝试通过将已发布的公共符号Field1重命名为Field2来引入破坏性 API 更改*。

  5. 验证Field2立即报告RS0016,并提供一个代码修复以添加Field2的公共 API 条目。应用代码修复将Field2添加到公共 API 表面并修复诊断。

  6. 构建项目,并验证项目在输出窗口中由于破坏性更改出现以下RS0017诊断错误而无法构建:ClassLibraryPublicAPI.Shipped.txt(3,1,3,21): 错误 RS0017: 符号'Class1.Field1 -> int'是已声明的 API 的一部分,但既不是公共的,也无法找到

  7. 在第 11 步和第 12 步中撤销更改。

  8. Method2 改为公共方法,验证它报告了 RS0016,并使用代码修复将其 API 条目添加到 PublicAPI.Unshipped.txt

  9. 现在,将 unshipped 公共符号从 c 重命名为 Method3. 验证 Method3 报告了 RS0016,并且代码修复将 Method2 的公共 API 条目替换为 Method3 的条目:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/629732ef-e6d7-46d1-a818-2416d0aee6f7.png

  1. 应用代码修复并验证构建是否成功,错误列表中没有诊断信息。

它是如何工作的…

DeclarePublicAPIAnalyzer 是一个额外的文件分析器,它通过比较编译中声明的公共符号与已发布和未发布的 API 表面文本文件中的公共 API 条目来工作。它为每个符号使用基于其完全限定名称和签名的唯一字符串表示形式,作为其公共 API 条目。它报告任何缺失或多余的公共 API 条目的诊断信息。您可以在 github.com/dotnet/roslyn-analyzers/blob/master/src/Roslyn.Diagnostics.Analyzers/Core/DeclarePublicAPIAnalyzer.cs 找到该分析器的实现,以及提供的相应代码修复 github.com/dotnet/roslyn-analyzers/blob/master/src/Roslyn.Diagnostics.Analyzers/Core/DeclarePublicAPIFix.cs

还有更多…

您可以在 github.com/dotnet/roslyn/blob/master/docs/analyzers/Using%20Additional%20Files.md 阅读更多关于如何编写和消费额外的文件分析器,如 DeclarePublicAPIAnalyzer 的信息。

使用第三方 StyleCop 分析器进行代码风格规则

在本节中,我们将向您介绍一个流行的第三方分析器包,用于 C# 项目的代码风格规则,即 StyleCop 分析器。我们将介绍如何安装 StyleCop 分析器 NuGet 包,给出 StyleCop 规则类别的示例违规,并展示如何配置和调整单个 StyleCop 规则。

准备工作

您需要在您的机器上安装 Visual Studio 2017 才能执行本章中的配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装免费的 Visual Studio 2017 社区版本。

如何操作…

  1. 启动 Visual Studio,导航到文件 | 新建 | 项目…,创建一个新的 C# 类库项目,并将 Class1.cs 中的代码替换为 ClassLibrary/Class1.cs 中的代码样本。

  2. 安装 StyleCop.Analyzers NuGet 包(截至本文撰写时,最新预发布版本为 1.1.0-beta001)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅配方,通过 NuGet 包管理器搜索和安装分析器,位于 第二章,在 .NET 项目中消费诊断分析器

  3. 验证以下 StyleCop 诊断是否显示在错误列表中:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9230d185-ddbd-49aa-8c5a-0408dd39dc67.png

  1. 从命令行或在 Visual Studio 的顶级构建菜单中构建项目,并验证这些诊断是否也来自构建。

  2. 双击警告 SA1025 代码不得在一行中包含多个空格字符,验证编辑器中是否提供了灯泡以修复间距违规,并通过按 Enter 键应用代码修复来修复它:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/8e7acd55-b902-4313-9df6-97438d3adb08.png

  1. 然后,双击警告 SA1200 使用指令必须在命名空间声明内出现,并验证它是否在 using System; 使用语句上报告,因为它位于命名空间 Namespace 之外。

  2. 向项目中添加一个名为 stylecop.json 的新文件。

  3. 在解决方案资源管理器中选择 stylecop.json,使用属性窗口将其构建操作从内容更改为 AdditionalFiles,并保存项目:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/fd18c60e-a282-4d68-b637-3e3d859ef4da.png

  1. 将以下文本添加到 stylecop.json 并验证 SA1200 已不再被报告:
{
  "settings": {
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace"
    }
  }
}

  1. using System; 移入命名空间 Namespace 内,并验证 SA1200 (使用指令必须出现在命名空间声明之外) 现在报告了位于命名空间内的使用语句。

它是如何工作的…

StypeCop 分析器包含以下类别的样式规则:

我们提供的代码示例按类别具有以下 StyleCop 诊断:

  • 间距

    • SA1025(代码不得在一行中包含多个空白字符)
  • 可读性

    • SA1101(使用 this 前缀调用局部变量)
  • 排序

    • SA1200(使用指令必须位于命名空间声明内)
  • 命名

    • SA1300(元素"method"必须以大写字母开头)

    • SA1303(常量字段名必须以大写字母开头)

  • 可维护性

    • SA1401(字段必须是私有的)

    • SA1402(文件只能包含一个类型)

  • 布局

    • SA1502(元素不得位于单行)

    • SA1503(大括号不得省略)

    • SA1505(开括号后不得跟空白行)

  • 文档

    • SA1600(元素必须进行文档化)

    • SA1633(文件头缺失或未位于文件顶部)

StyleCop 分析器包还包含针对某些规则的代码修复,以修复违规行为。

StyleCop 分析器可以作为 NuGet 包安装,用于特定的 C#项目/解决方案,或者作为为所有 C#项目启用的 VSIX 扩展。NuGet 包启用构建时的 StyleCop 诊断,因此是推荐安装分析器的方式。

代码风格通常被认为是一个非常主观的问题,因此允许最终用户选择性地启用、抑制或配置个别规则非常重要。

  • StyleCop 规则可以通过代码分析规则集文件启用或抑制(参见第二章的配方,使用规则集文件和规则集编辑器配置分析器,在第二章,在.NET 项目中使用诊断分析器中参考)。

  • Stylecop 规则可以通过添加到项目中作为额外非源文件的 stylecop.json 文件进行配置和调整。例如,考虑排序规则 SA1200(使用指令必须在命名空间声明内出现)在 (www.nuget.org/packages/StyleCop.Analyzers/)。默认情况下,此规则如果使用指令放置在文件顶部 外部 命名空间声明,则会报告违规。然而,此规则可以被配置为其语义相反,并要求使用指令在命名空间声明外部,如果它们在 内部 则报告违规,使用以下 stylecop.json

{
  "settings": {
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace"
    }
  }
}

StyleCop 分析器仓库为每个规则类别提供了详细的文档,以及单个样式规则。您可以在 github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/documentation 找到这些文档。

第五章:在 C#代码中识别安全漏洞和性能问题

在本章中,我们将涵盖以下食谱:

  • 识别 Web 应用程序中的配置相关安全漏洞

  • 识别 Web 应用程序中视图标记文件(.cshtml.aspx文件)中的跨站脚本漏洞

  • 识别可能导致 SQL 和 LDAP 注入攻击的不安全方法调用

  • 识别 Web 应用程序中的弱密码保护和管理工作

  • 识别外部组件数据弱验证以防止跨站请求伪造和路径篡改等攻击

  • 使用 FxCop 分析器识别源代码的性能改进

简介

在本章中,我们将涵盖两个非常重要且流行的 Roslyn 分析器类别:安全和性能分析器。

  • 安全: 考虑到.NET 应用程序的领域极其庞大,每个应用程序都有非常特定的安全漏洞,因此我们拥有特定领域的工具/扩展来捕获这些漏洞至关重要。基于 Roslyn 的安全分析器,如PUMA扫描分析器,在编译时捕获这些漏洞并报告诊断。PUMA 扫描分析器规则分为以下广泛类别:

  • 性能:对于所有应用程序来说,运行时性能都很重要,并且有许多不同的方面。.NET 应用程序的一个重要性能标准是 .NET 编译器生成的 MSIL 或 CIL (en.wikipedia.org/wiki/Common_Intermediate_Language) 的质量。MSIL 的质量受用户代码的质量和生成 MSIL 的编译器的质量共同影响。在本章中,我们将向您介绍 FxCop 分析器中的性能规则,这些规则是微软为识别 .NET 应用程序中的性能改进而编写的代码分析规则 (CAXXXX),以生成更有效的 MSIL。这些规则已被移植到 Roslyn 分析器框架,并在 github.com/dotnet/roslyn-analyzers 上开源。

识别 Web 应用程序中的配置相关安全漏洞

ASP.NET 允许您指定影响服务器上所有 Web 应用程序、仅影响单个应用程序、影响单个页面或影响 Web 应用程序中的单个文件夹的配置设置。您可以针对功能(如编译器选项、调试、用户身份验证、错误信息显示、连接字符串等)进行配置设置。配置数据存储在名为 Web.config 的 XML 文件中。

您可以在 msdn.microsoft.com/en-us/library/ff400235.aspx 上阅读有关 Web.config 文件中不同类型配置设置的更多详细信息。

在本节中,我们将向您介绍 PUMA 扫描分析器中的规则,以在 ASP.NET Web Forms 项目中捕获 Web 配置中的安全漏洞。

注意,Roslyn 分析器在 .NET 框架项目和 .NET Core 项目上都得到完全支持,因此本章中提到的 PUMA 扫描分析器在 ASP.NET 和 ASP.Net Core Web 项目上都能正常工作。

准备工作

您需要在您的机器上安装 Visual Studio 2017 以执行本章中的食谱。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装 Visual Studio 2017 的免费社区版本。

如何操作…

  1. 启动 Visual Studio,然后单击文件 | 新建 | 项目… 并使用 Web Forms 模板创建一个新的 Visual C# | Web | ASP.NET Web 应用程序,例如 WebApplication

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/ad226c58-fa64-4869-b81d-39419887b541.png

  1. 安装 Puma.Security.Rules 分析器 NuGet 包(在撰写本文时,最新稳定版本为 1.0.4)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅第二章,通过 NuGet 包管理器搜索和安装分析器的食谱,在 .NET 项目中消费诊断分析器

  2. 在解决方案资源管理器中选择 Web.config,并使用属性窗口将其构建操作从内容更改为 AdditionalFiles,然后保存项目:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9af8e7bd-1151-4fe8-9558-64f746c36a04.png

  1. 在编辑器中打开 Web.config,并用以下 XML 替换现有的system.web XML 元素。您可以在msdn.microsoft.com/en-us/library/dayb112d(v=vs.100).aspx上阅读更多关于system.web XML 元素的信息。
<system.web>
  <compilation debug="false" targetFramework="4.6.2" />
  <customErrors mode="Off" defaultRedirect="/home/error"/>
  <httpRuntime enableHeaderChecking="false" enableVersionHeader="true" />
  <httpCookies requireSSL="false" httpOnlyCookies="false"/>
  <pages enableEventValidation="false" enableViewStateMac="false" viewStateEncryptionMode="Never" validateRequest="false" />
  <authentication mode="Forms">
    <forms loginUrl="~/Account/Login.aspx" timeout="900" enableCrossAppRedirects="true" protection="None" />
  </authentication>
</system.web>

  1. 在 Visual Studio 或命令行中构建项目,并验证您是否从 PUMA 扫描分析器获得了以下SECXXXX警告:
1>CSC : warning SEC0014: Insecure HTTP cookies C:WebApplicationWeb.config(11): <httpCookies requireSSL="false" httpOnlyCookies="false" />
1>CSC : warning SEC0015: Cookies accessible via script. C:WebApplicationWeb.config(11): <httpCookies requireSSL="false" httpOnlyCookies="false" />
1>CSC : warning SEC0003: Forms authentication does not set requireSSL to true. C:WebApplicationWeb.config(14): <forms loginUrl="~/Account/Login.aspx" timeout="900" enableCrossAppRedirects="true" protection="None" />
1>CSC : warning SEC0004: Forms authentication does not set the cookieless attribute to UseCookies. C:WebApplicationWeb.config(14): <forms loginUrl="~/Account/Login.aspx" timeout="900" enableCrossAppRedirects="true" protection="None" />
1>CSC : warning SEC0006: Forms authentication cookie protection attribute is not set to All. C:WebApplicationWeb.config(14): <forms loginUrl="~/Account/Login.aspx" timeout="900" enableCrossAppRedirects="true" protection="None" />
1>CSC : warning SEC0007: Forms authentication timeout value exceeds the policy of 30 minutes. C:WebApplicationWeb.config(14): <forms loginUrl="~/Account/Login.aspx" timeout="900" enableCrossAppRedirects="true" protection="None" />
1>CSC : warning SEC0005: Forms authentication does not set the enableCrossAppRedirects attribute to false. C:WebApplicationWeb.config(14): <forms loginUrl="~/Account/Login.aspx" timeout="900" enableCrossAppRedirects="true" protection="None" />
1>CSC : warning SEC0002: Custom errors are disabled. C:WebApplicationWeb.config(9): <customErrors mode="Off" defaultRedirect="/home/error" />
1>CSC : warning SEC0008: HTTP header checking is disabled. C:WebApplicationWeb.config(10): <httpRuntime enableHeaderChecking="false" enableVersionHeader="true" />
1>CSC : warning SEC0009: The Version HTTP response header is enabled. C:WebApplicationWeb.config(10): <httpRuntime enableHeaderChecking="false" enableVersionHeader="true" />
1>CSC : warning SEC0010: Event validation is disabled. C:WebApplicationWeb.config(12): <pages enableEventValidation="false" enableViewStateMac="false" viewStateEncryptionMode="Never" validateRequest="false" />
1>CSC : warning SEC0012: Validate request is disabled. C:WebApplicationWeb.config(12): <pages enableEventValidation="false" enableViewStateMac="false" viewStateEncryptionMode="Never" validateRequest="false" />
1>CSC : warning SEC0013: Pages ViewStateEncryptionMode disabled. C:WebApplicationWeb.config(12): <pages enableEventValidation="false" enableViewStateMac="false" viewStateEncryptionMode="Never" validateRequest="false" />
1>CSC : warning SEC0011: ViewStateMac is disabled. C:WebApplicationWeb.config(12): <pages enableEventValidation="false" enableViewStateMac="false" viewStateEncryptionMode="Never" validateRequest="false" />

  1. Web.config文件中的system.web XML 元素替换为以下内容(更改以粗体突出显示):
<system.web>
  <compilation debug="false" targetFramework="4.6.2" />
  <customErrors mode="On" defaultRedirect="/home/error"/>
  <httpRuntime enableHeaderChecking="true" enableVersionHeader="false" />
  <httpCookies requireSSL="true" httpOnlyCookies="true"/>
  <pages enableEventValidation="true" enableViewStateMac="true" viewStateEncryptionMode="Always" validateRequest="true" />
  <authentication mode="Forms">
    <forms loginUrl="~/Account/Login.aspx" timeout="15" enableCrossAppRedirects="false" protection="All" requireSSL="true" cookieless="UseCookies" />
  </authentication>
</system.web>

  1. 再次构建项目并验证它是否编译且没有任何安全警告。

它是如何工作的…

PUMA 扫描分析器能够捕捉到 C# ASP.NET 网络项目中的网络配置文件中的安全漏洞。在前面的菜谱中,我们向您展示了 PUMA 扫描分析器捕捉到的不同类型的网络安全漏洞,例如不安全的表单身份验证、http cookies 配置、头部设置等。您可以在www.pumascan.com/rules.html#configuration上阅读 PUMA 扫描分析器识别的所有网络配置相关安全漏洞的详细描述。

这些安全分析器被编写为附加文件分析器,用于分析项目中标记为AdditionalFiles项目类型的非源文件。用户必须在他们的项目中将web.config文件标记为附加文件,以在构建期间触发安全分析。您可以在github.com/dotnet/roslyn/blob/master/docs/analyzers/Using%20Additional%20Files.md上阅读更多关于如何编写和消费附加文件分析器的信息。

在网络应用程序的视图标记文件(.cshtml, .aspx 文件)中识别跨站脚本漏洞

跨站脚本XSS)是一种通常在 Web 应用程序中发现的计算机安全漏洞。XSS 允许攻击者向其他用户查看的网页中注入客户端脚本。跨站脚本漏洞可能被攻击者用来绕过如同源策略这样的访问控制。截至 2007 年,Symantec 记录的所有安全漏洞中,大约有 84%是由网站上的跨站脚本造成的。其影响可能从微不足道的麻烦到重大的安全风险不等,这取决于受影响网站处理的数据的敏感性以及网站所有者实施的安全缓解措施的性质。

您可以在en.wikipedia.org/wiki/Cross-site_scripting上阅读更多关于跨站脚本的信息。

在本节中,我们将向您介绍 PUMA 扫描分析器中的规则,以捕获可能导致 ASP.NET Web 项目中跨站脚本攻击的安全漏洞。

准备工作

您需要在您的机器上安装 Visual Studio 2017 以执行本章中的配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装 Visual Studio 2017 的免费社区版本。

如何操作…

  1. 启动 Visual Studio,点击“文件”|“新建”|“项目…”并创建一个新的 Visual C# | Web | ASP.NET Web 应用程序,使用 MVC 模板,例如 WebApplication

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/66e98811-4b2c-4298-9ca3-338328939f7a.png

  1. 安装 Puma.Security.Rules 分析器 NuGet 包(在撰写本文时,最新稳定版本为 1.0.4)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅第二章 通过 NuGet 包管理器搜索和安装分析器 中的配方,在 .NET 项目中消费诊断分析器

  2. 打开“视图”| _ViewStart.cshtml 文件,并在文件末尾添加以下文本:

<div>
 @Html.Raw(string.Format("Welcome <span class=\"bold\">{0}</span>!", ViewContext.ViewBag.UserName))

 @{
   WriteLiteral(string.Format("Welcome <span class=\"bold\">{0}</span>!", ViewContext.ViewBag.UserName));
 }
</div>

  1. 在解决方案资源管理器中选择 _ViewStart.cshtml,并使用下面的属性窗口将其构建操作从内容更改为 AdditionalFiles,然后保存项目。

  2. 向项目中添加一个新的 Web 表单,例如 WebForm.aspx,并将以下带有原始内联表达式的 HTML 标题添加到表单中:

<div>
 <h2>Welcome <%= Request["UserName"].ToString() %></h2>
</div>

  1. 在解决方案资源管理器中选择 WebForm.aspx,并使用下面的属性窗口将其构建操作从内容更改为 AdditionalFiles,然后保存项目。

  2. 在 Visual Studio 或命令行中构建项目,并验证您是否收到来自 PUMA 扫描分析器的以下 SECXXXX 警告:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/607af216-65fc-49d7-8f54-48ace4b2e2d6.png

  1. 将步骤 3 中添加到 _ViewStart.cshtml 的 HTML 分区元素替换为以下内容:
<div>
 Welcome <span class=\"bold\">@ViewContext.ViewBag.UserName</span>!
</div>

  1. 将步骤 5 中添加到 WebForm.aspx 的 HTML 分区元素替换为以下内容:
<div>
 <h2>Welcome <%: Request["UserName"].ToString() %></h2>
</div>

  1. 再次构建项目,并验证它是否编译且没有任何安全警告。

它是如何工作的…

PUMA 扫描分析器在 C# ASP.NET Web 项目的视图标记文件(.cshtml.aspx.ascx)中捕获跨站脚本安全漏洞。在前面的配方中,我们向您展示了 PUMA 扫描分析器捕获的不同类型的安全漏洞,例如使用原始内联和绑定表达式、原始 razor 辅助程序和原始 WriteLiteral 方法将不受信任的数据源写入 HTML 文档的主体,等等。建议在将此类数据写入浏览器之前对其进行 HTML 编码。您可以在 www.pumascan.com/rules.html#cross-site-scripting 读取 PUMA 扫描分析器识别的所有跨站脚本相关安全漏洞的详细描述。

这些安全分析器被编写为额外的文件分析器,用于分析项目中标记为AdditionalFiles项类型的不源文件。用户必须在他们的项目中将视图标记文件标记为额外文件,以在构建期间触发安全分析。您可以在github.com/dotnet/roslyn/blob/master/docs/analyzers/Using%20Additional%20Files.md中了解更多关于如何编写和消费额外文件分析器的信息。

识别可能导致 SQL 和 LDAP 注入攻击的不安全方法调用

SQL 注入是一种代码注入技术,用于攻击数据驱动的应用程序,其中恶意 SQL 语句被插入到输入字段中以执行(例如,将数据库内容导出到攻击者)。SQL 注入攻击允许攻击者伪造身份、篡改现有数据、引发诸如取消交易或更改余额的拒绝问题、允许完全披露系统上的所有数据、破坏数据或使其不可用,并成为数据库服务器的管理员。

LDAP 注入是一种用于利用 Web 应用的代码注入技术,可能会泄露敏感用户信息或修改表示在轻量级目录访问协议LDAP)数据存储中的信息。LDAP 注入通过操纵传递给内部搜索、添加或修改函数的输入参数来利用应用程序中的安全漏洞。

您可以在en.wikipedia.org/wiki/SQL_injection了解更多关于 SQL 注入的详细信息,以及en.wikipedia.org/wiki/LDAP_injection了解更多关于 LDAP 注入的详细信息。

在本节中,我们将向您介绍 PUMA 扫描分析器中的规则,以捕获可能导致数据驱动.NET 项目中 SQL 注入和 LDAP 注入攻击的安全漏洞。

准备工作

您需要在您的机器上安装 Visual Studio 2017 才能执行本章中的配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15安装 Visual Studio 2017 的免费社区版本。

如何操作…

  1. 启动 Visual Studio,点击文件 | 新建 | 项目…并创建一个新的 Visual C# | 类库,例如ClassLibrary

  2. 安装Puma.Security.Rules分析器 NuGet 包(在撰写本文时,最新稳定版本为1.0.4)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅第二章中关于通过 NuGet 包管理器搜索和安装分析器的配方,在.NET 项目中消费诊断分析器

  3. 将以下框架程序集的引用添加到程序集中:<q>System.Data.Linq.dll</q>System.DirectoryServices.dll

  4. Class1.cs 中的默认 Class1 实现替换为以下代码:

using System.Data.Linq;
using System.Data.SqlClient;
using System.DirectoryServices;

public class Class1
{
  private void SQL_Injection(SqlConnection connection, string id)
  {
    using (DataContext context = new DataContext(connection))
    {
      context.ExecuteCommand("SELECT * FROM Items WHERE ID = " + id);
    }

    SqlCommand cmd = new SqlCommand("SELECT * FROM Items WHERE ID = " + id, connection);
    string result = cmd.ExecuteScalar().ToString();
  }

  private void LDAP_Injection(string domain, string userName)
  {
    DirectoryEntry entry = new DirectoryEntry(string.Format("LDAP://DC={0}, DC=COM/", domain));
    DirectorySearcher searcher = new DirectorySearcher(entry)
    {
      SearchScope = SearchScope.Subtree,
      Filter = string.Format("(name={0})", userName)
    };
    SearchResultCollection resultCollection = searcher.FindAll();
  }
}

  1. 在编辑代码以及调用显式构建时,验证您在错误列表和编辑器中的波浪线中是否获得了以下 SECXXX 诊断信息。

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/48965768-bfdc-4d65-9262-24e9f988ba8a.png

  1. 通过将 id 作为调用 context.ExecuteCommand 方法的第二个参数传递来修复在 SQL_Injection 方法中报告的 SEC0106context.ExecuteCommand("SELECT * FROM Items WHERE ID = {0}", id);

  2. 通过使用 *SqlParameter* 参数化传递给 *new* SqlCommand(...) 的查询来修复 SEC0107

SqlCommand cmd = new SqlCommand("SELECT * FROM Items WHERE ID = @id", connection);
SqlParameter parm = new SqlParameter("@id", id);
cmd.Parameters.Add(parm);

  1. 通过使用 Web 保护库(也称为 AntiXSS)的 LDAP 编码方法对域和 userName 参数进行编码来修复 SEC0114 诊断。

    1. 将 NuGet 包引用添加到 AntiXSS 库

    2. 将传递给新 DirectoryEntry(...) 的域参数替换为 Microsoft.Security.Application.Encoder.LdapDistinguishedNameEncode(domain)

    3. 将初始化器中用于筛选器的 string.Format 调用传递给 userName 参数替换为 Microsoft.Security.Application.Encoder.LdapFilterEncode(userName)

  2. 验证错误列表中没有诊断信息,并且项目构建时没有错误或警告。

如何工作…

PUMA 扫描分析器可以捕捉数据驱动应用程序源代码中的 SQL 注入和 LDAP 注入安全漏洞。在前面的配方中,我们向您展示了这些分析器捕捉到的不同类型的漏洞,例如将不受信任的数据与 SQL 查询字符串、SQL 命令、LDAP 目录条目路径和筛选器格式连接起来。

通过使用参数化查询(其中不受信任的数据作为显式格式参数传递)可以防止 SQL 注入攻击。

通过使用 LDAP 编码方法对不受信任的数据进行编码可以防止 LDAP 注入攻击。您可以在 www.pumascan.com/rules.html#injection 阅读 PUMA 扫描分析器识别的所有 SQL 和 LDAP 注入安全漏洞的详细描述。

识别网络应用中的弱密码保护和管理工作

负责密码管理应用程序承担着巨大的风险和责任。用户密码必须具有足够长度/复杂性,安全存储,并保护免受暴力破解和破解尝试。

在本节中,我们将向您介绍 PUMA 扫描分析器中的规则,以捕捉与 ASP.NET 网络项目中弱密码管理相关的漏洞。PUMA 扫描分析器目前支持以下密码管理规则:

  • ASP.NET Identity 弱密码复杂性

  • ASP.NET Identity 缺失密码锁定

您可以在 www.pumascan.com/rules.html#password-management 上阅读有关这些规则的更多详细信息。

准备工作

您需要在您的机器上安装 Visual Studio 2017 才能执行本章中的配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装 Visual Studio 2017 的免费社区版本。

如何做到这一点…

  1. 启动 Visual Studio 并点击 File | New | Project 创建一个新的 Visual C# | Web | ASP.NET Web Application,使用 Web Forms 模板,例如命名为 WebApplication。点击 Change Authentication 按钮并将身份验证更改为 Individual User Accounts:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/aa3dfea3-0f77-443c-a301-51dcd7a732b6.png

  1. 安装 Puma.Security.Rules 分析器 NuGet 包(在撰写本文时,最新稳定版本为 1.0.4)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅配方,通过 NuGet 包管理器搜索和安装分析器,位于 第二章,在 .NET 项目中消费诊断分析器

  2. 构建项目并验证是否从 PUMA 扫描分析器中获得了一堆 SECXXXX 诊断,包括一些与密码保护相关的诊断(SEC0017SEC0018):

WebApplicationApp_StartIdentityConfig.cs(50,41,57,14): warning SEC0017: Password validator settings do not meet the requirements - Minimum Length (12), Numeric Character (True), Lowercase Character (True), Uppercase Character (True), Special Character (True)
...
WebApplicationAccountLogin.aspx.cs(36,121,36,126): warning SEC0018: Password lockout is disabled. To protect accounts from brute force attacks, set the shouldLockout parameter to true.

  1. 打开 WebApplicationApp_StartIdentityConfig.cs 并将所需的最小密码长度更改为 12
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
 RequiredLength = 12,
 RequireNonLetterOrDigit = true,
 RequireDigit = true,
 RequireLowercase = true,
 RequireUppercase = true,
};

  1. 打开 WebApplicationAccountLogin.aspx.cs 并将 shouldLockout 参数更改为 PasswordSignIn 调用为 true:
var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: true);

  1. 构建项目并验证没有出现 SEC0017SEC0018 诊断。

识别来自外部组件的数据弱验证以防止跨站请求伪造和路径篡改等攻击

在本节中,我们将向您介绍 PUMA 扫描分析器中的规则,以捕获由于输入验证不足可能导致以下类型安全攻击的安全漏洞:

  • 跨站请求伪造 (en.wikipedia.org/wiki/Cross-site_request_forgery): 跨站请求伪造,也称为一键攻击或会话劫持,缩写为 CSRF 或 XSRF,是一种恶意利用网站的攻击方式,其中未经授权的命令从应用程序信任的用户那里传输。与利用用户对特定网站的信任的跨站脚本(XSS)不同,CSRF 利用网站对用户浏览器的信任。

  • 路径篡改 (en.wikipedia.org/wiki/Directory_traversal_attack): 目录遍历(或路径遍历)是通过利用对用户提供的输入文件名的不充分安全验证/清理,使得表示遍历到父目录的字符能够通过文件 API。此攻击的目的是使用受影响的应用程序来获取对文件系统的未授权访问

  • 未经验证的跳转 (www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet): 当一个网络应用程序接受可能导致应用程序将请求重定向到包含在不受信任输入中的 URL 的不受信任输入时,可能发生未经验证的跳转和转发。通过将不受信任的 URL 输入修改为恶意网站,攻击者可能成功发起钓鱼诈骗并窃取用户凭据

准备工作

您需要在您的机器上安装 Visual Studio 2017 以执行本章中的配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15安装 Visual Studio 2017 的免费社区版本。

如何操作…

  1. 启动 Visual Studio,点击文件 | 新建 | 项目…并使用 MVC 模板创建一个新的 Visual C# | Web | ASP.NET Web 应用程序,例如WebApplication

  2. 安装Puma.Security.Rules分析器 NuGet 包(在撰写本文时,最新稳定版本为1.0.4)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅第二章,在.NET 项目中使用诊断分析器中的配方,通过 NuGet 包管理器搜索和安装分析器

  3. 将新类Class1添加到项目中,并用以下代码替换文件内容:

using System.Configuration;
using System.Net.Http;
using System.Web.Mvc;

namespace WebApplication
{
  public class Class1
  {
    [AllowHtml]
    public string AllowHtmlProperty { get; set; }

    [HttpPost]
    [ValidateInput(false)]
    public ActionResult Missing_AntiForgeryToken()
    {
      return null;
    }

    [HttpPost]
    public FileResult Path_Tampering(string fileName)
    {
      string filePath = ConfigurationManager.AppSettings["DownloadDirectory"].ToString();
      return new FilePathResult(filePath + fileName, "application/octet-stream");
    }

    private void Certificate_Validation_Disabled()
    {
      using (var handler = new WebRequestHandler())
      {
        handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
      }
    }
  }
}

  1. 在编辑代码和调用显式构建时,验证错误列表和编辑器中的波浪线,以获取以下SECXXX诊断信息。

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/f3b71eb8-5d9b-4ad0-a35e-4f530a1c744b.png

  1. 通过将[ValidateAntiForgeryToken]属性应用于Missing_AntiForgeryTokenPath_Tampering方法来修复前两个SEC0019诊断。

  2. 通过删除AllowHtmlProperty上的[AllowHtml]属性来修复SEC0022错误。

  3. 通过删除Missing_AntiForgeryToken上的[ValidateInput(false)]属性来修复SEC0023错误。

  4. 通过在Path_Tampering方法中将return new FilePathResult(...)替换为return new ValidatedFileResult(...)来修复SEC01111错误,并添加以下ValidatedFileResult类型。

private class ValidatedFileResult : FileResult
{
  public ValidatedFileResult(string filePath, string fileName, string contentType)
  : base(contentType)
  {
    // Add validation logic.
  }

  protected override void WriteFile(HttpResponseBase response)
  {
    // Add write logic
  }
}

  1. 通过删除行handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;来修复SEC0113错误。

  2. 再次构建项目并验证它是否编译且没有任何安全警告。

您可以在www.pumascan.com/rules.html#validation上阅读有关 PUMA 扫描验证规则的更多详细信息。

使用 FxCop 分析器识别源代码的性能改进

在本节中,我们将向您介绍一个流行的第三方分析器包,用于 C#项目,即 FxCop 分析器。

我们将向您展示如何安装 FxCop 分析器 NuGet 包,并给出不同性能规则的违规示例,并展示如何使用 NuGet 包中分析器附带代码修复自动修复这些问题。

准备工作

您需要在您的机器上安装 Visual Studio 2017 才能执行本章中的配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15安装 Visual Studio 2017 的免费社区版本。

如何做到这一点…

  1. 启动 Visual Studio,点击文件 | 新建 | 项目… 创建一个新的 C#类库项目,并将Class1.cs中的代码替换为ClassLibrary\Class1.cs中的代码样本。

  2. 安装Microsoft.CodeAnalysis.FxCopAnalyzers NuGet 包(写作时的最新预发布版本是1.2.0-beta2)。有关如何在项目中搜索和安装分析器 NuGet 包的指导,请参阅第二章,在.NET 项目中使用诊断分析器中的配方,通过 NuGet 包管理器搜索和安装分析器

  3. 通过在解决方案资源管理器中右键单击ClassLibrary1来卸载项目文件,然后通过在解决方案资源管理器中右键单击未加载的项目来在 Visual Studio 中打开项目文件进行编辑。

  4. 将以下PropertyGroup添加到项目中以启用 FxCop 分析器使用的新的 Roslyn IOperation功能:

 <PropertyGroup>
  <Features>IOperation</Features>
 </PropertyGroup>

  1. 重新加载项目并验证以下 FxCop 诊断显示在错误列表中:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/5da129c7-005b-4a2f-80b6-6727dc741eaf.png

  1. 从命令行或 Visual Studio 的顶级构建菜单中构建项目,并验证这些诊断是否也来自构建。

  2. 双击警告CA1815(ValueType 应该重写 Equals)并验证编辑器中是否提供了灯泡以实现 equals、GetHashCode==以及!=运算符方法的重写:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/f4f32f5a-4088-4a2d-97b4-a096ef87d070.png

  1. 通过按Enter键应用代码修复来验证是否修复了CA1815诊断。请注意,这引入了新的CAXXXX诊断,这是由于重写默认实现。

  2. Class1.cs的内容替换为以下代码并验证所有CAXXXX诊断都已修复:

using System;

namespace Namespace
{
  public class Class1: IDisposable
  {
    private static int staticField = 3;
    private int[][] jaggedArray;

    public void Method1(int usedParam)
    {
      Console.WriteLine(usedParam);
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing)
      {
        // Dispose resources.
      }
    }
  }

  public struct ValueType: IEquatable<ValueType>
  {
    public int Property { get; }

    public override bool Equals(object obj)
    {
      return Equals((ValueType)obj);
    }

    public bool Equals(ValueType other)
    {
      return other.Property == Property;
    }

    public override int GetHashCode()
    {
      return Property.GetHashCode();
    }

    public static bool operator ==(ValueType left, ValueType right)
    {
      return left.Property == right.Property;
    }

    public static bool operator !=(ValueType left, ValueType right)
    {
      return left.Property != right.Property;
    }
  }
}

它是如何工作的…

FxCop 分析器是将最重要的 Microsoft 代码分析规则(CAXXXX)移植而来,这些规则是以 MSIL 为基础的二进制分析。与构建后二进制分析相比,FxCop 分析器在编辑代码时增加了实时分析和诊断的优势,以及丰富的代码修复功能来解决这些问题。

FxCop 包含各种不同类别的规则,例如性能、安全、代码风格、API 设计、可维护性等。在本节所涵盖的示例中,我们关注以下性能规则:

  • CA1801(审查未使用参数)(msdn.microsoft.com/en-us/library/ms182268.aspx):方法签名中包含一个在方法体中未使用的参数。

  • CA1810(内联初始化引用类型静态字段)(msdn.microsoft.com/en-us/library/ms182275.aspx):当一个类型声明了显式的静态构造函数时,即时编译器(JIT)会对类型的每个静态方法和实例构造函数添加检查,以确保静态构造函数之前已被调用。静态构造函数检查可能会降低性能。

  • CA1814(优先使用交错数组而非多维数组)(msdn.microsoft.com/en-us/library/ms182277.aspx):交错数组是一个元素为数组的数组。组成元素的数组可以有不同的大小,这可能导致某些数据集的浪费空间更少。

  • CA1815(在值类型上重写等于和运算符等于)(msdn.microsoft.com/en-us/library/ms182276.aspx):对于值类型,继承的等于实现使用反射库并比较所有字段的值。反射计算成本高昂,并且比较每个字段以检查相等性可能是不必要的。如果你期望用户比较或排序实例,或者将实例用作哈希表键,则你的值类型应该实现等于。

  • CA1816(正确调用 GC.SuppressFinalize)(msdn.microsoft.com/en-us/library/ms182269.aspx):实现 Dispose 的方法没有调用 GC.SuppressFinalize,或者不是 Dispose 实现的方法调用了 GC.SuppressFinalize,或者方法调用了 GC.SuppressFinalize 但传递了其他内容。

  • CA1821(移除空终结器)(msdn.microsoft.com/en-us/library/bb264476.aspx):在可能的情况下,避免使用终结器,因为跟踪对象生命周期的额外性能开销。空终结器会带来额外的开销,但没有任何好处。

您可以在 msdn.microsoft.com/en-us/library/ms182260(v=vs.140).aspx 阅读有关所有 FxCop 性能规则的详细文档。

注意,尽管大多数 Microsoft 代码分析性能规则已移植到 FxCop 分析器包中,但并非所有规则在 NuGet 包中默认启用。您可以通过规则集编辑器查看和配置每个 FxCop 规则的抑制状态和严重性。有关使用规则集编辑器的进一步指导,请参阅第二章 在 .NET 项目中消费诊断分析器 中的配方 使用规则集文件和规则集编辑器配置分析器,第二章。

第六章:Visual Studio Enterprise 中的实时单元测试

在本章中,我们将涵盖以下食谱:

  • 在 Visual Studio 中运行基于 NUnit、XUnit 和 MSTest 框架的单元测试项目的实时单元测试(LUT

  • 查看和导航实时单元测试结果

  • 理解代码更改的增量实时单元测试执行

  • 理解 LUT 的启动/停止/暂停/继续/重启功能,以实现细粒度控制

  • 包括和排除实时执行测试的子集

  • 使用“工具 | 选项”对话框配置实时单元测试的不同选项

简介

本章使开发者能够使用 Visual Studio 2017 Enterprise 版本中基于 Roslyn 的新功能,在后台实现智能实时单元测试执行。以下是从该 (blogs.msdn.microsoft.com/visualstudio/2016/11/18/live-unit-testing-visual-studio-2017-rc/) Visual Studio 博客文章中摘录的片段和截图,关于 LUT 的功能有很好的概述。

实时单元测试在您编辑代码时自动在后台运行受影响的单元测试,并实时可视化结果和代码覆盖率,在编辑器中实时显示。除了对您更改对现有测试的影响提供反馈外,您还可以立即获得关于您添加的新代码是否已被一个或多个现有测试覆盖的反馈。这将温和地提醒您在修复错误或添加功能时编写单元测试。您将朝着没有代码库测试债务的应许之地迈进!

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/204ce7ba-0850-4cf9-8e63-5d83bd3b7468.png

如文章中所述,任何给定行的三种潜在状态如下:

LUT 使用 Roslyn API 分析您的产品和测试代码的快照,并确定需要运行的项目单元测试集合。此外,它还使用 Roslyn API 分析代码的增量更新,以智能地确定需要重新运行先前测试运行中的单元测试子集。这些分析 API 与 Visual Studio IDE 诊断引擎使用的相同,用于增量更新错误列表中的智能感知/实时诊断和编辑器中的波浪线。

一旦确定了要执行的单元测试集,它将在后台安排它们的执行,并在测试完成时,自动显示它们的通过/失败/排除状态,并在测试方法上显示相应的符号。用户可以在任何给定时间开始/停止/暂停/恢复实时测试执行。此外,他们还可以为 LUT 排除/包含测试/文件/项目子集。他们还可以随时暂停/重启/停止 LUT,并为 LUT 配置不同的选项,例如在低电量时自动暂停、测试执行超时等。

在 Visual Studio 中运行基于 NUnit、XUnit 和 MSTest 框架的单元测试项目。

在本节中,我们将向您介绍如何为您的单元测试项目启用 LUT,查看和理解测试执行中的实时结果。在 VS2017 中,实时单元测试支持以下单元测试项目的测试项目:

我们将介绍基于前面提到的每个测试框架的单元测试项目中的 LUT(Live Unit Testing)。

入门

您需要在您的机器上安装 Visual Studio 2017 企业版才能执行此配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Enterprise&rel=15 安装授权的企业版。

如何操作

  1. 打开 Visual Studio 并创建一个新的 C# 类库项目,例如 ClassLibrary,以下为相应的源代码:
namespace ClassLibrary
{
  public class Class1
  {
    public bool Method1()
    {
      return true;
    }

    public bool Method2()
    {
      return false;
    }

    public bool Method3(Class1 c)
    {
      return c.Method1();
    }

    public bool Method4(Class1 c)
    {
      return c.Method2();
    }
  }
}

  1. [NUnit] 将一个 C# 单元测试项目,例如 NUnitBasedTestProject,添加到解决方案中,并将 ClassLibrary 的引用添加到该项目中。

  2. 打开项目的 NuGet 包管理器并卸载现有的 NuGet 包引用 MSTest.TestAdapterMSTest.TestFramework

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/46ea5831-af6a-401f-97db-0aae0e8ecb38.png

  1. 将 NUnit 和 NUnit3TestAdapter 的最新稳定版本添加到项目中。

  2. 将文件 UnitTest1.cs 中的源代码替换为以下代码:

using NUnit.Framework;

namespace NUnitBasedTestProject
{
  [TestFixture]
  public class UnitTest1
  {
    [Test]
    public void TestMethod1()
    {
      var c = new ClassLibrary.Class1();
      Assert.True(c.Method1());
    }

    [Test]
    public void TestMethod2()
    {
      var c = new ClassLibrary.Class1();
      Assert.True(c.Method2());
    }
  }
}

  1. 通过执行 Test | Live Unit Testing | Start 命令开始对项目进行实时单元测试。

  2. 等待几秒钟,并注意添加的单元测试在后台执行,TestMethod1 按预期通过,TestMethod2 失败,相应的绿色和红色符号在编辑器中显示。同时,验证输出窗口切换到实时单元测试视图,并显示带有执行时间戳的测试执行日志:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/4b699501-9926-4efe-9907-57b1f6803eb0.png

  1. [XUnit] 将 C# 单元测试项目,例如 XUnitBasedTestProject,添加到解决方案中,并将 ClassLibrary 的引用添加到该项目中。

  2. 打开项目的 NuGet 包管理器,并卸载现有的 NuGet 包引用 MSTest.TestAdapterMSTest.TestFramework

  3. 将 NuGet 包引用添加到 XUnit 和 xunit.runner.visualstudio 的最新稳定版本(晚于 2.2.0)。

  4. UnitTest1.cs 文件中的源代码替换为以下源代码:

using Xunit;

namespace XUnitBasedTestProject
{
  public class UnitTest1
  {
    [Fact]
    public void TestMethod1()
    {
      var c = new ClassLibrary.Class1();
      Assert.True(c.Method1());
    }

    [Fact]
    public void TestMethod2()
    {
      var c = new ClassLibrary.Class1();
      Assert.True(c.Method2());
    }
  }
}

  1. 等待几秒钟,并注意单元测试在后台执行,TestMethod1 通过而 TestMethod2 失败。验证这些测试在编辑器中分别显示绿色和红色图标。

  2. [MSTest] 将 C# 单元测试项目,例如 MSTestBasedTestProject,添加到解决方案中,并将 ClassLibrary 的引用添加到该项目中。

  3. 打开项目的 NuGet 包管理器,并将现有的 NuGet 包引用 MSTest.TestAdapterMSTest.TestFramework 更新到最新稳定版本(晚于 1.1.17)。

  4. 将文件 UnitTest1.cs 中的源代码替换为以下源代码:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MSTestBasedTestProject
{
  [TestClass]
  public class UnitTest1
  {
    [TestMethod]
    public void TestMethod1()
    {
      var c = new ClassLibrary.Class1();
      Assert.IsTrue(c.Method1());
    }

    [TestMethod]
    public void TestMethod2()
    {
      var c = new ClassLibrary.Class1();
      Assert.IsTrue(c.Method2());
    }
  }
}

  1. 等待几秒钟,并注意单元测试在后台执行,TestMethod1 通过而 TestMethod2 失败。验证这些测试在编辑器中分别显示绿色和红色图标。

  2. ClassLibrary 项目中打开 Class1.cs 文件,并在编辑器中验证每个方法的测试覆盖率以及通过/失败详情。

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/54b46b51-f51b-4b05-8aee-c2861854f034.png

查看和导航实时单元测试结果

在本节中,我们将向您展示如何使用测试资源管理器和 Visual Studio 编辑器中的工具提示查看和导航实时测试执行的结果。

入门指南

您需要在您的机器上安装 Visual Studio 2017 企业版才能执行此操作。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Enterprise&rel=15 安装授权的企业版。

如何操作…

  1. 打开 Visual Studio 并创建一个新的 C# 类库项目,例如 ClassLibrary

  2. 将源文件 Class1.cs 中的现有代码替换为附带的示例 ClassLibrary\Class1.cs 中的代码。

  3. 将 C# 单元测试项目,例如 UnitTestProject,添加到解决方案中,并将 ClassLibrary 的引用添加到该项目中。

  4. 打开项目的 NuGet 包管理器,并将现有的 NuGet 包引用 MSTest.TestAdapterMSTest.TestFramework 更新到最新稳定版本(晚于 1.1.17)。

  5. UnitTest1.cs 文件中的源代码替换为附带的示例 UnitTestProject\UnitTest1.cs 中的代码。

  6. 通过点击 Test | Window | Test Explorer 打开测试资源管理器窗口。

  7. 通过执行 Test | Live Unit Testing | Start 命令来为项目启动实时单元测试。

  8. 等待几秒钟,并注意添加的单元测试在后台执行,TestMethod1 按预期通过,而 TestMethod2 失败。

  9. 确认测试结果在测试资源管理器中显示,并且在编辑器中有相应的绿色和红色图标:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/4340f991-6fee-4d57-80d8-ce29d7c99c4a.png

  1. ClassLibrary 中打开源文件 Class1.cs 并点击 Method1 顶部的测试指示器,其显示为 1/1 passing。验证是否弹出一个包含测试名称和状态的工具栏,TestMethod1

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/2c312c28-0c56-45c0-8c6a-187f2e9ddc24.png

  1. 双击工具栏中的方法名称 TestMethod1 并确保导航到 UnitTest1.cs 中此方法的定义。

  2. 切换回 Class1.cs 并在 Method1 附近的绿色勾号 (https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/8381cf4a-2013-44c9-94b4-97b4dd85b194.png) 附近悬停,查看该方法被 1 个测试覆盖:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9a3298a1-a2ce-47c2-a12e-5f67372acb78.png

  1. 点击勾号 (https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/8381cf4a-2013-44c9-94b4-97b4dd85b194.png) 以弹出另一个包含方法名称的工具栏,并验证双击它是否可以带您到测试方法定义。

理解代码更改的增量实时单元测试执行

在本节中,我们将向您展示如何实时单元测试在配置为运行实时单元测试的解决方案中对测试和产品代码进行更改时增量运行。

入门

您需要在您的机器上安装 Visual Studio 2017 企业版才能执行此配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Enterprise&rel=15. 安装授权的企业版。

此外,克隆本章中“查看和导航实时单元测试结果”配方中附加的解决方案 ClassLibrary.sln,或者您可以在执行此配方之前手动执行该配方中的步骤。

如何操作…

  1. 使用两个项目:ClassLibraryUnitTestProject 打开 ClassLibrary.sln 解决方案,并通过导航到测试 | 实时单元测试 | 开始来启动实时单元测试。

  2. 将包含以下代码的新源文件 Class2.cs 添加到 ClassLibrary 项目中:

namespace ClassLibrary
{
  public class Class2
  {
    public bool Method5()
    {
      return false;
    }

    public bool Method6()
    {
      return true;
    }
  }
}

  1. 打开输出窗口,将“显示输出从:”组合框切换到实时单元测试,并通过按高亮按钮清除窗口中的所有内容:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/64291a34-9828-441b-9c2f-4baa3fa99a26.png

  1. 将包含以下代码的新源文件 UnitTest2.cs 添加到 UnitTestProject 项目中:
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MSTestBasedTestProject
{
  [TestClass]
  public class UnitTest2
  {
    [TestMethod]
    public void TestMethod5()
    {
      var c = new ClassLibrary.Class2();
      Assert.IsTrue(c.Method5());
    }

    [TestMethod]
    public void TestMethod6()
    {
      var c = new ClassLibrary.Class2();
      Assert.IsTrue(c.Method6());
    }
  }
}

  1. 等待几秒钟,并注意添加的单元测试在后台执行,TestMethod5 按预期通过,而 TestMethod6 失败。

  2. 此外,请注意输出窗口中的文本表示只执行了两个单元测试(新添加的 TestMethod5TestMethod6)。此外,测试资源管理器显示旧测试 TestMethod1TestMethod2 为灰色,因为这些测试在我们添加新的测试代码时没有执行:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/e49b1dcf-419d-4cf8-b177-2f42fb97c21e.png

  1. 切换回 ClassLibrary 项目中的 Class1.cs 文件并编辑 Method1 以返回 true

  2. 等待几秒钟,让测试在后台执行,并查看 TestMethod1 现在显示为通过。

  3. 注意,在测试资源管理器中,TestMethod5TestMxethod6 现在已变为灰色,表示它们在上次代码更改后没有执行:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/53b7902e-d6bf-4427-b519-dc8216714c87.png

工作原理

在本食谱中,我们向您展示了如何设计实时单元测试执行来分析增量产品和测试更改,并且只执行单元测试项目中的测试子集,这些测试可能会受到这些更改的语义影响。如本章引言部分所述,LUT 使用 Roslyn API 分析您先前测试运行中的增量代码更新。这些是与 Visual Studio IDE 诊断引擎用于增量更新错误列表中的智能感知/实时诊断和编辑器中的波浪线相同的分析 API。

在本食谱中,我们从 ClassLibrary 项目中的单个类 Class1* 和 UnitTestProject 中的单个单元测试类 UnitTest1* 开始。UnitTest1 包含两个方法 TestMethod1TestMethod2,分别测试 Class1 中的 Method1Method2 方法。我们在 ClassLibrary 项目中添加了一个新类 Class2,其中包含 Method5Method6 方法。然后,我们在 UnitTestProject 中添加了一个新的单元测试类 UnitTest2,包含 TestMethod5TestMethod6 方法,分别测试 Method5Method6 方法。在添加这些方法后,LUT 确定类型 UnitTest1 中的现有测试不受新添加的 Class2UnitTest2 的影响,因此没有重新执行它们。随后,当我们编辑 Class1.Method1 时,LUT 只重新执行了 UnitTest1.TestMethod1UnitTest1.TestMethod2,而没有重新执行 UnitTest2 中的测试方法。

理解启动/停止/暂停/继续/重启功能以对 LUT 进行细粒度控制

在本节中,我们将向您展示如何使用启动、停止、暂停、继续和重启命令在 Visual Studio 中控制实时单元测试执行。

入门指南

您需要在您的机器上安装 Visual Studio 2017 企业版才能执行此食谱。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Enterprise&rel=15 安装授权的企业版。

此外,克隆本章节中附带的解决方案ClassLibrary.sln查看和导航实时单元测试结果,在执行此配方之前。或者,您可以在执行此配方之前手动执行该配方中的步骤。

如何操作…

  1. 打开包含两个项目:ClassLibraryUnitTestProject的解决方案ClassLibrary.sln,通过点击测试 | 实时单元测试 | 开始来启动实时单元测试。同时,通过点击测试 | 窗口 | 测试资源管理器来打开测试资源管理器窗口。

  2. Class1.Method1改为返回 true 而不是 false。

  3. 等待几秒钟,并注意单元测试在后台执行,TestMethod1TestMethod2都通过了:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/212c5c58-8eb1-4608-9a37-91c5488a26c7.png

  1. 点击测试 | 实时单元测试 | 暂停以暂时暂停 LUT 执行。注意,一旦暂停 LUT,编辑器中的绿色勾选标记(https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/fda17483-050b-463d-9bc9-0846f151a3a8.png)就会消失:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/b224db4b-1d55-4aeb-ae17-f1b3e4c68b77.png

  1. Class1.Method1改为再次返回 false。这应该会导致在重新执行时TestMethod1失败,但请注意,测试在测试资源管理器窗口中仍然显示为通过,因为测试不是实时运行的:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9547dcb9-9758-4926-9f11-d3e09da0031d.png

  1. 导航到测试 | 实时单元测试 | 继续以恢复 LUT,并注意TestMethod1立即执行,现在在测试资源管理器和编辑器的图标中显示为失败:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/bf30fb79-f10b-482d-90d9-caefa738c42e.png

  1. 点击测试 | 实时单元测试 | 重新启动,并注意编辑器和测试资源管理器窗口中的所有测试结果都会暂时被清除。

  2. 注意输出窗口的实时单元测试面板显示的消息:构建完成(成功),表示项目已重新构建并重新执行了所有测试。

  3. 导航到测试 | 实时单元测试 | 停止,并注意编辑器和测试资源管理器窗口中的所有测试结果都会永久清除:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/590f2111-0fe1-4ba7-a418-e71dfe2b4d44.png

  1. 注意输出窗口的实时单元测试面板显示的消息:实时单元测试停止,确认 LUT 执行已停止。

包含和排除实时执行中的子集测试

在本节中,我们将向您展示如何选择性地包含和/或排除实时单元测试执行中的子集测试。这个功能对于提高非常大的解决方案的响应速度非常有帮助,因为在构建整个解决方案然后执行所有单元测试可能会很耗时且资源密集。

入门

您需要在您的机器上安装 Visual Studio 2017 企业版才能执行此配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Enterprise&rel=15安装授权的企业版。

如何操作…

  1. 打开 Visual Studio 2017 并创建一个包含 10 个 ClassLibrary 项目(例如 ClassLibraryClassLibrary1、…、ClassLibrary9)和一个单元测试项目 UnitTestProject 的 C# 解决方案:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/8ca650dd-cfc7-41f3-ad5d-7c3b050ee8af.png

  1. UnitTestProject 中将项目引用添加到所有类库项目。

  2. UnitTestProject 中添加一个新的测试类 UnitTest2 并将测试方法重命名为 TestMethod2.

  3. 导航到 Test | Live Unit Testing | Start 并注意以下对话框提示解决方案很大,如果包含测试子集,则响应速度会提高:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/2322b4be-14f8-470d-9859-ad3cd7bc9068.png

  1. 点击 No 以确保没有测试被包含在实时执行中。

  2. 验证 UnitTestProject 中测试方法前的蓝色破折号 (https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/91c7043d-5a4f-4c78-8ce4-09b3cb1ad729.png),确认已从实时执行中排除单元测试:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/0740f180-58e2-4fbe-a3bb-b0e76faac5c2.png

  1. 在编辑器中右键单击类 UnitTest1 并执行 Live Tests | Include 以包含此类中的单元测试到 LUT 中。

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/332dadc6-29b1-4082-8641-03bbe55072a1.png

  1. 验证 UnitTest1.TestMethod1 在 LUT 下立即执行并显示为通过,但 UnitTest2.TestMethod2 没有执行。

  2. 再次在编辑器中右键单击类 UnitTest1 并执行 Live Tests | Exclude 以排除此类中的单元测试到 LUT。

  3. 通过按 Enter 键编辑方法 UnitTest1.TestMethod1 并验证测试现在已从 LUT 中排除,并且测试结果也已从编辑器和测试资源管理器窗口中清除:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/7d6526c3-578f-41de-93f1-3cc0d50b0e9c.png

您可以通过在解决方案资源管理器中右键单击项目节点并单击 Live Tests | Include/Exclude 来包含/排除单元测试项目中的所有测试。

使用工具选项对话框配置实时单元测试的不同选项

在本节中,我们将向您展示如何配置 LUT 执行选项,例如在解决方案加载时启动 LUT、配置保持 LUT 启用所需的最低电池百分比以节省电池电量,等等。这使用户能够控制何时自动启动/暂停 LUT,并控制日志记录级别以满足他们的需求。

入门

您需要在您的机器上安装 Visual Studio 2017 Enterprise 版本才能执行此配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Enterprise&rel=15 安装授权的企业版。

此外,克隆本章中附带的 ClassLibrary.sln 解决方案,查看和导航实时单元测试结果,然后继续此配方。或者,您可以在继续此配方之前手动执行该配方中的步骤。

如何操作…

  1. 使用包含两个项目,ClassLibraryUnitTestProject**,**的解决方案 ClassLibrary.sln,并通过导航到测试 | 实时单元测试 | 开始来启动实时单元测试。同时,通过点击测试 | 窗口 | 测试资源管理器来打开测试资源管理器窗口。

  2. 点击工具 | 选项,并在搜索栏中搜索 Live Unit Testing,然后点击常规选项卡以查看 LUT 配置选项:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/75fdd645-88d0-44be-8dad-4e73202e51bf.png

  1. 在解决方案加载时检查启动实时单元测试,并在对话框中点击确定。

  2. 通过导航到测试 | 实时单元测试 | 停止来停止解决方案的实时单元测试。

  3. 关闭并重新打开解决方案,并验证在解决方案加载完成后所有单元测试都会自动执行。

  4. 再次使用工具 | 选项打开 LUT 配置选项,并将暂停 LUT 的最低电池百分比从 30% 更改为 100%

  5. 从笔记本电脑断开电源线,并验证 LUT 会立即暂停,并且编辑任何测试方法都不会导致测试以 LUT 重新执行。

  6. 连接笔记本电脑电源线,并再次验证单元测试;在后台以 LUT 执行。

第七章:C# 交互式和脚本

在本章中,我们将涵盖以下配方:

  • 在 Visual Studio 交互式窗口中编写简单的 C# 脚本并评估它

  • 在 C# 交互式窗口中使用脚本指令和 REPL 命令

  • 在 C# 交互式窗口中使用键盘快捷键评估和导航脚本会话

  • 从现有的 C# 项目初始化 C# 交互式会话

  • 使用 csi.exe 在 Visual Studio 开发者命令提示符中执行 C# 脚本

  • 使用 Roslyn 脚本 API 执行 C# 代码片段

简介

本章介绍了基于 Roslyn 编译器 API 的最强大功能/工具之一:C# 交互式和脚本。您可以在 msdn.microsoft.com/en-us/magazine/mt614271.aspx 阅读有关 C# 脚本的概述。以下是前述文章中关于此功能的一个小示例:

C# 脚本是一个用于测试您的 C# 和 .NET 代码片段的工具,无需创建多个单元测试或控制台项目。它提供了一种简单的方法来探索和理解 API,而无需在您的 %TEMP% 目录中创建另一个 CSPROJ 文件。C# 读取-评估-打印循环 (REPL) 作为 Visual Studio 2015 及以后版本中的交互式窗口以及称为 CSI 的新命令行界面 (CLI) 提供。

这是 Visual Studio 中 C# 交互式窗口的截图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/eae90547-838c-4402-89ae-f66ac39072c0.png

以下是从 Visual Studio 2017 开发者命令提示符中执行的 C# 交互式命令行界面 (csi.exe) 的截图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/bb41acbf-d61f-4a28-a65e-2ee2dd022322.png

在 Visual Studio 交互式窗口中编写简单的 C# 脚本并评估它

在本节中,我们将向您介绍 C# 脚本的基础知识,并展示如何使用 Visual Studio 交互式窗口来评估 C# 脚本。

入门

您需要在您的机器上安装 Visual Studio 2017 社区版才能执行此配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装免费的社区版。

如何操作…

  1. 打开 Visual Studio 并通过点击视图 | 其他窗口 | C# 交互式窗口来启动 C# 交互式窗口:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/ee52c0cc-4925-43f6-8040-2c3cece4c648.png

  1. 在交互式窗口中输入 Console.WriteLine("Hello, World!") 并按 Enter 键来评估 C# 表达式。

  2. 确认 Hello, World! 在交互式窗口中作为结果输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/e6ac3c5f-ea5d-4e64-b429-9780f12769af.png

  1. 现在,输入一个类型为 List<int> 的变量声明语句,并使用集合初始化器:var myList = new List<int> { 3, 2, 7, 4, 9, 0 }; 并按 Enter 键。

  2. 接下来,输入一个表达式语句,该语句访问先前语句中声明的 myList 变量,并使用 linq 表达式过滤列表中的所有偶数:myList.Where(x => x % 2 == 0)

  3. 按下 Enter 键来评估表达式并验证评估结果输出为一个格式良好的可枚举列表:Enumerable.WhereListIterator<int> { 2, 4, 0 }.

  4. 输入命令 $"The current directory is { Environment.CurrentDirectory }."。这访问当前目录环境变量并验证当前目录输出。

  5. 现在,将以下命令输入到交互窗口中,并验证按下 Enter 键会导致根据输入的 await 表达式出现 10 秒的 UI 延迟:

> using System.Threading.Tasks;
> await Task.Delay(10000)
>

  1. 在交互窗口中输入以下类声明并按下 Enter 键:
class C
{
  public void M()
  {
    Console.WriteLine("C.M invoked");
  }
}

  1. 实例化之前声明的类型 C 并通过评估以下语句在实例上调用方法 Mnew C().M();.

  2. 验证交互窗口中的输出为:C.M invoked

您可以在附带的文本文件 InteractiveWindowOutput.txt 中查看本食谱的整个交互窗口内容。

它是如何工作的…

在本食谱中,我们编写了一组简单的 C# 交互脚本命令,以执行在常规 C# 代码中常见的多项操作,但无需声明存根类型/主方法或创建源文件/项目。交互会话期间执行的操作包括:

  1. 评估一个输出字符串到控制台的表达式 (Console.WriteLine(...)).

  2. 声明一个在交互会话期间存在的局部变量,并用集合 initializer (myList) 初始化它。

  3. 在后续的 linq 语句中访问先前声明的变量并评估结果值 (myList.Where(...)).

  4. 在 C# 表达式评估中访问环境变量 (Environment.CurrentDirectory).

  5. 通过使用声明在会话中导入命名空间 (using System.Threading.Tasks;).

  6. 等待异步表达式 (await Task.Delay(10000)).

  7. 在交互会话期间声明一个具有方法的 C# 类 (class Cmethod M)。

  8. 在后续语句中实例化先前声明的类并调用方法 (new C().M()).

让我们简要回顾一下 C# 交互编译器的实现,它使得在交互模式下执行所有前面的常规 C# 操作成为可能。

Csi.main (source.roslyn.io/#csi/Csi.cs,14) 是 C# 交互编译器的入口点。在编译器初始化后,控制最终会到达 CommandLineRunner.RunInteractiveLoop (source.roslyn.io/#Microsoft.CodeAnalysis.Scripting/Hosting/CommandLine/CommandLineRunner.cs,7c8c5cedadd34d79),即 REPL,或读取-评估-打印循环,它读取交互命令并在循环中评估它们,直到用户通过按 Ctrl + C 退出。

对于每条输入的行,REPL 循环会执行 ScriptCompiler.ParseSubmission (source.roslyn.io/#Microsoft.CodeAnalysis.Scripting/ScriptCompiler.cs,54b12302e519f660) 来将给定的源文本解析成一个语法树。如果提交不完整(例如,如果类声明的第一行已经输入),则输出 . 并继续等待更多文本以完成提交。否则,它使用当前提交文本连接到先前的提交并运行新的提交,通过调用核心 C# 编译器 API。提交的结果输出到交互窗口。

关于提交如何链接到先前的提交并在交互编译器中执行的更多详细信息超出了本章的范围。您可以通过 (source.roslyn.io/#q=RunSubmissionsAsync) 导航到脚本编译器的代码库以了解其内部工作原理。

在 C# 交互窗口中使用脚本指令和 REPL 命令

在本节中,我们将向您介绍 C# 交互脚本中可用的常见指令和 REPL 命令,并展示如何在 Visual Studio 交互窗口中使用它们。

入门

您需要在您的机器上安装 Visual Studio 2017 社区版才能执行此菜谱。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装免费的社区版。

如何操作…

  1. 打开 Visual Studio 并通过点击视图 | 其他窗口 | C# 交互来启动 C# 交互窗口。

  2. 将附带的示例菜谱中的 Newtonsoft.Json.dll 复制到您的临时目录 %TEMP%

  3. 执行以下 #r 指令以将此程序集加载到交互会话中:

#r "<%YOUR_TEMP_DIRECTORY%>\Newtonsoft.Json.dll"

  1. 验证您现在可以引用此程序集中的类型以及创建对象和调用方法。例如,将以下代码片段输入到交互窗口中:
using Newtonsoft.Json.Linq;
JArray array = new JArray();
array.Add("Manual text");
array.Add(new DateTime(2000, 5, 23));

JObject o = new JObject();
o["MyArray"] = array;

o.ToString()

  1. 验证输出到交互窗口的数组字符串表示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/273b2757-5621-4487-85df-5361864399e7.png

  1. 执行 REPL 命令#clear(或#cls),并验证这清除了交互窗口中的所有文本。

  2. 将附带的示例菜谱中的MyScript.csx复制到您的临时目录%TEMP%

  3. 执行以下#load指令以在交互会话中加载并执行此脚本:

#load "<%YOUR_TEMP_DIRECTORY%>\MyScript.csx"

  1. 验证脚本是否执行,并从执行中获得以下输出:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/36cbf5c2-5ef2-4ec4-b1fd-352b8a8e597c.png

  1. 执行#reset REPL 命令以重置交互会话。

  2. 现在,尝试在交互会话中引用之前重置之前添加的Newtonsoft.Json命名空间,并验证您是否得到错误,因为程序集已不再在会话中加载:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/882c3609-67a6-48b6-889e-17da609bed02.png

  1. 最后,执行#help REPL 命令以打印交互窗口中可用的键盘快捷键、指令和 REPL 命令的帮助文本:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/b894f5a4-4f72-43e5-9bee-9a9d579e48ed.png

您可以在附带的文本文件InteractiveWindowOutput.txt中查看此菜谱的交互窗口的完整内容。

在 C#交互窗口中使用键盘快捷键评估和导航脚本会话

在本节中,我们将向您介绍 C#交互脚本中常见的键盘快捷键,并展示如何在 Visual Studio 交互窗口中使用它们。

如前一道菜谱的最后一步所示,您可以在交互窗口中使用*#help* REPL 命令查看 C#交互窗口中可用的所有键盘快捷键的完整列表。

开始使用

您需要在您的机器上安装 Visual Studio 2017 社区版才能执行此菜谱。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15.安装免费的社区版。

如何操作…

  1. 打开 Visual Studio,通过点击视图|其他窗口|C#交互窗口*.*打开 C#交互窗口。

  2. 输入字符串常量"World!"并按Enter键以评估和输出字符串。

  3. 输入"Hello, "并将光标移至步骤 2 中的上一个提交,然后按Ctrl + Enter键将上一个提交的文本附加到当前提交。当前提交的文本应更改为"Hello, " + "World!",按Enter键应输出文本"Hello, World!"

  4. 输入@"Hello, World并按Shift + Enter键在当前提交中添加新行。在下一行输入with a new line!"并按Enter键,应输出文本"Hello, World\r\nwith a new line!",如下所示:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/1fefa632-013e-4858-b970-b4d91bb1d6da.png

  1. 输入Hello并按Esc键;这应该清除当前行的文本。

  2. 同时按下 Alt + 向上箭头键;这应该将当前提交的文本更改为与上一个提交相同,在我们的例子中,是 @"Hello, World

    . 在新的一行!".

  3. 按下 Enter 键再次输出 "Hello, World\r\nwith a new line!"

  4. 按下引号键 ". 这应该自动添加另一个引号。按 Delete 键删除这个自动添加的引号并添加第二个引号。

  5. 现在,同时按下 Ctrl + Alt + 向上箭头键;这应该将当前提交的文本更改为与之前提交中相同字符的最后一个提交相同,即 ". 在我们的例子中,这是提交 "Hello, " + "World!".

  6. 按下 Enter 键输出 "Hello, World!".

  7. 现在,将光标放在会话中的第一个提交上,即步骤 2 中的提交。

  8. 同时按下 Ctrl + A 键以选择第一个提交中的全部文本,即 "World!"。然后,同时按下 Ctrl + Enter 键以将此文本复制到当前提交。

  9. 按下 Enter 键输出 "World!".

  10. 将光标移回之前的提交,并连续两次按下 Ctrl + A 键以选择交互窗口的全部内容。

您可以在附带的文本文件 InteractiveWindowOutput.txt 中查看此菜谱的交互窗口的全部内容。

从现有 C# 项目初始化 C# 交互会话

在本节中,我们将向您展示如何从现有的 C# 项目初始化 C# 交互式脚本会话,然后在使用 Visual Studio 交互窗口中的项目类型。

入门

您需要在您的机器上安装 Visual Studio 2017 社区版才能执行此菜谱。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15. 免费安装社区版。

如何操作…

  1. 打开 Visual Studio 并通过单击“视图”|“其他窗口”|“C# 交互”来启动 C# 交互窗口。

  2. 在交互窗口中声明一个局部变量 int x = 0; 并按下 Enter 键。

  3. 执行 Console.WriteLine(x) 并验证输出 0 以确认变量 x 已在当前会话中声明。

  4. 创建一个新的 C# 类库项目,例如 ClassLibrary

  5. 将以下方法 M 添加到创建的项目中的 Class1 类型:

public void M()
{
  Console.WriteLine("Executing ClassLibrary.Class1.M()");
}

  1. 在解决方案资源管理器中右键单击项目,然后单击“使用项目初始化交互”:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/3948a6dd-e7ba-442c-a196-020065aa464a.png

  1. 验证项目构建是否开始,以及 C# 交互会话是否已使用项目引用和输出程序集(ClassLibrary.dll)重置:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/b9b1a12d-5e11-4ecf-8cfa-c559885de8ad.png

  1. 在交互窗口中输入以下文本 new Class1().M(); 并按下 Enter 键以执行提交。

  2. 验证 Executing ClassLibrary.Class1.M() 作为结果输出,确认交互会话是用 ClassLibrary 项目初始化的。

  3. 尝试引用在步骤 2 中定义的变量 x,即在通过执行 Console.WriteLine(x); 初始化项目交互会话之前。

  4. 验证这导致以下编译时错误,确认当我们从项目初始化会话时,会话状态已完全重置:

> Console.WriteLine(x);
(1,19): error CS0103: The name 'x' does not exist in the current context

您可以在附带的文本文件 InteractiveWindowOutput.txt 中查看此配方的整个交互窗口内容。

在 Visual Studio 开发者命令提示符中使用 csi.exe 执行 C# 脚本

在本节中,我们将向您展示如何使用命令行界面执行 C# 脚本及其交互模式。csi.exe(CSharp Interactive)是 C# 交互的 CLI 可执行文件,它随 C# 编译器工具集和 Visual Studio 一起提供。

入门

您需要在您的机器上安装 Visual Studio 2017 社区版才能执行此配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装免费的社区版。

如何操作…

  1. 启动 Visual Studio 2017 开发命令提示符并执行命令 csi.exe 以启动 C# 交互会话*.*。

  2. 在控制台输入 Console.WriteLine("Hello, World!") 并按 Enter 键以交互模式执行命令:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/c7852ad9-4c20-4356-b701-d9c5e7528d17.png

  1. Ctrl + C 退出交互模式。

  2. 创建一个名为 MyScript.csx 的脚本文件,包含以下代码以输出脚本参数:

var t = System.Environment.GetCommandLineArgs();

foreach (var i in t)
{
  System.Console.WriteLine(i);
}

  1. 使用参数 1 2 3 执行脚本并验证以下输出。注意,执行脚本后,我们返回到命令提示符,而不是交互会话:
c:\>csi MyScript.csx 1 2 3
csi
MyScript.csx
1
2
3

  1. 现在,在脚本前添加一个额外的 -i 参数并执行,验证与之前相同的输出,不过这次我们返回到交互式提示符:
c:\>csi -i MyScript.csx 1 2 3
csi
-i
MyScript.csx
1
2
3
>

  1. 执行 Console.WriteLine(t.Length) 并验证输出为 6,确认在当前交互会话中,脚本中声明的并使用命令行参数初始化的变量 t 仍然存在。

  2. Ctrl + C 退出交互模式。

  3. 执行 csi -i 以以交互模式启动 csi.exe 并执行 #help 命令以获取可用键盘快捷键、REPL 命令和脚本指令列表:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/24dd68e6-500d-48ba-be68-2ef98b2a8a9b.png

  1. 注意,csi.exe 中可用的快捷键、REPL 命令和脚本指令集合是 Visual Studio 交互窗口中相应集合的子集。有关可用的快捷键、命令和指令的详细信息,请参阅本章前面的配方 在 C# 交互窗口中使用脚本指令和 REPL 命令在 C# 交互窗口中使用键盘快捷键评估和导航脚本会话

  2. Ctrl + C 退出交互模式。

  3. 尝试使用参数执行 csi.exe,但不要指定脚本名称,并验证关于缺少源文件的错误 CS2001

c:\>csi.exe 1
error CS2001: Source file 'c:\1' could not be found.

您可以在 github.com/dotnet/roslyn/wiki/Interactive-Window#repl 了解有关命令行 REPL 和 csi.exe 参数的更多信息。

使用 Roslyn 脚本 API 执行 C# 代码片段

在本节中,我们将向您展示如何编写一个使用 Roslyn 脚本 API 执行 C# 代码片段并消费其输出的 C# 控制台应用程序。脚本 API 允许 .NET 应用程序实例化一个 C# 引擎,并针对宿主提供的对象执行代码片段。脚本 API 也可以直接在交互会话中使用。

开始使用

您需要在您的机器上安装 Visual Studio 2017 社区版才能执行此配方。您可以从 www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15 安装免费的社区版。

如何操作…

  1. 打开 Visual Studio 并创建一个新的以 .NET Framework 4.6 或更高版本为目标的 C# 控制台应用程序,例如 ConsoleApp.

  2. 安装 Microsoft.CodeAnalysis.CSharp.Scripting NuGet 包(在撰写本文时,最新稳定版本是 2.1.0)。有关如何在项目中搜索和安装 NuGet 包的指导,请参阅第二章 在 .NET 项目中消费诊断分析器 中的配方,通过 NuGet 包管理器搜索和安装分析器

  3. Program.cs 中的源代码替换为附带的代码示例中的源代码 \ConsoleApp\Program.cs.

  4. Ctrl + F5 构建并启动不带调试的项目 .exe

  5. 验证 EvaluateSimpleAsync 评估的第一个输出:*

Executing EvaluateSimpleAsync...
3

  1. 按任意键继续执行。

  2. 验证 EvaluateWithReferencesAsync 评估的第二个输出:

Executing EvaluateWithReferencesAsync...
<%your_machine_name%>

  1. 按任意键继续执行。

  2. 验证 EvaluateWithImportsAsync 评估的第三个输出:

Executing EvaluateWithImportsAsync...
1.4142135623731

  1. 按任意键继续执行。

  2. 验证 EvaluateParameterizedScriptInLoopAsync 评估的最后输出:

Executing EvaluateParameterizedScriptInLoopAsync...
0
1
4
9
16
25
36
49
64
81
Press any key to continue . . .

  1. 按任意键退出控制台。

请参阅文章 (github.com/dotnet/roslyn/wiki/Scripting-API-Samples) 以获取脚本 API 的更多示例。

它是如何工作的…

在这个菜谱中,我们基于 Roslyn 脚本 API 编写了一个 C# 控制台应用程序,以执行各种常见的脚本操作。丰富的脚本 API 为评估、创建和执行具有配置选项的脚本提供了一个强大的对象模型。

让我们逐步分析这个菜谱中的代码,了解我们是如何实现这些操作的:

public static void Main(string[] args)
{
  EvaluateSimpleAsync().Wait();

  Console.ReadKey();
  EvaluateWithReferencesAsync().Wait();

  Console.ReadKey();
  EvaluateWithImportsAsync().Wait();

  Console.ReadKey();
  EvaluateParameterizedScriptInLoopAsync().Wait();
}

Main 方法调用单个方法以执行以下操作:

  • EvaluateSimpleAsync: 对二进制加法表达式的简单评估

  • EvaluateWithReferencesAsync: 一个涉及传递给脚本选项的引用程序集的评估

  • EvaluateWithImportsAsync: 一个涉及导入系统命名空间并从该命名空间调用 API 的评估

  • EvaluateParameterizedScriptInLoopAsync: 通过参数化脚本并在值循环中调用创建和评估脚本

EvaluateSimpleAsync 调用最常用的脚本 API,即 CSharpScript.EvaluateAsync (source.roslyn.io/#q=CSharpScript.EvaluateAsync),并使用一个表达式作为评估该表达式的参数。在我们的例子中,我们将 1 + 2 作为参数传递给 EvaluateAsync,输出结果 3

private static async Task EvaluateSimpleAsync()
{
  Console.WriteLine("Executing EvaluateSimpleAsync...");
  object result = await CSharpScript.EvaluateAsync("1 + 2");
  Console.WriteLine(result);
}

EvaluateWithReferencesAsync 调用相同的 CSharpScript.EvaluateAsync API (source.roslyn.io/#q=CSharpScript.EvaluateAsync),但使用通过脚本选项传递的额外引用程序集,通过 ScriptOptions.WithReferences API (source.roslyn.io/#q=ScriptOptions.WithReferences)。

在我们的例子中,我们传递 typeof(System.Net.Dns).Assembly 作为评估 System.Net.Dns.GetHostName() 的额外引用,输出机器名:

private static async Task EvaluateWithReferencesAsync()
{
  Console.WriteLine("Executing EvaluateWithReferencesAsync...");
  var result = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()",
  ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly));
  Console.WriteLine(result);
}

EvaluateWithImportsAsync 使用通过脚本选项传递的命名空间导入调用 CSharpScript.EvaluateAsync API (source.roslyn.io/#q=CSharpScript.EvaluateAsync),通过 ScriptOptions.WithImports API (source.roslyn.io/#q=ScriptOptions.WithImports)。在我们的例子中,我们将 System.Math 作为评估 Sqrt(2) 的额外命名空间导入,输出结果 1.4142135623731

private static async Task EvaluateWithReferencesAsync()
{
  Console.WriteLine("Executing EvaluateWithReferencesAsync...");
  var result = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()",
  ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly));
  Console.WriteLine(result);
}

EvaluateParameterizedScriptInLoopAsync 使用 CSharpScript.Create API (source.roslyn.io/#Microsoft.CodeAnalysis.CSharp.Scripting/CSharpScript.cs,3beb8afb18b9c076) 创建一个参数化的 C# 脚本,该脚本接受要执行的脚本代码和一个全局类型作为参数:

private static async Task EvaluateParameterizedScriptInLoopAsync()
{
  Console.WriteLine("Executing EvaluateParameterizedScriptInLoopAsync...");
  var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals));
  script.Compile();
  for (int i = 0; i < 10; i++)
  {
    Console.WriteLine((await script.RunAsync(new Globals { X = i, Y = i })).ReturnValue);
  }
}

然后它调用 Script.Compile API (source.roslyn.io/#q=Script.Compile) 来编译脚本。编译后的脚本随后在循环中使用 Script.RunAsync API (source.roslyn.io/#q=Script.RunAsync) 执行,循环中使用不同实例的全局类型 Globals,字段 XY 的值递增。每次迭代计算表达式 X * Y 的结果,在我们的例子中,这只是从零到九的循环中所有数字的平方。

第八章:向 Roslyn C#编译器开源代码贡献简单功能

在本章中,我们将介绍以下食谱:

  • 设置 Roslyn 征募

  • 在 C#编译器代码库中实现新的语法错误

  • 在 C#编译器代码库中实现新的语义错误

  • 为 C#编译器代码库中的新错误编写单元测试

  • 使用 Roslyn 语法可视化器查看源文件的 Roslyn 语法标记和节点

  • 向 Roslyn Pull request 提交以贡献 C#编译器和 VS IDE 的下一个版本

简介

本章使开发者能够向 Roslyn C#编译器添加新功能。

让我们简要地浏览一下 Roslyn 源代码树的不同部分。您可以在 VS2017 分支中快速查看 Roslyn 仓库的最顶层源文件夹:github.com/dotnet/roslyn/tree/Visual-Studio-2017/src

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/d251ab68-d073-46e3-b62c-658b8681a930.png

最重要的源文件夹及其对应的组件如下:

  • Compilers: 这实现了 Roslyn C#和 VB 编译器以及核心 Microsoft 代码分析层,该层公开了丰富的语言无关 API,用于对源代码进行语法和语义分析。此层的核心概念包括 SyntaxTree(源文件)、SemanticModel(源文件的语义)、Symbol(源中的声明)和 Compilation(源文件和选项的集合)。

  • Workspaces: 这实现了工作区层及其相应的 API,用于对项目和解决方案进行工作区级别的代码分析和重构。此层对在工作区上运行的宿主操作系统(如 Visual Studio 或命令行工具)完全无关。此层的核心概念包括文档(带有相关语义模型的语法树)、项目(由文档和程序集引用组成的集合,构成编译,并具有配置编译的属性)、解决方案(项目的集合)和工作区级别的选项。

  • Features: 建立在 Workspaces 层之上的可扩展 IDE 功能,如代码修复、重构、IntelliSense、完成、查找引用、导航到定义、编辑并继续(EnC)、诊断等,都位于此层。此层与 Visual Studio 无关,可以托管在不同的宿主或命令行工具中。

  • VisualStudio: 在功能和 Workspaces 层之上构建的 Visual Studio 特定组件提供了一个端到端的 C#和 VB IDE 体验。此层的核心概念包括Visual Studio 工作区、项目系统(组件通过填充工作区和启用上述提到的 IDE 功能,在静态程序表示和实时 IDE 表示之间架起桥梁)和语言服务(向项目系统公开的核心语言语义服务)。

  • ExpressionEvaluator: 用于解析和评估简单表达式以及计算运行时结果的 C# 和 VB 表达式评估器。

  • Samples: 展示 Roslyn API 使用的示例和教程。您可以在github.com/dotnet/roslyn/wiki/Samples-and-Walkthroughs中阅读更多详细信息。

您可以在github.com/dotnet/roslyn/wiki/Roslyn%20Overview阅读更多关于 Roslyn 的详细信息。

设置 Roslyn 入队

在本节中,我们将向您介绍安装所需工具、加入 Roslyn、构建 Roslyn 编译器源代码以及部署、调试和运行本地构建的编译工具集测试的步骤。

入门

您需要在您的机器上安装 Visual Studio 2017 以执行本章中的配方。您可以从www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15安装免费的 Visual Studio 2017 社区版本。请确保所选的工作负载包括 C#、VB、MSBuild 和 Visual Studio 扩展性。更具体地说,将 .NET 桌面开发工作负载和 Visual Studio 扩展性工具工作负载添加到您的 VS 安装中。

如何做到这一点…

  1. 按照以下步骤在desktop.github.com/安装 GitHub for desktop 并使用您的 GitHub 个人资料登录 GitHub。如果您没有个人资料,您可以在github.com/join创建一个。

  2. Git Shell 执行以下命令以使用 VS2017 标签加入并恢复 Roslyn 编译器源代码:

    • git clone https://github.com/dotnet/roslyn c:\Roslyn

    • cd c:\Roslyn

    • git checkout tags/Visual-Studio-2017

    • Restore.cmd

  3. 从 VS2017 管理员开发命令提示符构建 Roslyn 编译器子树:msbuild /m /v:m Compilers.sln。您也可以使用 Build.cmdmsbuild /m /v:m Roslyn.sln 构建整个 Roslyn 源树。此步骤构建源代码并将本地构建的编译工具集(或整个 Roslyn IDE + 编译工具集)部署到 RoslynDev hive。

注意:如果您由于强名称签名失败而遇到构建错误,请从管理员开发命令提示符执行以下命令以禁用强名称验证:sn -Vr *,然后执行构建。

  1. 在 VS2017 中打开 Roslyn.sln 并将 Compilers\CompilerExtension.csproj 设置为启动项目。

  2. 点击 Ctrl + F5 将本地构建的编译工具集部署到单独的 Visual Studio hive 并从这个 hive 启动一个新的 Visual Studio 实例(RoslynDev)。

  3. 在新的 Visual Studio 实例中,创建一个新的 C# 类库项目。

  4. 通过打开“工具”|“选项”|“项目和解决方案”|“构建和运行”|“MSBuild 项目构建输出详细程度”,将 msbuild 输出详细程度从“最小”更改为“正常”。

  5. 构建 C#类库项目,并打开输出窗口以确认是否使用了本地构建的<q>csc.exe</q>来构建库,并且它是从 Visual Studio RoslynDev 存储库执行的:C:\USERS\<%USER_NAME%>\APPDATA\LOCAL\MICROSOFT\VISUALSTUDIO\15.0_XXXXXXXXROSLYNDEV\EXTENSIONS\MICROSOFT\ROSLYN COMPILERS\42.42.42.42424\csc.exe

  6. 右键单击Compilers\CSharpCompilerSemanticTest.csproj并打开项目属性|调试页面。通过执行 Start external program 文本框中指定的 xunit 控制台 exe 文件,并使用命令行参数文本框中的参数执行 C#编译器语义单元测试:C:\Users\<%USER_NAME%>\.nuget\packages\xunit.runner.console\<%VERSION%>\tools\xunit.console.x86.exe "<%REPO_ROOT%>\Binaries\Debug\UnitTests\CSharpCompilerSemanticTest\\Roslyn.Compilers.CSharp.Semantic.UnitTests.dll" -html "<%REPO_ROOT%>\Binaries\Debug\UnitTests\CSharpCompilerSemanticTest\\xUnitResults\Roslyn.Compilers.CSharp.Semantic.UnitTests.html" -noshadow

您可以在github.com/dotnet/roslyn/blob/dev16/docs/contributing/Building,%20Debugging,%20and%20Testing%20on%20Windows.md获取有关注册、构建和测试 Roslyn 源代码的更详细说明。

在 C#编译器代码库中实现新的语法错误

本节将使 Roslyn 贡献者能够修改 C#解析器以添加新的语法错误。当在符号声明(如字段、方法、局部变量等)中使用不正确的修饰符时,C#解析器报告诊断CS0106t*he modifier 'modifier' is not valid for this* item)(msdn.microsoft.com/en-us/library/3dd3ha66.aspx)。例如,以下错误代码生成了三个CS0106实例:

class Class
{
  // Error CS0106: The modifier 'async' is not valid for this item
  async int field;

  // Error CS0106: The modifier 'readonly' is not valid for this item               
  readonly static void M()         
  {
    // Error CS0106: The modifier 'readonly' is not valid for this item
    readonly int local = 0;        
    System.Console.WriteLine(local);
  }
}

然而,如果您声明了一个具有不正确修饰符的参数,例如readonly int param,它不会生成CS0106错误,而是生成大量与缺失标记、无效标识符等相关的不太有用的语法错误和波浪线。考虑以下示例:

class Class
{
  static void M(readonly int param)
  {
  }
}

这会生成以下错误和波浪线集:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/734e2d61-fb46-43a1-b916-acd118645928.png

在本节中,我们将修改 C#解析器以实现更好的错误恢复机制,对于此类无效参数修饰符,将报告单个CS0106语法错误,这对最终用户更有帮助。

入门

您需要确保已安装带有.NET 开发和 VS 扩展性工作负载的 Git 工具、VS2017,并且已使用 VS2017 标签注册和构建了 Roslyn 源代码。有关参考,请参阅本章开头“设置 Roslyn 注册”的配方。

如何操作…

  1. 在 VS2017 中打开 Roslyn 仓库根目录下的 Roslyn.sln

  2. 打开源文件 <%ROOT%>\src\Compilers\CSharp\Portable\Parser\LanguageParser.cs

  3. 导航到私有方法 IsPossibleParameter(第 4060 行)并将高亮的 || 子句添加到默认情况返回语句中:

default:
  return IsPredefinedType(this.CurrentToken.Kind) || GetModifier(this.CurrentToken) != SyntaxModifier.None;

  1. 导航到私有方法 ParseParameterModifiers(第 4234 行),并将现有的 while (IsParameterModifier(this.CurrentToken.Kind, allowThisKeyword)) 替换为 while (true) 循环,在 while 循环的开始处添加以下 if 语句:
while (true)
{
  if (!IsParameterModifier(this.CurrentToken.Kind, allowThisKeyword))
  {
    if (GetModifier(this.CurrentToken) != SyntaxModifier.None)
    {
      // Misplaced modifier
      var misplacedModifier = this.EatToken();
      misplacedModifier = this.AddError(misplacedModifier, ErrorCode.ERR_BadMemberFlag, misplacedModifier.Text);
      modifiers.Add(misplacedModifier);
      continue;
    }

    break;
  }
  ...

  1. 构建解决方案。

  2. VisualStudio\VisualStudioSetup.Next.csproj 设置为启动项目,然后按 Ctrl + F5 启动一个新的 VS 实例,并使用本地构建的编译器和 IDE 工具集。

  3. 创建一个新的 C# 类库项目,例如 ClassLibrary,并添加以下代码:

class Class
{
  static void M(readonly int param)
  {
  }
}

  1. 验证错误列表中有一个 CS0106 诊断,用于无效的 readonly 修饰符,并且编辑器有一个单条波浪线。

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/8f4b6f12-be09-473a-af01-11f9f09e747d.png

  1. 构建项目并验证构建输出只有一个 CS0106 诊断。

您可以在 github.com/mavasani/roslyn/commit/02b7be551b46fa9a8e054c3317bc2ae7957b563c. 查看在此配方中做出的所有解析器更改。

它是如何工作的…

解析、语法分析或句法分析是分析一串符号的过程,这些符号可以是自然语言或计算机语言中的,并符合形式语法的规则。

C# 语言解析器是编译器工具链的第一个阶段,它根据 C# 语言规范解析源文件以生成语法树。解析每个源文件的主要入口点是 SyntaxFactory.ParseCompilationUnit (source.roslyn.io/#q=SyntaxFactory.ParseCompilationUnit),它将给定的源文本转换为带有语法节点、标记和语法的 CompilationUnitSyntax

使用 source.roslyn.io/ 进行丰富的语义搜索和导航 Roslyn 源代码。请注意,该网站索引的源代码版本对应于 Roslyn 仓库 master 分支的最新源代码,可能与 Visual-Studio-2017 标签的源代码不同。

LanguageParser.ParseCompilationUnitCore (source.roslyn.io/#q=LanguageParser.ParseCompilationUnitCore) 是 LanguageParser 的核心方法,它解析最外层的命名空间声明(如果有的话),然后解析命名空间体内的类型和成员声明。它使用 Lexer (source.roslyn.io/#q=Lexer.cs) 来读取下一个标记,并使用非常复杂的错误恢复机制为错误代码提供有意义的语法错误,这些错误代码包含位置不当或无效的标记。

在本配方中,我们确定了一个参数上无效修饰符的案例,其中 C#编译器没有执行最佳错误恢复,并更改了解析器代码以查找参数上的无效修饰符,并使用CS0601诊断解析它们。

在步骤 3 中对IsPossibleParameter默认情况返回语句的以下突出更改确保了当我们遇到参数声明修饰符列表中的成员级修饰符(如 readonly、public、private 等)时,不会完全跳过参数列表。相反,我们将修饰符与参数关联。

default:
 return IsPredefinedType(this.CurrentToken.Kind) || GetModifier(this.CurrentToken) != SyntaxModifier.None;

步骤 4 中添加到ParseParameterModifiers方法 while 循环中的If语句确保我们将有效和无效的修饰符都解析到参数修饰符列表中(与原始代码中仅解析有效修饰符相反),并在无效修饰符上生成适当的CS0106语法错误。

在 C#编译器代码库中实现新的语义错误

本节将使 Roslyn 贡献者能够对 C#绑定/语义分析阶段进行更改,以添加新的语义诊断。此外,我们还将展示如何扩展在本地重写(降低)阶段报告的现有语义诊断,以涵盖更多情况。

使用var关键字进行隐式类型声明的使用是一个非常主观的问题。C#编译器仅在无法推断类型或类型无效的隐式类型声明上报告非主观语义错误。然而,在某些情况下,初始化器的类型是有效的并且可以推断,但由于初始化器表达式的转换而不太明显。例如,考虑以下表达式var x = 1 + 1.0, var y = "string" + 1xy的初始化器包含二元表达式的左右两侧的隐式转换,这还可能涉及用户定义的隐式运算符转换,因此,变量的推断类型并不明显。我们将扩展 C#绑定以报告针对此类情况的新警告CS0823警告 CS0823:使用显式类型进行声明,因为初始化器类型'{0}'由于转换而不明显

此外,在本配方中,我们将扩展CS1717(对同一变量进行赋值;您是想分配其他内容?)(docs.microsoft.com/en-us/dotnet/csharp/misc/cs1717)以报告在自赋值属性访问上的错误。目前,它仅涵盖自赋值字段、局部变量、参数和事件访问。

在这两个更改之后,我们将看到以下新的警告:

class Class
{
  int X { get; set; }

  void M(int x)
  {
    // Warning CS1717 Assignment made to same variable; did you mean to 
    //assign something else?
    X = X;     

    // Warning CS0823 Use an explicit type for declaration as the 
    //initializer type 'string' is not apparent due to conversions
    var y = x + "" ; 
  }
}

入门

您需要确保已安装Git工具、VS2017 以及.NET 开发,并且已安装 VS 扩展性工作负载,并使用 VS2017 标签登记和构建 Roslyn 源代码。有关参考,请参阅本章开头处的配方设置 Roslyn 登记

如何做到这一点…

  1. 在 VS2017 中打开 Roslyn 仓库根目录下的Roslyn.sln

  2. 打开源文件<%ROOT%>\src\Compilers\CSharp\Portable\Errors\ErrorCode.cs,在行 566 处添加以下新的警告 ID:WRN_ImplicitlyTypedVariableNotRecommended = 823,

  3. 打开 resx 文件<q><%ROOT%>\src\Compilers\CSharp\Portable\CSharpResources.resx</q>,为警告消息添加以下新的资源字符串:

<data name="WRN_ImplicitlyTypedVariableNotRecommended" xml:space="preserve">
  <value>
    Use an explicit type for declaration as the initializer type '{0}' is not apparent due to conversions
  </value>
</data>

<data name="WRN_ImplicitlyTypedVariableNotRecommended_Title" xml:space="preserve">
  <value>
    Use an explicit type for declaration as the initializer type is not apparent due to conversions
  </value>
</data>

  1. 打开开源文件<%ROOT%>\src\Compilers\CSharp\Portable\Errors\ErrorFacts.cs,在方法GetWarningLevel的第 320 行添加一个新的 switch case:ErrorCode.WRN_ImplicitlyTypedVariableNotRecommended:

  2. 打开源文件<%ROOT%>\src\Compilers\CSharp\Portable\Binder\Binder_Statements.cs,在方法BindInferredVariableInitializer的第 702 行添加以下 if 语句:

if (expression.Kind == BoundKind.BinaryOperator)
{
  var binaryOperation = (BoundBinaryOperator)expression;
  if (!binaryOperation.Left.GetConversion().IsIdentity || !binaryOperation.Right.GetConversion().IsIdentity)
  {
    // Use an explicit type for declaration as the initializer type '{0}' is 
    //not apparent due to conversions.
    Error(diagnostics, ErrorCode.WRN_ImplicitlyTypedVariableNotRecommended, errorSyntax, expression.Display);
  }
}

  1. 打开源文件<%ROOT%>\src\Compilers\CSharp\Portable\Lowering\DiagnosticsPass_Warnings.cs,在方法IsSameLocalOrField的第 204 行添加以下 switch section:
case BoundKind.PropertyAccess:
  var prop1 = expr1 as BoundPropertyAccess;
  var prop2 = expr2 as BoundPropertyAccess;
  return prop1.PropertySymbol == prop2.PropertySymbol &&
  (prop1.PropertySymbol.IsStatic || IsSameLocalOrField(prop1.ReceiverOpt, prop2.ReceiverOpt));

  1. 构建解决方案。

  2. VisualStudio\VisualStudioSetup.Next.csproj设置为启动项目,然后按Ctrl + F5启动一个新的 VS 实例,使用本地构建的编译器和 IDE 工具集。

  3. 创建一个新的 C# 类库项目,例如ClassLibrary,并添加以下代码:

class Class
{
  int X { get; set; }

  void M(int x)
  {
    X = X;
    var y = x + "" ;
  }
}

  1. 验证新的警告CS0823CS1717出现在错误列表中,并在编辑器中出现波浪线:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/35dfbc71-878b-48b1-aae6-38ac3ec9356e.png

  1. 构建项目并验证构建输出也包含新的诊断信息。

您可以在github.com/mavasani/roslyn/commit/a155824a41150414966c6f03493b0bb05a45a59e查看为CS0823所做的所有源代码更改,以及github.com/mavasani/roslyn/commit/9f33d6809202d9b2b7ef5e0fa79df0b56ea46110CS1717所做的更改。

它是如何工作的…

语义分析,也称为上下文相关分析,是编译器构建过程中的一个步骤,通常在解析之后,从源代码中收集必要的语义信息。

C# 绑定器是编译器工具链的第二阶段,它操作于由解析器输出的语法树、节点、标记和 trivia,并根据 C# 语言规范分析代码的语义。此阶段生成绑定树并报告语义诊断。绑定树本质上是一个具有与树中每个节点相关联的丰富语义信息的抽象语法树。在 CodeAnalysis 层提供的所有语义信息都来自与语法相关的绑定节点。绑定语句和表达式的入口点分别是Binder.BindStatementBinder.BindExpression

使用source.roslyn.io/进行 Roslyn 源代码的丰富语义搜索和导航。请注意,该网站上索引的源代码版本对应于 Roslyn 仓库的master分支的最新源代码,可能与Visual-Studio-2017标签的源代码不同。

在本配方中,我们向您展示了如何在绑定器中做出以下更改以添加新的诊断CS0823

  1. 将新的错误代码添加到ErrorCode枚举中。

  2. 为编译器的诊断消息添加新的资源字符串。

  3. 当初始化器是一个涉及非明显隐式转换的二进制表达式时,向方法添加一个新的语义诊断以绑定隐式变量初始化器。

C#本地重写或降低是编译器工具链的第三阶段,它将绑定树简化为非常简单的绑定节点。此外,它还执行流分析并报告流分析诊断(如不可达代码)。然后本地重写器的输出被馈送到代码生成器,该生成器为简化的绑定树生成 MSIL。

在本配方中,我们将现有的本地重写诊断传递扩展到报告自我赋值属性访问表达式的CS1717

在 C#编译器代码库中为新错误编写单元测试

本节将使您能够向 C#编译器添加单元测试。Roslyn.sln 中有以下一系列单元测试项目:

  • CSharpCompilerSyntaxTest:解析和语法错误的单元测试

  • CSharpCompilerSemanticTest:语义错误和语义模型 API 的单元测试

  • CSharpCompilerSymbolTest:编译器层定义的符号的单元测试

  • CSharpCommandLineTest:编译器的命令行选项的单元测试

  • CSharpCompilerEmitTest:代码生成阶段的单元测试,用于验证生成的 MSIL

在本节中,我们将为新增的语义错误向CSharpCompilerSemanticTest添加单元测试。

入门

您需要确保已执行本章中此前的配方,在 C#编译器代码库中实现新的语义错误,以向 C#编译器添加新的语义诊断:警告 CS0823:由于初始化器类型 '{0}' 由于转换而不明显,请使用显式类型进行声明

如何做到这一点…

  1. 在 VS2017 中打开 Roslyn 仓库根目录下的Roslyn.sln

  2. 打开源文件<%ROOT%>\src\Compilers\CSharp\Test\Semantic\Semantics\ImplicitlyTypedLocalsTests.cs

  3. 将以下新的单元测试添加到源文件中:

[Fact]
public void VarInferredTypeNotApparent()
{
  var source = @"
  class Class
  {
    void M(int x, string y)
    {
      var z = x + y;
    }
  }";

  CreateCompilationWithMscorlib(source).VerifyDiagnostics();
}

  1. 构建测试项目,并在命令行控制台中使用从项目的Debug属性页复制的命令行执行单元测试,为新增的单元测试添加-method开关:
*<%USERS_FOLDER%>*\.nuget\packages\xunit.runner.console\2.2.0-beta4-build3444\tools\xunit.console.x86.exe "*<%ROOT%>*\Binaries\Debug\UnitTests\CSharpCompilerSemanticTest\Roslyn.Compilers.CSharp.Semantic.UnitTests.dll" -html "C:\roslyn\Binaries\Debug\UnitTests\CSharpCompilerSemanticTest\xUnitResults\Roslyn.Compilers.CSharp.Semantic.UnitTests.html" -noshadow -method Microsoft.CodeAnalysis.CSharp.UnitTests.ImplicitlyTypedLocalTests.VarInferredTypeNotApparent

  1. 验证单元测试因缺少CS0823诊断而失败:
Expected:
Actual:
 // (6,7): warning CS0823: Use an explicit type for declaration as the initializer type 'string' is not apparent due to conversions
 // var z = x + y;
 Diagnostic(ErrorCode.WRN_ImplicitlyTypedVariableNotRecommended, "z = x + y").WithArguments("string").WithLocation(6, 7)

Diff:
 ++> Diagnostic(ErrorCode.WRN_ImplicitlyTypedVariableNotRecommended, "z = x + y").WithArguments("string").WithLocation(6, 7)

  1. 将缺失的诊断作为参数添加到我们的单元测试中的VerifyDiagnostics调用:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/bee7e297-96e7-4734-bb36-8237213514ec.png

  1. 通过重复步骤 4 重新执行单元测试,并验证测试现在是否通过。

如果您遇到 DirectoryNotFoundException,请确保测试结果目录存在于机器上:<%ROOT%>\Binaries\Debug\UnitTests\CSharpCompilerSemanticTest\xUnitResults

  1. 添加另一个单元测试以验证诊断不会在初始化二进制表达式没有隐式转换的情况下触发:
[Fact]
public void VarInferredTypeApparent_NoDiagnostic()
{
  var source = @"
  class Class
  {
    void M(int x, string y)
    {
      var z = (string)(x + y);
    }
  }";

  CreateCompilationWithMscorlib(source).VerifyDiagnostics();
}

  1. 执行新的单元测试并验证它是否通过。

您也可以使用测试资源管理器窗口在 Visual Studio 中执行单元测试,但由于整个解决方案中有数千个单元测试,因此对 Roslyn.sln 的测试发现相当慢。因此,您可能需要等待几分钟才能执行第一个单元测试。

使用 Roslyn 语法可视化器查看源文件的 Roslyn 语法标记和节点

语法可视化器是一个 Visual Studio 扩展,它简化了对 Roslyn 语法树的检查和探索,并在您使用 .NET 编译器平台 (Roslyn) API 开发自己的应用程序时可以作为调试辅助工具使用。

在本节中,我们将向您展示如何安装和使用 Roslyn 语法可视化器来查看 Visual Studio 中 C# 和 Visual Basic 源代码的语法树、节点和属性。您还可以查看与语法节点关联的语义,例如符号信息、类型信息和表达式的编译时常量值。

入门

您需要安装 .NET Compiler Platform SDK 来安装 Roslyn 语法可视化器。有关安装 SDK 的说明,请参阅第一章中的配方,“在 Visual Studio 中创建、调试和执行分析器项目”,编写诊断分析器

如何操作…

  1. 打开 Visual Studio,使用命令“查看 | 其他窗口 | 语法可视化器”启动 Roslyn 语法可视化器,并将其停靠在 Visual Studio 窗口的左侧。

  2. 创建一个新的 C# 类库项目,例如 ClassLibrary,并将以下方法添加到 Class1.cs 中:

public void Method()
{
  Console.WriteLine("Hello world!");
}

  1. 选择调用 Console.WriteLine("Hello world!") 的代码,并查看语法可视化器的层次树视图:CompilationUnit 包含一个 NamespaceDeclaration,它包含一个 ClassDeclaration,它包含一个具有 BlockMethodDeclaration,其第一个语句是一个 ExpressionStatement,该语句包含一个 InvocationExpression

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/19dcd0d3-2887-4c1d-8792-dc9e435c813a.png

  1. 在语法可视化器的 SyntaxTree 面板中右键单击 InvocationExpression 节点,然后单击“查看符号(如果有)”命令。

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/ddc2f5ea-28bd-4a91-8c5c-80ee77cd397c.png

  1. 您可以查看绑定到调用的 PE 元数据符号 System.Console.WriteLineProperties

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/9468dced-3cca-420f-b500-6e576a9057d2.png

  1. 将一个新的 VB 类库项目添加到解决方案中,例如 ClassLibrary1,并将以下方法添加到现有类中:
Public Sub Method()
  Console.Write("Hello World!")
End Sub

  1. 在编辑器中选择调用表达式 Console.Write("Hello World!"),你可以在 Syntax Visualizer 中查看 VB 代码的语法树、节点和属性:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/roslyn-cb/img/7ace532c-cde2-49ae-abdc-3a11eba9f7d8.png

你可以在 github.com/dotnet/roslyn/wiki/Syntax%20Visualizer 阅读关于 Syntax Visualizer 工具的更详细概述。

向 Roslyn 提交拉取请求以贡献下一个版本的 C# 编译器和 VS IDE

在本节中,我们将指导你完成发送拉取请求以贡献 Roslyn 编译器和 Visual Studio IDE 下一个版本的步骤。

入门

你需要确保你已经安装了 Git 工具,VS2017 带有 .NET 开发和 VS 扩展工作负载,并且已经注册并构建了 Roslyn 源代码。有关参考,请参阅本章开头的配方,设置 Roslyn 注册

如何做到这一点…

  1. 建议你在 github.com/dotnet/roslyn/issues 上为你的计划工作创建一个 Roslyn 问题,并在编码之前与 Roslyn 团队成员讨论,以避免任何不必要的或重复的工作。

  2. 在你的本地注册中做出你想要贡献给 Roslyn 代码库的源代码更改。例如,执行本章前面提到的配方,在 C# 编译器代码库中实现新的语义错误

  3. 为你的代码更改添加足够的单元测试。例如,执行本章前面提到的配方,为 C# 编译器代码库中的新错误编写单元测试

  4. 在仓库根目录下执行 Test.cmd 以构建和运行所有测试,以确认你的更改没有出现回归。有关参考,请参阅本章开头的配方,设置 Roslyn 注册

  5. 创建一个新的 Git 分支,添加并提交你的更改,然后将它们推送到远程仓库。有关 Git 帮助,请搜索 help.github.com/.

  6. 在发送拉取请求之前,请在 cla2.dotnetfoundation.org/ 上签署 .NET 贡献者许可协议CLA)。

  7. 按照以下步骤 help.github.com/articles/creating-a-pull-request/ 在你的分支上发起一个新的拉取请求。

  8. 在拉取请求的描述标签中填写拉取请求模板。你可以在创建拉取请求后编辑此信息。

  9. 在创建拉取请求后,添加一条新评论标记编译器或/和 IDE 团队以审查更改:

    • 编译器团队:@dotnet/roslyn-compiler

    • IDE 团队:@dotnet/roslyn-ide

  10. 根据审阅者的要求进行代码更改,并确保你的分支中没有合并冲突。

  11. 在你至少获得两个批准并且所有测试在拉取请求上通过后,你可以请求团队成员合并你的更改。

您可以在github.com/dotnet/roslyn/wiki/Contributing-Code阅读关于 Roslyn 仓库贡献代码的指南以获取更多详细信息。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐