C# 中对象相等性判断的全面解析

在 C# 中,"相等"并不是一个单一概念。根据类型、上下文和运算符/方法的不同,相等性判断可能表示引用相等值相等自定义语义相等

常见的四种判断方式如下:

  • a.Equals(b)
  • Object.Equals(a, b)
  • a == b
  • Object.ReferenceEquals(a, b)

它们在实现机制、空值处理、可重写性以及适用场景上均存在显著差异。本文将从 CLR 角度出发,对它们进行逐一剖析,并给出实践建议。


一、相等性的三种语义

在进入具体 API 之前,先明确三种核心语义:

语义 含义
引用相等(Reference Equality) 两个变量是否指向同一个对象实例
值相等(Value Equality) 两个对象的内容是否相同
语义相等(Semantic Equality) 按业务规则定义的"相等"

C# 的不同判断方式,本质上是在这三者之间切换。


二、Object.ReferenceEquals(a, b)

1. 定义

csharp 复制代码
public static bool ReferenceEquals(object? objA, object? objB);

2. 行为特征

  • 只判断引用是否相同
  • 不可重写
  • 不会调用 Equals
  • 不会触发运算符重载
  • 对值类型参数会发生装箱

3. 判定规则

情况 结果
两者引用同一实例 true
两者引用不同实例 false
两者均为 null true
一个为 null false

4. 示例

csharp 复制代码
string a = "hello";
string b = new string("hello".ToCharArray());

Object.ReferenceEquals(a, b); // false

5. 使用场景

  • 判断是否为同一实例
  • 底层框架、缓存、单例检测
  • 调试对象生命周期

⚠️ 不用于业务逻辑中的"相等"判断


三、a.Equals(b)

1. 定义来源

csharp 复制代码
public virtual bool Equals(object? obj);

该方法定义在 System.Object 中,可被重写

2. 调用机制

  • 实例方法

  • 调用的是 运行时类型Equals 实现

  • 可能是:

    • Object.Equals
    • 重写后的 Equals
    • 值类型的结构比较

3. 默认行为(未重写)

csharp 复制代码
public virtual bool Equals(object obj)
{
    return ReferenceEquals(this, obj);
}

即:默认等价于引用相等

4. 示例(值相等)

csharp 复制代码
public class Person
{
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        return obj is Person p && p.Name == Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}
csharp 复制代码
var p1 = new Person { Name = "Tom" };
var p2 = new Person { Name = "Tom" };

p1.Equals(p2); // true

5. 注意事项

  • anull,会抛出 NullReferenceException
  • 重写 Equals 必须同时重写 GetHashCode

四、Object.Equals(a, b)

1. 定义

csharp 复制代码
public static bool Equals(object? objA, object? objB);

2. 内部逻辑(简化)

csharp 复制代码
if (objA == objB) return true;
if (objA == null || objB == null) return false;
return objA.Equals(objB);

3. 核心特性

  • 空安全
  • 若两者非空,调用 objA.Equals(objB)
  • 尊重类型重写的 Equals

4. 示例

csharp 复制代码
Object.Equals(null, null); // true
Object.Equals(null, obj);  // false

5. 使用场景

  • 通用工具方法
  • 框架/基础库
  • 不确定是否为 null 的对象比较

✅ 推荐在公共代码中使用


五、a == b

1. 本质

  • 运算符

  • 行为取决于:

    • 是否为值类型
    • 是否重载 operator ==

2. 对引用类型(未重载)

csharp 复制代码
// 等价于 ReferenceEquals

3. 对值类型

  • 比较字段值(逐字段)
  • 不可为 null

4. 对重载的引用类型(如 string)

csharp 复制代码
string a = "hi";
string b = new string("hi".ToCharArray());

a == b; // true

string== 做了值相等重载。

5. 风险点

  • 同一表达式,不同类型行为不同
  • 重载不当会破坏直觉一致性

六、行为对比总表

判断方式 空安全 是否可重写 是否值比较 是否引用比较
ReferenceEquals
a.Equals(b) 取决于实现 取决于实现
Object.Equals(a,b) 取决于实现 取决于实现
a == b 取决于类型 ✔(运算符) 取决于类型 取决于类型

七、实践建议(最佳实践)

1. 业务对象

  • 重写 Equals + GetHashCode
  • 同时重载 ==!=
  • 保持三者语义一致

2. 框架/工具代码

  • 使用 Object.Equals(a, b)
  • 避免 a.Equals(b) 直接调用

3. 判断实例唯一性

  • 使用 ReferenceEquals

八、结语

C# 中的"相等"是一个多层次、多语义的问题

理解这四种方式的差异,不仅能避免隐藏 bug,还能写出更健壮、可维护的代码

相关推荐
寻星探路9 小时前
【Python 全栈测开之路】Python 基础语法精讲(三):函数、容器类型与文件处理
java·开发语言·c++·人工智能·python·ai·c#
逑之9 小时前
C语言笔记8:操作符
c语言·开发语言·笔记
无限进步_9 小时前
【C语言&数据结构】相同的树:深入理解二叉树的结构与值比较
c语言·开发语言·数据结构·c++·算法·github·visual studio
枫叶丹49 小时前
【Qt开发】Qt系统(五)-> Qt 多线程
c语言·开发语言·c++·qt
Larry_Yanan9 小时前
Qt多进程(九)命名管道FIFO
开发语言·c++·qt·学习·ui
聆风吟º9 小时前
【C++藏宝阁】C++入门:命名空间(namespace)详解
开发语言·c++·namespace·命名空间
潇潇云起9 小时前
mapdb
java·开发语言·数据结构·db
prettyxian9 小时前
【QT】信号与槽基础:手动连接的原理与实践
开发语言·qt
傻乐u兔9 小时前
C语言初阶————结构体
c语言·开发语言