当前位置: 首页 > news >正文

C# 中的 ReferenceEquals 方法 - 教程

C# 中的 ReferenceEquals 方法

1. 核心定义与作用

Object.ReferenceEquals 是一个静态方法,它的作用非常纯粹和单一:

判断两个对象引用是否指向内存中的同一个实例(即同一个对象)。

它的方法签名如下:

public static bool ReferenceEquals (object? objA, object? objB);

2. 工作原理与举例说明

让我们通过一系列例子来彻底理解它。

例 1:引用类型的基本行为
class Person
{
public string Name {
get;
set;
}
}
Person p1 = new Person { Name = "Alice"
};
Person p2 = p1;
// p2 是 p1 的引用副本,它们指向同一个对象
Person p3 = new Person { Name = "Alice"
};
// 新对象,内容虽然和 p1 一样,但内存地址不同
Console.WriteLine(Object.ReferenceEquals(p1, p2));
// 输出: True
Console.WriteLine(Object.ReferenceEquals(p1, p3));
// 输出: False
Console.WriteLine(Object.ReferenceEquals(null, null));
// 输出: True (特殊情况)

说明

  • p1p2 指向堆上的同一个 Person 实例,所以 ReferenceEquals 返回 true
  • p1p3 虽然内容相同,但分别是两个不同的对象实例,所以返回 false
  • 两个 null 引用被认为是相等的。
例 2:字符串的特殊情况 - 字符串驻留

字符串 (string) 在 C# 中是不可变的引用类型,但 CLR 使用了一种叫“字符串驻留”的优化技术,这会让 ReferenceEquals 的行为变得有趣。

string s1 = "Hello";
string s2 = "Hello";
// 编译器会进行驻留,s2 和 s1 指向同一个内存地址
string s3 = new string("Hello".ToCharArray());
// 强制在堆上创建一个新的字符串对象
Console.WriteLine(Object.ReferenceEquals(s1, s2));
// 输出: True (因为驻留)
Console.WriteLine(Object.ReferenceEquals(s1, s3));
// 输出: False (不同对象)
// 使用String.Intern方法将s3驻留,之后获取的引用就是驻留池中的引用
string s4 = String.Intern(s3);
Console.WriteLine(Object.ReferenceEquals(s1, s4));
// 输出: True

说明:对于字面量字符串,CLR 会将其放入“驻留池”,所有相同值的字面量都会共享同一个引用,所以 s1s2 是同一个引用。但用 new 等方式创建的字符串对象不会自动驻留。

例 3:值类型的比较 - 装箱

ReferenceEquals 的参数是 object,所以当传递值类型(如 int, struct)时,会发生装箱

int num1 = 10;
int num2 = 10;
// 值类型传递给ReferenceEquals时会被装箱
// num1被装箱到一个新的object实例中
// num2被装箱到另一个新的object实例中
// 两个不同的装箱对象,引用自然不同
Console.WriteLine(Object.ReferenceEquals(num1, num2));
// 输出: False
// 更明显的例子:和自己比较
Console.WriteLine(Object.ReferenceEquals(num1, num1));
// 输出: False 

这是最重要的陷阱!
Object.ReferenceEquals(num1, num1) 也返回 false,因为每次装箱都会产生一个新的临时对象。所以,ReferenceEquals 方法永远不适用于比较值类型,它的结果总是 false(除非比较 null)。

例 4:与 ==Equals 的对比
class Student
{
public string Id {
get;
set;
}
// 假设我们重写了Equals,只比较Id字段
public override bool Equals(object obj) => obj is Student s && Id == s.Id;
// 重写Equals最好也重写GetHashCode
public override int GetHashCode() => Id?.GetHashCode() ?? 0;
}
Student stu1 = new Student { Id = "001"
};
Student stu2 = new Student { Id = "001"
};
Student stu3 = stu1;
Console.WriteLine("ReferenceEquals:");
Console.WriteLine(Object.ReferenceEquals(stu1, stu2));
// False (不同对象)
Console.WriteLine(Object.ReferenceEquals(stu1, stu3));
// True (同一对象)
Console.WriteLine("== Operator:");
// == 默认行为与ReferenceEquals相同,除非被重写
// 假设我们没有重写 == 运算符,所以它执行引用比较
Console.WriteLine(stu1 == stu2);
// False
Console.WriteLine(stu1 == stu3);
// True
Console.WriteLine("Equals Method:");
// Equals 方法被我们重写了,它比较的是Id字段的值
Console.WriteLine(stu1.Equals(stu2));
// True (内容相同)
Console.WriteLine(stu1.Equals(stu3));
// True (内容相同,且是同一对象)

三者的区别总结

方法比较内容可被重写适用于值类型
ReferenceEquals引用地址永远不适用(因装箱)
== 运算符默认是引用地址,但可重写为比较值是(对于内置值类型已重写)
Equals 实例方法默认是引用地址,但通常被重写为比较值是(对于内置值类型已重写)

3. 主要使用场景

既然有 ==Equals,为什么还需要 ReferenceEquals

  1. 进行绝对的引用比较:当你明确地、故意地想知道两个变量是否指向内存中的绝对同一个实例,而不是“值”是否相等时。例如,在实现某些底层基础设施、缓存机制或监听对象身份变化的逻辑时。

  2. 避免被重写逻辑干扰==Equals 都可能被类重写。如果你不信任或不想依赖这些自定义的比较逻辑,ReferenceEquals 提供了一个不可被重写的、最基础的比较方式。

  3. 处理可能为 null 的对象:它是静态方法,即使参数为 null 也不会抛出异常,比直接使用 == 在某些复杂情况下更安全。

// 一个实用的例子:在实现Equals时,先进行引用比较以优化性能
public override bool Equals(object obj)
{
// 如果引用相同,肯定是同一个对象,无需继续比较字段
if (Object.ReferenceEquals(this, obj))
return true;
// 如果对方为null或类型不同,肯定不相等
if (obj is null || this.GetType() != obj.GetType())
return false;
// 最后再进行耗时的字段逐一比较
// ... 比较各个字段的值
}

总结

  • Object.ReferenceEquals 只检查引用是否相同,不检查值。
  • 永远不会用于值类型,因为装箱会产生临时对象,导致比较结果总是 false
  • 对于字符串,要小心字符串驻留带来的影响。
  • 它的主要用途是进行身份识别(Identity Check)而不是值相等性检查,常用于底层实现或需要绕过自定义相等性逻辑的场景。

简单来说,当你问“是同一个东西吗?”时,用 ReferenceEquals;当你问“看起来一样吗?”时,用 Equals==

http://www.hskmm.com/?act=detail&tid=14299

相关文章:

  • 遇到一件循环导入事件
  • flask实现后端接口的封装和开发部分
  • 上海这样的地段简直是逆天
  • 【GitHub每日速递 250923】 Google 又放大招!TimesFM 2.5 参数减半,预测更准更快
  • 具身智能机器人架构:人形机器人系统架构深度拆解
  • 卓驭,欧洲无绝境
  • 下周审核4家IPO,2家再融资。其中两家IPO企业于在审期间调减募资规模
  • 280亿国产AI独角兽,惹怒“地表最强法务部”
  • 读人形机器人20财富再分配
  • Java 与人工智能的深度融合:从数据到推理服务
  • Java 与大数据实时处理:Kafka、Flink 与企业应用
  • Java 与企业级中间件:消息、缓存与数据库集成
  • 基于 Vite7 与 Vue3 的 WebOS 后台系统架构实践
  • 啊哈哈20250923_03:23
  • js获取浏览器指纹
  • gitIgnore 无法忽略dist目录的变更
  • 翻转二叉树
  • 我的第一篇博客
  • 测试测试测试测试测试
  • java中的浮点数计算
  • XYCTF2025复现(WEB)
  • 洛谷 P13973 [VKOSHP 2024] Nightmare Sum
  • 发布/订阅(Publish/Subscribe)与交换机(Exchange)
  • 线性结构之链表
  • 二叉树最近公共祖先
  • AI 编程“效率幻觉”:为何你感觉快了,项目却慢了?
  • lc1033-移动石子直到连续
  • 一些正在制作的“格林达姆”测试项目,以及“假无损”
  • 个人项目
  • 北京 意大利学签 北京意大利签证中心 贵宾 vip vfs