使用 C# 8 非空引用类型实现更安全的代码
什么是非空引用类型?
启用非空引用类型
这给我们带来了什么?
迁移到可空引用类型
结论
空引用异常是 .NET 应用程序中最常见的错误之一。尽管框架功能强大,但它的核心假设是引用类型可以指向 null,因此任何使用引用类型的代码都需要预先知道该对象不为 null,或者进行显式检查。
由于对象为空的情况通常很少发生,因此在开发甚至测试过程中很容易无法捕获此类错误。这意味着 .NET Framework 和 C# 语言的构建方式使得 bug 很容易隐藏在代码深处。
我们需要的是语言在这些问题发生之前告诉我们它们。
C# 8 的可空引用类型功能旨在解决此问题。
什么是非空引用类型?
首先声明一下——我们之前已经支持可空引用类型了。这就是问题所在。新特性实际上是我们必须使用的非空引用类型,但文档更倾向于将这些新特性称为可空引用类型。
可空引用类型功能会接受所有现有的引用类型,并在编辑器级别默认假定其为非空。存在风险的代码仍可编译,但对于初始化为空、可能为空等情况,您会收到警告。
因为 null 仍然是一个重要的概念,所以您仍然能够表示潜在的空类型,但您必须明确地说该类型可以为空,因为引用类型默认被视为非空。
如果您熟悉 TypeScript 中的可空性,这与TypeScript 的工作方式有点类似。
启用非空引用类型
要启用此功能,至少在撰写本文时,您必须手动编辑要启用该功能的每个项目的 .csproj 文件。您还需要使用 Visual Studio 2019 或 .NET Core 3.0 或更高版本。
具体来说,您需要向任何PropertyGroup
:LangVersion
和Nullable
:添加两个新属性
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>MattEland.SoftwareQualityTalk</AssemblyName>
<RootNamespace>MattEland.SoftwareQualityTalk</RootNamespace>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
LangVersion 告诉 C# 使用 C# 8 编译器。
Nullable 告诉 C# 假定所有声明的引用类型默认都是非空的。
这给我们带来了什么?
当您激活可空引用类型时,您将在 Visual Studio 中看到很多警告,表明各种值可能为空或返回空。
具体来说,你会看到很多类似这样的警告:
这指出了上述方法声明了它返回一个ResumeKeyword
返回类型。之前,默认情况下,返回类型为可空引用类型,现在除非另有说明,否则默认为非空。为了解决这个问题,我们将返回类型更改为 ,ResumeKeyword?
并?
指明该实例可能不存在。
正确的代码如下:
private static ResumeKeyword? FindKeyword(IDictionary<string, ResumeKeyword> keywordBonuses, string key)
{
if (keywordBonuses.ContainsKey(key))
{
return keywordBonuses[key];
}
return null;
}
另一个你会看到大量警告的原因是非空属性和变量未初始化。为了解决这个问题,你可以为它们分配一个合适的初始值,或者使用我们?
上面用到的运算符更改它们的变量类型,以指示其值为空。
需要注意的是,非空引用类型不会从根本上改变生成的代码,因为它们只是为了方便开发。但它带来的附加价值以及代码的简洁性是巨大的,值得投入。
迁移到可空引用类型
首次打开可空引用类型时,您可能会在代码中看到大量类似上述的警告。
如果需要一点一点地迁移,则可以使用新的#nullable disable
预处理器指令恢复代码某些部分的旧行为,如下例所示:
#nullable disable
public class KeywordData
{
public int Id { get; set; }
public string Keyword { get; set; }
public int Modifier { get; set; }
}
#nullable restore
或者(这是微软推荐的方法),除非你已经迁移过来支持可空引用类型的类,否则你不能在项目中启用它们。在这种情况下,你可以使用#nullable enable
而不是disable
。这有助于逐步迁移代码,但代价是你的整个代码中可能会出现大量的可空指令。
该代码如下所示:
#nullable enable
public class KeywordData
{
public int Id { get; set; }
public string? Keyword { get; set; }
public int Modifier { get; set; }
}
#nullable restore
有关升级现有代码以使用可空引用类型的更多全面信息,请参阅Microsoft 的升级指南。
结论
我强烈建议您小规模地尝试一下这个功能,看看效果如何。鼓励您明确代码行为,可以大大提升缺陷的消除潜力。
如果您想要一种更严格的方法来在编译器级别强制执行可空性,请阅读我关于使用 Language-Ext 库中的 Option 类在编译器级别捕获问题的文章,但请注意,语法比 C# 8 非空引用类型要麻烦得多。
如果您喜欢使用属性来注释参数和属性的可空性的想法,请查看我关于 JetBrains.Annotations 包的文章。
总的来说,我喜欢 C# 8 中非空引用类型的简洁性,我会继续使用它们,直到找到更好的方案。如果您想了解更多信息,请阅读官方文档。
如果您发现其他更适合您构建高质量软件的方法,我很乐意听到,因为我一直在寻找更好的方法来消除所有类型的软件缺陷。
文章来源:https://dev.to/integerman/safer-code-with-c-8-non-null-reference-types-4f2c