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? 变量可以保存三个值之一:true、false 或 null,使其非常适合需要显式表示值的存在或不存在的场景。
语法 T? 是 System.Nullable<T> 结构的简写。 此结构提供两个重要的成员
HasValue:指示当前System.Nullable<T>是否包含其底层类型的有效值。Value:检索存储在可空类型中的值,如果值为null,则抛出异常。
null 值的场景中,可空值类型至关重要。 它们有助于弥合缺失数据和严格类型安全之间的差距。可空值类型遵循与常规值类型相同的赋值规则。 方法中的局部变量必须在使用前进行初始化,以确保代码执行的稳定性。 但是,如果类中的字段未显式初始化,则默认为 null。
C# 可空示例
在下一个示例中,我们将使用可空值类型。
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 值。
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。
通过 Nullable 的 HasValue 属性,我们检查当前可空元素是否具有其底层类型的有效值。
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。
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 记录:Name 和 Occupation。 借助 ?. 运算符访问对象的 name 成员。
users.ForEach(user => Console.WriteLine(user.Name?.ToUpper()));
我们使用 ?. 访问 Name 成员并调用 ToUpper 方法。 ?. 通过不调用 null 值上的 ToUpper 来防止 System.NullReferenceException。
?[] 运算符允许将 null 值放入集合中。
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。
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 教程。
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,则返回该操作数的值; 否则,它将计算右侧操作数并返回其结果。
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 时,才将其右侧操作数的值赋给其左侧操作数。
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、空或仅包含空白字符。
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 或空字符串 ("")。
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# 中的可空类型。
作者
列出所有 C# 教程。