使用主构造函数重构 C# 代码

作为 .NET 8 一部分的 C# 12 引入了一组引人注目的新功能!在这篇文章中,我们将探讨其中一个功能,特别是主构造函数,解释其用法和相关性。然后,我们将演示一个重构示例,以展示如何将其应用到您的代码中,并讨论其好处和潜在的缺陷。这将帮助您了解这一更改的影响并有助于您决定是否采用该功能。

作为 .NET 8 一部分的 C# 12

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12

主构造函数

主构造函数被认为是一项“C#日常”的开发人员功能。它们允许您在一个简洁的声明中定义类或结构及其构造函数。这可以帮助您减少需要编写的样板代码量。如果您一直在关注 C# 版本,您可能熟悉记录类型,其中包括主构造函数的第一个示例。

与记录类型的区别

记录类型作为类或结构的类型修饰符引入,这简化了构建简单类(如数据容器)的语法。记录可以包括主构造函数。该构造函数不仅生成一个支持字段,而且还为每个参数公开一个公共属性。与传统的类或结构类型不同,在传统的类或结构类型中,主构造函数参数可以在整个类定义中访问,而记录被设计为透明的数据容器。他们本质上支持基于值的相等,这与他们作为数据持有者的预期角色相一致。因此,它们的主构造函数参数可以作为属性访问是合乎逻辑的

重构示例

.NET 提供了许多模板,如果您曾经创建过 Worker Service,您可能见过以下 Worker 类模板代码:

namespace Example.Worker.Service{    public class Worker : BackgroundService    {        private readonly ILogger<Worker> _logger;
        public Worker(ILogger<Worker> logger)        {            _logger = logger;        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)        {            while (!stoppingToken.IsCancellationRequested)            {                if (_logger.IsEnabled(LogLevel.Information))                {                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);                }                await Task.Delay(1000, stoppingToken);            }        }    }}
前面的代码是一个简单的 Worker 服务,每秒记录一条消息。目前,Worker 类有一个构造函数,它将 ILogger实例作为参数,并将其分配给相同类型的只读字段。此类型信息位于两个位置,即构造函数的定义中,以及字段本身。这是 C# 代码中的常见模式,但可以使用主构造函数进行简化。

值得一提的是,Visual Studio Code 中不提供此特定功能的重构工具,但您可以手动重构主构造函数。若要在 Visual Studio 中使用主构造函数重构此代码,可以使用“使用主构造函数(并删除字段)”重构选项。右键单击 Worker 构造函数,选择“快速操作和重构…”(或按 Ctrl + .),然后选择“使用主构造函数”(并删除字段)。

现在生成的代码类似于以下 C# 代码:

namespace Example.Worker.Service{    public class Worker(ILogger<Worker> logger) : BackgroundService    {        protected override async Task ExecuteAsync(CancellationToken stoppingToken)        {            while (!stoppingToken.IsCancellationRequested)            {                if (logger.IsEnabled(LogLevel.Information))                {                    logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);                }                await Task.Delay(1000, stoppingToken);            }        }    }}
就这样,您已经成功重构了 Worker 类以使用主构造函数!ILogger字段已被删除,并且构造函数已替换为主构造函数。这使得代码更加简洁,更容易阅读。记录器实例现在在整个类中可用(因为它在范围内),而不需要单独的字段声明。
记录类型

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records

.NET 提供了许多模板

https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new

Worker Service

https://learn.microsoft.com/en-us/dotnet/core/extensions/workers

其他注意事项

主构造函数可以删除在构造函数中分配的手写字段声明,但需要注意。如果您将字段定义为只读,那么它们在功能上并不完全等效,因为非记录类型的主构造函数参数是可变的。因此,当您使用这种重构方法时,请注意您正在更改代码的语义。如果要保持只读行为,请就地使用字段声明并使用主构造函数参数分配该字段:

namespace Example.Worker.Service;
public class Worker(ILogger<Worker> logger) : BackgroundService{    private readonly ILogger<Worker> _logger = logger;
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)    {        while (!stoppingToken.IsCancellationRequested)        {            if (_logger.IsEnabled(LogLevel.Information))            {                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);            }            await Task.Delay(1000, stoppingToken);        }    }}
其他构造函数

当您定义主构造函数时,您仍然可以定义其他构造函数。然而,这些构造函数必须调用主构造函数。调用主构造函数可确保在类声明中的所有位置进行初始化主构造函数参数。如果需要定义其他构造函数,则必须使用 this 关键字调用主构造函数。

namespace Example.Worker.Service{    // Primary constructor    public class Worker(ILogger<Worker> logger) : BackgroundService    {        private readonly int _delayDuration = 1_000;
        // Secondary constructor, calling the primary constructor        public Worker(ILogger<Worker> logger, int delayDuration) : this(logger)        {            _delayDuration = delayDuration;        }
        // Omitted for brevity...    }}

并不总是需要额外的构造函数。让我们进行一些额外的重构以包含一些其他功能!

额外重构

主构造函数非常棒,但是我们还可以做更多事情来改进代码。

C# 包含文件范围的命名空间。它们是减少嵌套级别和提高可读性的重要功能。继续前面的示例,将光标放在命名空间名称的末尾,然后按 ; 键(Visual Studio Code 不支持此操作,但您可以手动执行此操作)。这会将命名空间转换为文件范围的命名空间。

经过一些额外的编辑,最终的重构代码如下:

namespace Example.Worker.Service;
public sealed class Worker(ILogger<Worker> logger) : BackgroundService{    protected override async Task ExecuteAsync(CancellationToken stoppingToken)    {        while (!stoppingToken.IsCancellationRequested)        {            if (logger.IsEnabled(LogLevel.Information))            {                logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);            }
            await Task.Delay(1_000, stoppingToken);        }    }}
除了重构文件范围的命名空间之外,我还添加了 seal 修饰符,因为在多种情况下都有性能上的优势。最后,我还使用数字分隔符功能更新了传递到 Task.Delay 的数字文字,以提高可读性。您知道还有很多方法可以简化您的代码吗?查看 C# 中的新增功能以了解更多信息!

数字分隔符功能

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types#integer-literals

C# 中的新增功能

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/89330.html

(0)
guozi的头像guozi
上一篇 2024年6月4日
下一篇 2024年6月4日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注