C# 8 如何帮助提高软件质量

2025-06-07

C# 8 如何帮助提高软件质量

.NET Core 3 是 .NET 的一个重要里程碑,它带来了一些令人兴奋的新功能 - 最值得注意的是 C# 8 的到来。

我想就此版本提供我自己的看法,特别是 C# 8 如何帮助提高软件质量。

请注意,我所说的软件质量是指交付软件的整体质量,而不是与源代码的可维护性和可读性相关的代码质量。

C# 8.0

C# 8.0 现已随 Visual Studio 2019 推出,并可用于 .NET Core 3 和 .NET Standard 2.1 项目。

新功能集依赖于 .NET 运行时内部的变化,这意味着我们将在此讨论的新语言功能目前不适用于 .NET Framework 或 Visual Studio 2017 或更早版本。

所以,这是个坏消息。

好消息是,C# 8 是一个功能非常丰富的更新,它为软件开发的各个方面提供了一些非常令人兴奋的功能。

我不会讨论 C# 8 中的所有新功能,而是讨论能够提升软件质量的最重要的语言特性。具体来说,我将重点关注:

  • 可空引用类型
  • 空合并赋值
  • 只读属性
  • Switch 表达式和模式匹配

接下来,我们将研究它们各自是什么,以及它们可以做什么来提高软件质量。

可空引用类型

我个人不同意这个语言特性的名称,因为到目前为止,我们已经拥有的是可空引用类型。现在,我们得到的是默认为非空的引用类型。

让我解释一下。

在 C# 中,引用类型要么持有对某个对象的引用,要么就是null。这意味着,对于那些可以为 null 的对象,在尝试与其交互之前,你需要检查该对象是否存在。如果不存在,就会出现臭名昭著的NullReferenceException

C# 8.0 在这方面为您提供了一个新选项。您可以通过项目设置或预处理器语句在代码中启用非可空引用类型,Visual Studio 2019 将自动假定声明的引用类型永远不会为空,除非您通过添加?到类型声明中明确告知它。

让我们看一个简单的类:

#nullable enable
public class KeywordData
{
    public int Id { get; set; }
    public string  SomeNonNullValue { get; set; }
    public string? SomeNullableValue { get; set; }
}
#nullable restore
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,SomeNonNullValue假定永远不会包含空值,因为它缺少?语法,并且如果 Visual Studio 注意到您检查它是否为空或在其中设置它认为可能为空的内容,它会警告您。

相反,SomeNullableValue由于语法表明了这一点,因此被解释为可能具有空值?

存在预处理器语句的原因nullable是因为大多数使用遗留代码的人无法一次为整个代码库激活此功能,因此这些区域允许您根据需要启用或禁用该功能。


为什么这对质量很重要

此功能的影响是巨大的,因为它可以从代码中删除不必要的空检查,更重要的是,确保在开发时正确地检查是否为空。

这种新的语言特性提供了从代码中删除或大幅减少整个错误类别的可能性,这始终是一件好事。

我个人很喜欢这种新语法,并发现它与 TypeScript 类型定义之间的相似之处非常有吸引力。


如果您想了解有关这些入门的更多信息,请查看我关于 C# 8.0 中可空引用类型的深入文章

空合并赋值

这又是一个大问题。

合并是指将事物组合成一个整体。C# 已经支持多种空运算符,从简单的?.安全访问对象属性(这些属性可能为空也可能不为空)到??合并运算符(如果第一个属性为空,则使用另一个值)。

空合并赋值运算符??=通过解决特定场景,在这些运算符的基础上稍加构建。

假设您有一段代码,想要检查某个对象是否为空,如果为空,则需要为其分配一个值。

在正常的 C# 语法中,您可以执行以下操作:

public void MyMethod(string value) {
   if (value == null) {
      value = "Batman";
   }

   // Do something with value
}
Enter fullscreen mode Exit fullscreen mode

我们可以使用空合并赋值运算符将此代码简化为以下内容:

public void MyMethod(string value) {
   value ??= "Batman";

   // Do something with value
}
Enter fullscreen mode Exit fullscreen mode

为什么这对质量很重要

对于那些不熟悉的人来说(现在大多数人都是如此),这稍微损害了可读性??=,但这也使得参数默认值分配逻辑更加简洁,为方法的实际内容提供了更多的垂直空间。

此外,这可以防止您可能会看到的复制和粘贴错误,即检查一个变量是否为空,但在为空的情况下将值分配给另一个变量。

听起来有些牵强,但这种“复制代码块然后无法更改所有必要区域”的行为是许多系统中出现错误的常见原因。

只读成员

这个 readonly 和你想的不一样。当你想到 C# 中的 readonly 时,人们通常会想到将字段定义为 readonly 是为了获得少量的 CLR 性能提升以及代码可读性和安全性优势。

另一方面,只读成员是指被定义为不允许更改其关联实例状态的属性或方法。这听起来很像逻辑,而且大多数实现也都如此,但并不能保证只读成员不会修改其自身实例以外的其他对象的状态。

让我们看一下实际效果:

public int Foo {get; set;}

public readonly int CalculateFooPlusFortyTwo() => Foo + 42;
Enter fullscreen mode Exit fullscreen mode

这里我们将CalculateFooPlusFortyTwo方法定义为readonly,这样可以防止它在编译器级别修改任何实例状态。假设未来的某个程序员尝试将代码改为如下形式:

public int Foo {get; set;}

public readonly int CalculateFooPlusFortyTwo() {
   Foo += 42;
   return Foo;
}
Enter fullscreen mode Exit fullscreen mode

Foo在这个例子中,代码无法编译,因为当方法定义为时逻辑试图修改属性readonly


为什么这对质量很重要

只读之所以重要,主要有两个原因:

  1. 它可以防止您在意想不到的地方意外更改内部状态(例如大多数属性获取器)。
  2. 它更清晰地向其他代码传达您的代码意图。

说到第二点,我们来看一下这个DateTime.AddDays()方法。对于不熟悉的人来说,这个方法会获取 DateTime 实例的日期,添加 X 天,然后返回新的日期。许多初学者认为调用AddDays一个对象会将该对象的日期引用增加到新值,但这是一个常见的 .NET 陷阱

假设该AddDays方法被定义为readonly。在这种情况下,调用者会更清楚地知道该方法不会修改日期实例的内部状态,这对不熟悉该类行为的人来说是另一个线索。

Switch 表达式和模式匹配

最后我们来谈谈C# 8对switch语句的新改进。

对于大多数开发人员来说,switch 语句是相当标准的知识。

switch (entry.Name) {
  case "Bruce Wayne":
  case "Matt Eland":
     return Heroes.Batman;
  case "The Thing":
     if (entry.Source == "John Carpenter") {
       return Heroes.AntiHero;
     }
     return Heroes.ComicThing;
  case "Bruce Banner":
     return Heroes.TheHulk;
  // Many heroes omitted...
  default:
     return Heroes.NotAHero;
}
Enter fullscreen mode Exit fullscreen mode

问题在于,switch 语句中有很多重复的语法,让人感觉有点枯燥。此外,switch 语句有时会让你不得不在 case 语句中执行一些有趣的逻辑来消除歧义,就像我们在上例中的 Thing 条目中看到的那样。

C# 8 在 switch 语句中提供了更多的灵活性和简洁性。

例如,我可以将上面的例子重写为:

return entry.Name switch {
   "Bruce Wayne" => Heroes.Batman,
   "Matt Eland" => Heroes.Batman,
   "The Thing" when entry.Source == "John Carpenter" => Heroes.AntiHero,
   "The Thing" => Heroes.ComicThing,
   "Bruce Banner" => Heroes.TheHulk,
   _ => Heroes.NotAHero
};
Enter fullscreen mode Exit fullscreen mode

首先,这明显短多了。其次,通过条件逻辑可以when帮助我们更清晰地区分两种模棱两可的情况。

此外,其逻辑比上面展示的更加强大。假设你不需要知道类,而是需要切换到Object某个抽象类型。

请看下面的车辆定价示例:

VehicleBase myVehicle = GetVehicle();
return myVehicle switch {
   Tank { Movement = TankType.Treads } => 100000M,
   Tank => 75000M,
   RocketShip => 99999999M,
   Car { Color = Colors.Red } => 21999M,
   Car => 20000M,
   _ => 1000M
};
Enter fullscreen mode Exit fullscreen mode

这里我们实际上可以关闭具体对象及其属性。这相当强大,但逻辑却简洁。


为什么这对质量很重要

乍一看,您不会认为这些“语法糖”改进对实际软件质量有太大作用,但我认为,作为开发人员,我们经常低估样板逻辑和语法对软件质量和代码可维护性的影响。

让我这样说吧:如果我不必编写一行代码,那么我就不会在那一行上犯错误,直到投入生产时我才有机会发现它

通过使用切换语言语法和简洁性,我依靠 .NET 为我进行转换和匹配,而无需为此编写自定义逻辑(并且可能会出错)。

此外,通过将样板逻辑压缩到更小的代码区域,它会更加强调真正重要的程序逻辑,这意味着在代码审查和调试会话期间,我们会更加关注应用程序逻辑,从而增加发现这些块中的缺陷的几率。

结束语

这是 C# 8 中四个有趣的新功能。此外还有许多其他功能,但这四个功能共同通过识别代码中的潜在问题并将琐碎问题最小化到我们可以专注于有意义的事情的程度来帮助提高软件质量。

C# 8 中还有许多其他新功能。如果您想了解更多信息,我建议您阅读 Microsoft 的C# 8.0 中的新功能文章

您最想使用哪些功能?发现哪些功能有助于提高代码质量?

文章来源:https://dev.to/integerman/how-c-8-helps-software-quality-4ln
PREV
迁移到 TypeScript
NEXT
使用实体框架创建 ASP .NET Core 站点