C#对象的相等性与同一性
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/94b90fc9a705803623e573bb5071c91a.jpeg#pic_center)
1. 概述与准备
1.1 概述
在C#中,对象的相等性(equality)和同一性(identity)是两个不同的概念。
-
相等性(Equality):相等性指的是两个对象的值是否相等。
- 对于引用类型,默认情况下相等性比较的是对象的引用,即两个对象是否引用同一个内存地址;但可以通过重写 Equals 方法来自定义相等性比较的逻辑。
- 对于值类型,相等性比较的是对象的值。
-
同一性(Identity):同一性指的是两个对象是否是同一个实例。
- 如果两个对象引用同一个内存地址,则它们是同一实例;
- 否则,它们是不同的实例。
其实,这里涉及不少的值得关注的点,否则稍微不慎,便会出错。
这篇文章便总结一下**==操作符**、Equal、ReferenceEquals等等方法,以便更好地把握C#对象的相等性与同一性。
1.2 准备
为了方便后面举例子,这里先定义两个类:学生类、学校类。
class Student
{
public int StuID { get; set; }
public string StuName { get; set; }
public School StuSchool { get; set; }
public Student(int stuID, string stuName, School stuSchool)
{
this.StuID = stuID;
this.StuName = stuName;
this.StuSchool = stuSchool;
}
}
class School
{
public string SchoolName { get; set; }
public string SchoolAddress { get; set; }
public School(string name, string address)
{
this.SchoolName = name;
this.SchoolAddress = address;
}
}
2. Equals(Object)
2.1 功能:
System.Object类型提供了名为Equals的虚方法,作用是判断指定对象是否等于当前对象。此方法比较的是对象的引用。
public virtual bool Equals (object? obj);
2.2 实例:
Student stu1 = new Student(001, "孙悟空", new School("清华大学", "北京市"));
Student stu2 = new Student(002, "猪八戒", new School("北京大学", "北京市"));
Student stu3 = new Student(001, "孙悟空", new School("清华大学", "北京市")); //与stu1属性值相同
//输出False。Equals方法比较的是对象的引用,而stu1和stu2是两个不同的对象,因此它们的引用不同。
Console.WriteLine(stu1.Equals(stu2));
//输出False。尽管stu1 和stu3的属性值相同,但它们仍然是两个不同的对象,因此它们的引用不同。
Console.WriteLine(stu1.Equals(stu3));
总结:对于Object的Equals方法的默认实现,它实现的实际是同一性(identity),而非相等性(equality)。这是易混淆的点!
2.3 扩展:
在 C# 中,== 操作符对于引用类型默认比较的是对象的引用,而不是属性值。stu1 和 stu2 是两个不同的对象,所以它们的引用不同,结果为 False。
Student stu1 = new Student(001, "孙悟空", new School("清华大学", "北京市"));
Student stu2 = new Student(002, "猪八戒", new School("北京大学", "北京市"));
Student stu3 = new Student(001, "孙悟空", new School("清华大学", "北京市")); //与stu1属性值相同
Console.WriteLine(stu1 == stu2); //输出False
Console.WriteLine(stu1 == stu3); //输出False
2.4 重写此方法
上面讲到,此方法是一个虚方法,这意味着其派生类可以重写此类。如果你想的话,你可以在Student类中增加下面的代码,以至于实现相等性而非同一性,即能够按对象的属性值进行比较:
public override bool Equals(object? obj)
{
if (obj == null && this.GetType() != obj.GetType())
return false;
Student other = (Student)obj;
return this.StuID == other.StuID &&
this.StuName == other.StuName &&
this.StuSchool.Equals(other.StuSchool);
}
注意,上述代码中的 StuSchool 的判等取决于StuSchool.Equals()是否被重写。
3. Equals(Object, Object)
3.1 功能
这是Object类的静态方法,也是比较类对象的引用。
public static bool Equals (object? objA, object? objB);
这里分几种情况:
- objA 与 objB 都为null,返回false;
- objA 与 objB 仅有一个为null,返回false;
- objA 与 objB 都不为null,调用objA.Equals(objB)并返回结果。
3.2 实例
Student stu1 = new Student(001, "孙悟空", new School("清华大学", "北京市"));
Student stu2 = new Student(002, "猪八戒", new School("北京大学", "北京市"));
Student stu3 = new Student(001, "孙悟空", new School("清华大学", "北京市")); //与stu1属性值相同
Console.WriteLine(Object.Equals(stu1, stu2)); //输出False。这里实际上调用stu1.Equals(stu2);
Console.WriteLine(Object.Equals(stu1, stu3)); //输出False。这里实际上调用stu1.Equals(stu3);
4. ReferenceEquals(Object, Object)
4.1 功能
比较指定的 Object 实例是否是相同的实例,比较对象的引用。
public static bool ReferenceEquals (object? objA, object? objB);
注意:此方法不可重写。
4.2 使用场景:
由于类型能够重写Equal方法,所以不能再用它测试同一性。为了解决这个问题,Object提供了另一个静态方法ReferenceEquals。
如果要测试两个对象引用是否相等,并且你不确定该类 Equals 方法是否被重写了,则可以调用此方法。
Student stu1 = new Student(001, "孙悟空", new School("清华大学", "北京市"));
Student stu2 = new Student(002, "猪八戒", new School("北京大学", "北京市"));
Student stu3 = new Student(001, "孙悟空", new School("清华大学", "北京市")); //与stu1属性值相同
Console.WriteLine(Object.Equals(stu1, stu2));//输出False
Console.WriteLine(Object.ReferenceEquals(stu1, stu2)); //输出False
当你确定该类的Equals方法没有被重写,那这两个方法几乎是一样的。
4.3 要注意的地方
请注意,我为什么说“几乎是一样呢”?理由简单,因为有不一样的地方:
int a = 1;
Console.WriteLine(a == a); //输出True。因为 == 操作符比较的是 int 类型的值。
Console.WriteLine(a.Equals(a)); //输出True。因为在int类型中,Equals方法被重写成比较int类型的值。
Console.WriteLine(Object.Equals(a, a)); //输出True。同上,这里会调用int类型的重写的Equals方法进行值的比较。
Console.WriteLine(Object.ReferenceEquals(a, a)); //输出False。由于 a 是 int 类型的值类型,它在比较过程中会被装箱为一个对象。因此,两个 a 的引用是不同的,结果为 False。
总结:
- 比较值类型时,如果 objA 并且 objB 是值类型,则会在将它们传递到 ReferenceEquals 方法之前进行装箱。这意味着,如果同时objA与objB表示值类型的同一实例,ReferenceEquals该方法仍返回false。
- 检查引用类型对象的同一性时建议调用ReferenceEquals,不建议使用==或者Equals。
5. 扩展:字符串的判等
string s1 = "Hello";
Console.WriteLine(s1 == s1); //输出True
Console.WriteLine(s1.Equals(s1)); //输出True。在string类型中Equals方法被重写,用于比较字符串的内容
Console.WriteLine(Object.Equals(s1, s1)); //输出True。同上。
Console.WriteLine(Object.ReferenceEquals(s1, s1)); //输出True。因为两个s1的引用是相同的。
注意:
- 字符串常量会被存储在一个字符串池(string pool)中,以便重复使用。
- 当创建两个具有相同内容的字符串时,它们实际上会引用相同的字符串对象。
如下:
string s2 = "Hello";
Console.WriteLine(s1 == s2); //输出True
Console.WriteLine(s1.Equals(s2)); //输出True。在string类型中Equals方法被重写,用于比较字符串的内容
Console.WriteLine(Object.Equals(s1, s2)); //输出True。同上。
Console.WriteLine(Object.ReferenceEquals(s1, s2)); //输出True。由于值相同,所以s1与s2引用相同的字符串对象。
参考
https://learn.microsoft.com/zh-cn/dotnet/api/system.object?view=net-7.0