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# 教程。