.NET Tools How-To's

重构代码以使用新的 C# 语言功能

Read this post in other languages:

在使用任何语言时,向现代语言功能进行现代化改造或迁移有助于提高代码的可读性、效率和安全性。在这篇博文中,我们将研究对代码进行现代化改造的方式,从而优化代码。

利用现代语言功能

为何只为使代码现代化就要重构代码?倘若完好,何必修正?有时,人们会有充分的理由去升级或迁移到新版本的平台或语言。例如,语言逐渐演变并添加了功能,以便利用操作系统功能、设备和浏览器功能、云或其他技术。此外,语言更新还包括引入错误修正、更简洁的语法和语法糖,以及更高效的对象和数据处理方式

对代码进行现代化改造的重构可以通过提高可读性和可维护性来显著提高代码质量。这是因为随着语言的逐渐演变,新添加的语法往往会有助于减少您必须编写的代码行数并降低代码的复杂性。

ReSharper 和 Rider 等工具可以帮助您实现代码库的现代化。它们会随时更新以支持 .NET 语言的新功能,并且可以在有机会充分利用这些功能时提供建议。附带的好处是,这些建议有助于使您的团队不断更新自身的 C# 技能。

顶级语句

顶级语句在 C# 9 中引入,使您无需恪守显式定义命名空间或类的旧规即可立即开始编写代码。在引入顶级语句之前,即使是打印单个“Hello World”风格消息的最基础应用,也需要包含三层嵌套中括号才能支持一行实际运行代码。这样就会牵扯到大量不必要的样板代码。这真的有必要吗?

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, modernized world!");
        }
    }
}

不!我们完全没有必要使用任何额外的代码。借助顶级语句,我们可以删除 Hello World 应用中每一行多余的代码,而仅仅保留实际执行某些功能的那一行代码。

System.Console.WriteLine("Hello, modernized world!");

对于想要使用短名称的开发者,代码如下所示:

using static System.Console;
WriteLine("Hello, modernized world!");

使用新建项目模板(File | New Project(文件 | 新建项目))时,会默认使用顶级语句。要迁移代码以使用顶级语句,请删除命名空间和类声明以及 main 函数。只保留 main 函数的主体。删除代码很有趣,但很容易漏掉中括号,因此请在包含 Main 的类上使用 Alt+Enter 并选择 Convert to top-level code(转换为顶级代码)。就是这样!一切更新完毕。

Use top level statements in C#

顶级语句最初于 C# 9 中引入。

文件范围命名空间

命名空间是命名、组织和访问对象的绝佳方法,但您并不一定要始终使用它们。.NET 中常见的文件组织模式是每个文件使用一个类,但真的有必要将单个类包含在命名空间中吗?这就是文件范围命名空间的用武之地。就像使用顶级语句一样,您需要担心的缩进问题减少了一级。对于语法:在文件顶部声明一个命名空间并立即以分号结束,大功告成。无需中括号!如下所示:

namespace MealPlanner.Menu;

就是这样。这一行代码便可告知编译器将文件中的所有内容全部包含在 MealPlanner.Menu 命名空间中。要使用 Rider 转换为文件范围命名空间,只需在命名空间声明上按 Alt+Enter 并选择 To file-scoped namespaces(转换为文件范围命名空间)即可。选择文件范围命名空间应当仅应用于此文件还是升级整个解决方案。

文件范围命名空间在 C# 10 中首次亮相。

Init-only setter 和索引器

有时,您需要实例化一个对象,但其数据应当不可变(不可更改)。也许您具有 DTO 或表示数据库中表的对象。此类对象可能包含表和字段的名称,因此该对象应当不可变。无法从构造函数外部初始化这种只读数据。因此,在早期版本的 C# 中,您需要在构造函数中声明和初始化对象,并通过仅创建不带 setter 的 getter 来将属性设为只读,类似于以下代码:

class Column
{
    public Column(string ColumnName, 
string DataType, string IsPrimaryKey)
    {
        ColumnName = "Column1";
        DataType = "varchar(20)";
        IsPrimaryKey = false;
    }
    public string ColumnName { get; }
    public string DataType { get;  }
    public bool IsPrimaryKey { get;  }
}

var c = new Column();

然而,经典的实例化和初始化方式意味着数据必须从类内部进行设置,构造函数就是唯一位置。通常,需要从类的外部设置数据,尤其是在使用表示数据库结构的对象(如本例)时。借助 init-only 属性,我们可以创建具有在调用代码时会初始化的只读属性的对象。相比以往,这样更加灵活。

要对代码进行现代化改造以使用 init-only setter,请使用 getter 创建一个属性并对 setter 使用 init 关键字而非 set(或完全删除 set 语句)。您很可能会需要批量升级这些内容 – 那么请按 Alt+Enter 并选择 To init accessor(转换为初始化访问器),然后选择范围:文件、项目或解决方案。然后,使用者必须在创建类实例或出现编译器错误时初始化该类。请注意代码行数的减少。在下面的动画中,灰显的代码表示可以安全地删除。不再需要或使用这些内容。

Rider Converts a setter to an init only property
class Column
{
    public string ColumnName { get; init; }
    public string DataType { get; init; }
    public bool IsPrimaryKey { get; init; }
}

var c = new Column
{
    ColumnName = "First_Name"
};

Init-only 属性于 C# 9 中引入。

Record 和 Record Struct

Record 是一种可使整个对象不可变的方式,不同于在 init-only 属性中找到的单个属性。Record 不同于类,因为 record 类型会使用基于值的相等性。因此,需要对所有属性比较值的对象通常可以很好地迁移到 record 或 record struct。通常,在涉及到高并发和共享数据的程序中会出现可变类型相关问题。

Record 声明在语法上几乎与类完全相同 – 唯一区别是关键字不同。要更新旧代码,请将 class 关键字替换为 record,删除构造函数,并以内联方式初始化属性。一键即可完成所有操作:Alt+Enter(请确保文本光标位于类名上)。选择 To record(转换为记录)。Rider 会处理全部转换。现在,该类已重构为 record,请注意构造函数已灰显,以便转换为主构造函数(随后介绍)。

Convert a class to a record

C# 9 中引入了 record,C# 10 中引入了 record struct。

主构造函数

随着 C# 的逐渐演变,与以前相比,完成相同任务所需的代码已变得更少。这意味着有很多不必要的样板代码已派不上用场,将消失在人们的视野当中。这样很好,因为那种代码往往会造成妨碍并且并不会带来多少业务价值。

通过将关键字 class 更改为 record (或将 struct 更改为 record struct)从类迁移到 record 后,您就不再需要构造函数和 setter。此时,您可以直接将它删除。但如果您需要设置这些值,请使用主构造函数。

要迁移到主构造函数,请将文本光标放置在现有构造函数上,按 Alt+Enter 并选择适当的 Convert to primary constructor(转换为主构造函数)操作。选择要更新的内容:当前对象、当前文件、项目或解决方案。

Convert to primary constructor

主构造函数在 C# 9 中推出。

现代 Switch 表达式

Switch 语句可以根据一列值来评估表达式。stringint 和 enum 等类型是评估中通常会使用的类型,它们往往非常简单。但是,switch 表达式会根据一列基于模式匹配的候选表达式来评估表达式。它可以通过不同的方式评估更多表达式(例如使用 lambda),并且比以前更加灵活。并非所有 switch 语句都最适于作为 switch 表达式,但仅仅是因为一些开发者喜欢更短、更简洁的语法,便足够值得升级。

在文本光标位于 switch 关键字上时按 Alt+Enter 将 switch 语句转换为 switch 表达式,然后选择转换选项。

Upgrade from switch statement to switch expression

C# 8 中采取了这一转变,为我们带来了 switch 表达式。

可空引用类型

在包括早期版本 C# 在内的许多语言中,您都需要在代码中编写 null 检查。在许多圈子里,尽职尽责地检查 null 都会被认为是一种“优秀的防御性编程”。但这一行为已经可以退出舞台。在 2022 年的当下,编译器和工具已经可以轻松地为我们完成这些检查。编程语言应使开发者能够专注于解决业务问题,这是应用程序开发方面的共同主题。否则,开发者们需要花费大量时间来重新创建或修正常见的技术场景,包括耗时地进行本例中的 null 检查。

虽然在更新代码时有许多可以考虑的可空性检查方法,但有些快速修复会非常方便且易于上手。例如,您可能在重构期间对构造函数中的实参应用了 ? 字符以表示可空性,但忘记将其应用于相应的属性。不用担心,您会看到一个指示器,提醒您在编辑器中进行必要的更新。按 Alt+Enter 选择要执行的操作。在此示例中,将该属性设置为可空以完善构造函数的实参。

Top level statements

同时,请阅读有关迁移到可空引用类型它们的幕后运作方式以及如何升级代码以使用可空引用类型的更多内容。

可空引用类型始于 C# 9。

结论

这篇博文向您展示了如何借助诸如 ReSharper 和 Rider 等工具来重构代码以实现现代化。虽不必一直使用语言的最新版本,但有必要重构代码以使其更具可读性和效率。这意味着需要适时进行更新。优秀的工具可以帮助重构和更新旧代码、迁移和现代化代码库。请查看我们的 .NET 工具,并告诉我们您计划如何对代码进行现代化改造。

本博文英文原作者:

image description

Discover more