ZetCode

C# 可空类型

最后修改于 2025 年 4 月 30 日

本 C# 教程提供了一个关于使用可空类型的全面指南,解释了如何在语言中有效地处理 null 值。

null 关键字表示一种独特的数据类型,用于表示缺失或不存在的值。当取消引用一个 null 对象引用时,会抛出一个 NullReferenceException 异常。为了最大限度地减少这些异常,C# 在增强 null 安全性方面取得了重大进展。

C# 区分两种基本数据类型:值类型引用类型。值类型(如整数和布尔值)不能包含 null 值。相反,除非显式初始化,否则引用类型默认为 null 引用。

随着时间的推移,C# 引入了一系列功能来更有效地管理 null 值。 null 安全性 的概念包括旨在减少 NullReferenceException 发生的可能性并使代码更安全、更可预测的工具和实践。

从 C# 2.0 开始,该语言引入了可空值类型,允许值类型使用 T? 语法来保存 null 值。 这种增强扩展了值类型在需要显式表示缺少值的场景中的灵活性。

在 C# 8.0 中,引入了可空引用类型。使用此功能,开发人员可以指定引用类型是否可以保存 null 值,或者是否必须始终为非 null。 相同的 T? 语法用于声明可空引用类型,使开发人员能够清楚地表达他们的意图并提高代码安全性。

可空上下文

可空上下文可以对编译器解释引用类型变量的方式进行细粒度控制。 从 .NET 6 开始,默认情况下,新项目中启用了 null 状态分析和变量注解。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

在项目级别,可空上下文使用 Nullable 标签设置。

#nullable enable

可以使用编译器指令将可空上下文限定为 C# 文件。

C# 可空值类型

可空值类型,表示为 T?,通过包含一个额外的 null 值来扩展其底层值类型 T 的功能。 例如,bool? 变量可以保存三个值之一:truefalsenull,使其非常适合需要显式表示值的存在或不存在的场景。

语法 T?System.Nullable<T> 结构的简写。 此结构提供两个重要的成员

注意: 在数据库或 API 调用等外部源可能返回 null 值的场景中,可空值类型至关重要。 它们有助于弥合缺失数据和严格类型安全之间的差距。

可空值类型遵循与常规值类型相同的赋值规则。 方法中的局部变量必须在使用前进行初始化,以确保代码执行的稳定性。 但是,如果类中的字段未显式初始化,则默认为 null

C# 可空示例

在下一个示例中,我们将使用可空值类型。

Program.cs
Nullable<int> x = 0;
Console.WriteLine(x);

int? y = 0;
Console.WriteLine(y);

Console.WriteLine("-------------------------");

x = null;

if (x == null)
{
    Console.WriteLine("null value");
}

y = null;

if (y == null)
{
   Console.WriteLine("null value");
}

在该示例中,我们创建了两个可空整数变量。 首先将它们初始化为零,然后将它们赋值为 null

$ dotnet run
0
0
-------------------------
null value
null value

C# 检查 null

我们可以使用 !=is not 运算符来检查 null 值。

Program.cs
string?[] words = { "sky", "black", "rock", null, "rain", "cup" };

foreach (var word in words)
{
    if (word != null)
    {
        Console.WriteLine($"{word} has {word.Length} letters");
    }
}

Console.WriteLine("-------------------------");

foreach (var word in words)
{
    if (word is not null)
    {
        Console.WriteLine($"{word.ToUpper()}");
    }
}

我们有一个单词列表。 为了防止 NullReferenceExceptions,我们确保每个值在调用元素的属性或方法之前都不是 null。


通过 NullableHasValue 属性,我们检查当前可空元素是否具有其底层类型的有效值。

Program.cs
int?[] vals = { 1, 2, 3, null, 4, 5 };

int sum = 0;

foreach (var e in vals)
{
    if (e.HasValue)
    {
        sum += e.Value;
    }
}

Console.WriteLine($"The sum is: {sum}");

在该示例中,我们计算整数值的总和。

if (e.HasValue)
{
    sum += e.Value;
}

我们使用 HasValue 检查元素是否具有某个值,并通过 Value 解包该值。

C# null 条件运算符

null 条件运算符 仅当其操作数求值为非 null 时才对其应用成员访问 ?. 或元素访问 ?[] 操作。 如果操作数的计算结果为 null,则应用运算符的结果为 null。

Program.cs
var users = new List<User>
{
    new User("John Doe", "gardener"),
    new User(null, null),
    new User("Lucia Newton", "teacher")
};

users.ForEach(user => Console.WriteLine(user.Name?.ToUpper()));

record User(string? Name, string? Occupation);

在该示例中,我们有一个具有两个成员的 User 记录:NameOccupation。 借助 ?. 运算符访问对象的 name 成员。

users.ForEach(user => Console.WriteLine(user.Name?.ToUpper()));

我们使用 ?. 访问 Name 成员并调用 ToUpper 方法。 ?. 通过不调用 null 值上的 ToUpper 来防止 System.NullReferenceException


?[] 运算符允许将 null 值放入集合中。

Program.cs
int?[] vals = { 1, 2, 3, null, 4, 5 };

int i = 0;

while (i < vals.Length)
{
    Console.WriteLine(vals[i]?.GetType());
    i++;
}

我们在数组中有一个 null 值。 我们通过在数组元素上应用 ?. 运算符来防止 System.NullReferenceException

C# ArgumentNullException

当将 null 引用传递给不接受它作为有效参数的方法时,会抛出 ArgumentNullException

Program.cs
var words = new List<string?> { "falcon", null, "water", "war", "fly",
    "wood", "forest", "cloud", "wrath" };

foreach (var word in words)
{
    int n = 0;

    try
    {
        n = CountLtr(word, 'w');
        Console.WriteLine($"{word}: {n}");
    }
    catch (ArgumentNullException e)
    {
        Console.WriteLine($"{e.Message}");
    }
}

int CountLtr(string? word, char c)
{
    // if (word is null)
    // {
    //     throw new ArgumentNullException(nameof(word));
    // }

    ArgumentNullException.ThrowIfNull(word);

    return word.Count(e => e.Equals(c));
}

在该示例中,我们抛出并捕获 ArgumentNullException

$ dotnet run 
falcon: 0
Value cannot be null. (Parameter 'word')
water: 1
war: 1
fly: 0
wood: 1
forest: 0
cloud: 0
wrath: 1

C# null 宽恕运算符

在启用的可空上下文中,null 宽恕运算符 (!) 会抑制编译器警告。 该运算符在运行时无效。 它只会影响编译器的静态流分析。

以下示例使用 Playwright 库; 有关更多信息,请查看 Playwright 教程

Program.cs
using Microsoft.Playwright;
using System.Text;

using var pw = await Playwright.CreateAsync();
await using var browser = await pw.Chromium.LaunchAsync();

var page = await browser.NewPageAsync();

var ehds = new Dictionary<string, string>{ {"User-Agent", "C# program" } };
await page.SetExtraHTTPHeadersAsync(ehds);

var resp = await page.GotoAsync("http://webcode.me/ua.php");
var body = await resp!.BodyAsync();

Console.WriteLine(Encoding.UTF8.GetString(body));

在该示例中,我们请求一个返回包含用户年龄的响应的资源。

var resp = await page.GotoAsync("http://webcode.me/ua.php");
var body = await resp!.BodyAsync();

当网站关闭或我们遇到连接问题时,响应可能为 null。 但我们明确表示不必担心这一点。

C# null 合并运算符

如果 null 合并运算符 ?? 的左侧操作数不为 null,则返回该操作数的值; 否则,它将计算右侧操作数并返回其结果。

Program.cs
var words = new List<string?> { "work", "falcon", null, "cloud", "forest" };

foreach (var word in words)
{
    var e = word ?? "null value";
    Console.WriteLine(e);
}

我们使用 ?? 运算符来打印“null 值”而不是默认的空字符串。

C# null 合并赋值运算符

仅当 null 合并赋值运算符 ??= 的左侧操作数的计算结果为 null 时,才将其右侧操作数的值赋给其左侧操作数。

Program.cs
List<int>? vals = null;

vals ??= new List<int> { 1, 2, 3, 4, 5 };

// if (vals == null)
// {
//     vals = new List<int> { 1, 2, 3, 4, 5 };
// }

vals.Add(6);
vals.Add(7);
vals.Add(8);

Console.WriteLine(string.Join(", ", vals));

vals ??= new List<int> { 1, 2, 3, 4, 5 };

Console.WriteLine(string.Join(", ", vals));

我们在整数值列表上使用 null 合并赋值运算符。 注释掉了不使用该运算符的等效代码。

List<int>? vals = null;

首先,将列表赋值为 null

vals ??= new List<int> { 1, 2, 3, 4, 5 };

我们使用 ??= 将新的列表对象赋值给变量。 由于它是 null,因此分配了列表。

vals.Add(6);
vals.Add(7);
vals.Add(8);

Console.WriteLine(string.Join(", ", vals));

我们将三个值添加到列表中并打印其内容。

vals ??= new List<int> { 1, 2, 3, 4, 5 };

我们尝试将新的列表对象赋值给变量。 由于该变量不再是 null,因此未分配列表。

$ dotnet run
1, 2, 3, 4, 5, 6, 7, 8
1, 2, 3, 4, 5, 6, 7, 8

C# string.IsNullOrWhiteSpace

IsNullOrWhiteSpace 方法指示指定的字符串是否为 null、空或仅包含空白字符。

Program.cs
var words = new List<string?> { "\t", "falcon", "  ", null, "\n", "cloud", "" };

for (int i = 0; i < words.Count; i++)
{
    var e = words[i];

    if (string.IsNullOrWhiteSpace(e))
    {
        Console.WriteLine($"{i}: null or white space");
    } else
    {
        Console.WriteLine($"{i}: {e}");
    }
}

在该示例中,我们在可空字符串元素列表上使用 IsNullOrWhiteSpace 方法。

$ dotnet run
0: null or white space
1: falcon
2: null or white space
3: null or white space
4: null or white space
5: cloud
6: null or white space

C# string.IsNullOrEmpty

IsNullOrEmpty 方法指示指定的字符串是否为 null 或空字符串 ("")。

Program.cs
var words = new List<string?> { "\t", "falcon", "  ", null, "\n", "cloud", "" };

for (int i = 0; i < words.Count; i++)
{
    var e = words[i];

    if (string.IsNullOrEmpty(e))
    {
        Console.WriteLine($"{i}: null or empty");
    } else
    {
        Console.WriteLine($"{i}: {e}");
    }
}

在该示例中,我们在可空字符串元素列表上使用 IsNullOrEmpty 方法。

$ dotnet run
0:
1: falcon
2:
3: null or empty
4:

5: cloud
6: null or empty

来源

可空值类型

在本文中,我们使用了 C# 中的可空类型。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程