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

C# 中的 ReferenceEquals 方法

C# 中的 ReferenceEquals 方法

1. 核心定义与作用

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

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

  • 返回值bool 类型。如果两个引用指向同一个对象,返回 true;否则返回 false
  • 它不关心:对象的内容是什么、对象的类型是否相同、== 运算符或 Equals 方法是如何被重写的。它只进行引用同一性的比较。

它的方法签名如下:

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最好也重写GetHashCodepublic 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); // TrueConsole.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.dtcms.com/a/393598.html

相关文章:

  • BERT:用于语言理解的深度双向Transformer预训练【简单分析】
  • 力扣hot100:两数相加(模拟竖式加法详解)(2)
  • Zotero + Word 插件管理参考文献的引用
  • 用Python一键整理文件:自动分类DOCX与PDF,告别文件夹杂乱
  • Ubuntu部署Elasticsearch教程
  • 61.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--提取金额
  • 一款基于 .NET 开源、免费、命令行式的哔哩哔哩视频内容下载工具
  • Win Semi宣布推出线性优化的GaN工艺
  • 考研408计算机网络2025年第38题真题解析
  • C++编写的经典贪吃蛇游戏
  • 风险预测模型原理
  • PS练习5:利用翻转制作图像倒影
  • 平替Jenkins,推荐一款国产开源免费的CICD工具 - Arbess
  • aws 实战小bug
  • NumPy 系列(一):numpy 数组基础
  • VSCode 的 launch.json 配置
  • OpenLayers地图交互 -- 章节六:范围交互详解
  • 分布式专题——15 ZooKeeper特性与节点数据类型详解
  • 分布式专题——16 ZooKeeper经典应用场景实战(上)
  • Torch-Rechub学习笔记-task2
  • Hadoop分布式计算平台
  • hive调优系列-1.调优须知
  • 爆炸特效:Unity+Blender-01
  • 解决切换 Node 版本后 “pnpm 不是内部或外部命令”问题
  • flag使用错误出现bug
  • 【Kafka面试精讲 Day 20】集群监控与性能评估
  • SQL 注入攻防:绕过注释符过滤的N种方法
  • 微软常用运行库
  • 在Kubernetes(k8s)环境中无法删除持久卷(PV)和持久卷声明(PVC)的解决方案
  • 【连载7】 C# MVC 跨框架异常处理对比:.NET Framework 与 .NET Core 实现差异