.NET10之C# 中的is null深入理解

== nullis null 的演进

在 C# 编程中,null 检查是日常开发中最常见的操作之一。从 C# 1.0 到 C# 6.0,开发者们一直使用 == null 进行 null 检查,这种方式简单直观,但在某些场景下存在潜在问题。C# 7.0 引入了模式匹配(Pattern Matching)特性,其中 is null 作为一种新的 null 检查方式应运而生。随后在 C# 9.0 中,又引入了 is not null 语法,进一步完善了 null 检查的模式匹配体系。

本文将深入剖析 is null 的设计原理、底层实现、与传统 == null 的核心差异,以及在不同场景下的最佳实践,帮助你全面掌握这一现代 C# 特性。


一、is null 的本质:模式匹配中的 null 模式

1.1 模式匹配基础

模式匹配是 C# 7.0 引入的核心语言特性,它允许开发者基于值的形状、类型或其他属性来检查值并提取信息。is null 是模式匹配中的常量模式(Constant Pattern) 特例,专门用于检查值是否为 null 引用或 null 值。

1.2 语法与基本用法

is null 的基本语法非常简洁:

csharp 复制代码
if (variable is null)
{
    // 处理 null 情况
}

对于可空值类型,is null 同样适用:

csharp 复制代码
int? nullableInt = null;
if (nullableInt is null)
{
    Console.WriteLine("nullableInt is null");
}

C# 9.0 进一步引入了 is not null 语法,用于检查非 null 情况:

csharp 复制代码
if (variable is not null)
{
    // 处理非 null 情况
}

二、is null== null 的核心差异

2.1 运算符重载的影响(最关键差异)

这是两种写法最本质的区别,也是微软官方推荐使用 is null 的核心原因。

写法 运算符重载行为 底层实现
variable == null 可能调用用户自定义的 == 运算符 依赖类型的 == 实现,可能是值比较或引用比较
variable is null 编译器保证不调用任何用户重载的 ==!= 运算符 等价于 object.ReferenceEquals(variable, null),直接比较内存地址

示例:重载 == 运算符时的行为差异

csharp 复制代码
public class CustomType
{
    public int Id { get; }

    public CustomType(int id) => Id = id;

    // 自定义 == 运算符,让所有实例都"等于"null
    public static bool operator ==(CustomType a, CustomType b) => true;
    public static bool operator !=(CustomType a, CustomType b) => false;
    
    public override bool Equals(object obj) => true;
    public override int GetHashCode() => 0;
}

// 测试代码
var instance = new CustomType(1);
Console.WriteLine(instance == null); // 输出 true(调用自定义 == 运算符)
Console.WriteLine(instance is null); // 输出 false(执行引用相等性检查)

在这个例子中,== null 的结果被自定义运算符扭曲,而 is null 始终保持正确的 null 检查行为,不受类型实现影响。

2.2 可空值类型处理

对于可空值类型(如 int?string?),两种写法都能正确处理,但 is null 提供了更统一的语法体验:

csharp 复制代码
int? num1 = null;
int? num2 = 42;

// == null 方式
Console.WriteLine(num1 == null); // true
Console.WriteLine(num2 == null); // false

// is null 方式
Console.WriteLine(num1 is null); // true
Console.WriteLine(num2 is null); // false

2.3 与模式匹配的集成

is null 最大的优势之一是与其他模式匹配特性的无缝集成,这是 == null 无法比拟的:

csharp 复制代码
// 结合类型模式进行 null 检查和类型转换(C# 7.0+)
if (input is string str && str.Length > 0)
{
    Console.WriteLine($"Valid string: {str}");
}

// 结合属性模式(C# 8.0+)
if (input is Person { Name: not null } person)
{
    Console.WriteLine($"Person name: {person.Name}");
}

// 结合 switch 表达式(C# 8.0+)
var result = input switch
{
    null => "Value is null",
    int i => $"Integer: {i}",
    string s => $"String: {s}",
    _ => "Unknown type"
};

三、is null 的底层实现与性能分析

3.1 编译期处理

C# 编译器对 is null 有特殊处理,确保其行为不受用户代码影响:

  1. 对于引用类型,is null 编译为 object.ReferenceEquals(variable, null)
  2. 对于可空值类型,is null 编译为检查 HasValue 属性是否为 false
  3. 对于非可空值类型,is null 编译为常量 false,并给出编译警告

3.2 IL 代码对比

让我们通过一个简单的例子对比 is null== null 的 IL 代码:

C# 代码

csharp 复制代码
public void CheckNull(string input)
{
    if (input == null) { }
    if (input is null) { }
}

IL 代码(简化版)

il 复制代码
// input == null
ldarg.1      // 加载 input
ldnull       // 加载 null
ceq          // 比较相等(可能调用重载的 ==)
brfalse.s    // 如果不等,跳转到下一个检查

// input is null
ldarg.1      // 加载 input
ldnull       // 加载 null
call bool [System.Runtime]System.Object::ReferenceEquals(object, object) // 直接调用 ReferenceEquals
brfalse.s    // 如果不等,跳转到方法结束

可以看到,is null 直接调用 object.ReferenceEquals,而 == null 使用 ceq 指令,可能被重载的 == 运算符影响。

3.3 性能考量

在大多数场景下,is null== null 的性能差异可以忽略不计,因为 JIT 编译器会进行优化。但在以下两种情况下,is null 可能更有优势:

  1. 类型重载了 == 运算符is null 避免了方法调用,直接进行引用比较,性能略优
  2. 模式匹配组合场景is null 可以与其他模式组合使用,减少代码行数,提高可读性,间接提升开发效率

四、is not null 与其他非 null 检查方式对比

C# 9.0 引入的 is not null 为非 null 检查提供了更流畅的语法,以下是几种常见非 null 检查方式的对比:

写法 适用版本 运算符重载影响 可读性 推荐指数
if (variable != null) 所有版本 可能调用重载的 != 运算符 ⭐⭐⭐ ⭐⭐⭐
if (!(variable is null)) C# 7.0+ 不受重载影响 ⭐⭐ ⭐⭐⭐⭐
if (variable is not null) C# 9.0+ 不受重载影响 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
if (variable is { }) C# 8.0+ 不受重载影响 ⭐⭐⭐⭐ ⭐⭐⭐⭐

is not null 的优势

  1. 语法简洁直观,符合自然语言表达习惯
  2. 不受运算符重载影响,行为更可靠
  3. 可与其他模式组合使用,如 if (input is not null and string str)

五、微软官方建议与代码分析规则

5.1 IDE0041 代码分析规则

微软在 .NET 代码分析器中提供了 IDE0041 规则,明确推荐使用 is null 进行 null 检查,而非 == nullobject.ReferenceEquals

该规则的默认配置为:

json 复制代码
{
  "dotnet_style_prefer_is_null_check_over_reference_equality_method": true,
  "dotnet_style_prefer_is_null_check_over_equality_operator": true
}

5.2 官方文档明确说明

微软官方文档指出:

编译器保证在评估 x is null 时,不会调用任何用户重载的相等运算符 ==。这使得 is null 成为检查 null 引用的可靠方式,不受类型实现的影响。

5.3 适用场景优先级

微软建议在以下场景优先使用 is null

  1. 框架/库代码:确保 null 检查行为不受用户类型重载影响
  2. 公共 API 边界:提供一致、可靠的 null 检查行为
  3. 模式匹配组合场景:与其他模式(如类型模式、属性模式)结合使用
  4. 可空引用类型(NRT)场景:与 C# 8.0+ 的可空引用类型特性更好地配合

六、迁移指南:从 == nullis null

6.1 安全迁移的场景

大多数情况下,你可以安全地将 == null 替换为 is null,包括:

  • 引用类型的 null 检查
  • 可空值类型的 null 检查
  • 不需要依赖 == 运算符重载行为的场景

6.2 需要谨慎的场景

以下场景中,不应盲目迁移:

  1. 依赖自定义 == 运算符行为的代码(罕见情况)
  2. 非可空值类型 的 null 检查(虽然两种写法都会给出警告,但 is null 更明确)

6.3 迁移步骤

  1. 启用 IDE0041 规则 :在项目的 .editorconfig 文件中配置相关规则
  2. 使用 Visual Studio 重构工具 :右键点击 == null 表达式,选择"快速操作和重构"→"使用 'is null' 检查"
  3. 手动检查特殊情况 :对于重载了 == 运算符的类型,确认迁移后行为是否符合预期
  4. 测试验证:确保迁移后的代码通过所有单元测试

6.4 迁移示例

重构前

csharp 复制代码
public string GetName(Person person)
{
    if (person == null)
        throw new ArgumentNullException(nameof(person));
    
    return person.Name;
}

重构后

csharp 复制代码
public string GetName(Person person)
{
    if (person is null)
        throw new ArgumentNullException(nameof(person));
    
    return person.Name;
}

七、高级用法:is null 与模式匹配的深度集成

7.1 结合类型模式进行 null 检查与转换

C# 7.0+ 允许在 is 表达式中同时进行 null 检查和类型转换,这是 == null 无法实现的:

csharp 复制代码
// 安全地检查并转换为 string 类型
if (input is string str && str is not null && str.Length > 0)
{
    Console.WriteLine($"Valid string: {str}");
}

// C# 9.0+ 更简洁的写法
if (input is string { Length: > 0 } str)
{
    Console.WriteLine($"Valid string: {str}");
}

7.2 在 switch 语句/表达式中使用

is null 可以自然地与 switch 语句/表达式结合,实现更清晰的分支逻辑:

csharp 复制代码
// switch 语句
switch (input)
{
    case null:
        Console.WriteLine("Input is null");
        break;
    case int i:
        Console.WriteLine($"Integer: {i}");
        break;
    case string s when s.Length > 0:
        Console.WriteLine($"Non-empty string: {s}");
        break;
    default:
        Console.WriteLine("Unknown type");
        break;
}

// C# 8.0+ switch 表达式
var description = input switch
{
    null => "Null value",
    int i => $"Integer: {i}",
    string s => s.Length > 0 ? $"Non-empty string: {s}" : "Empty string",
    _ => "Unknown type"
};

7.3 与可空引用类型的配合

C# 8.0 引入的可空引用类型(Nullable Reference Types, NRT)特性与 is null 完美配合,提供更强大的静态代码分析:

csharp 复制代码
#nullable enable

public void Process(string? input)
{
    if (input is not null)
    {
        // 在此块中,input 被编译器视为非 null 引用
        Console.WriteLine(input.Length); // 无警告
    }
    
    Console.WriteLine(input.Length); // 编译警告:可能为 null
}

八、常见误区与最佳实践

8.1 常见误区

  1. 认为 is null== null 完全等价 :在类型重载 == 运算符时,两者行为可能完全不同
  2. 在非可空值类型上使用 is null :虽然合法,但会给出编译警告,且结果恒为 false
  3. 过度使用 is null 而忽略其他模式is { } 有时更适合非 null 检查,尤其是需要同时创建变量时

8.2 最佳实践

  1. 优先使用 is nullis not null:遵循微软官方建议,提高代码的可靠性和一致性
  2. 结合模式匹配简化代码 :在需要同时进行类型检查和 null 检查时,使用 is Type variable 语法
  3. 在框架/库代码中强制使用 is null:确保 null 检查行为不受用户类型影响
  4. 使用 is not null 替代 !(is null) :C# 9.0+ 中,is not null 语法更简洁、可读性更高
  5. 与可空引用类型特性配合使用 :启用 NRT 后,is null/is not null 能帮助编译器提供更准确的 null 分析

九、总结:为什么 is null 是现代 C# 的首选

is null 不仅仅是一种语法糖,它代表了 C# 语言向更安全、更具表达力方向的演进。相比传统的 == nullis null 具有以下核心优势:

  1. 行为可靠 :不受类型重载的 == 运算符影响,始终执行引用相等性检查
  2. 语法统一:与模式匹配特性无缝集成,支持更复杂的检查逻辑
  3. 官方推荐 :微软通过 IDE0041 规则明确推荐使用 is null
  4. 未来兼容 :与 C# 后续版本的新特性(如 is not null、属性模式等)更好地配合

在 C# 7.0+ 的项目中,我们建议全面采用 is null 进行 null 检查,让代码更具现代感、更可靠、更易维护。

相关推荐
小鸡食米2 小时前
Linux-例行性工作+时间服务器
linux·服务器·网络
bjzhang752 小时前
FastReport——一个面向.NET生态的开源报表引擎
.net·fastreport
大新软件技术部2 小时前
Linux 服务器下dotnetcore 程序监控
linux·运维·服务器
程序猿编码2 小时前
Linux内核级隐身术:进程与端口隐藏技术剖析
linux·运维·服务器·linux内核·进程
jiayi_19992 小时前
[bug] unsupported GNU version! gcc versions later than 12 are not supported!
服务器·bug·gnu
龙侠九重天3 小时前
C# 机器学习数据处理
开发语言·人工智能·机器学习·ai·c#
安审若无12 小时前
运维知识框架
运维·服务器
Arvin62715 小时前
Nginx 添加账号密码访问验证
运维·服务器·nginx
筱璦16 小时前
期货软件开发 - C# 调用 HQChart 指标计算 C++ 动态库
c++·microsoft·c#