目录
背景介绍:
对象相等机制有所不同,这取决于比较的是引用类型(类的实例)还是值类型(基本数据类型、结构或枚举的实例)。在讲解值类型跟引用类型相等性之前。有必要介绍一张图。
下面分别介绍引用类型和值类型的相等性。
比较引用类型的相等性:
System.Object定义了3个不同的方法来比较对象的相等性:ReferenceEquals()、Equals虚方法和静态方法、比较运算符(==)。这里我首先介绍比较运算符(==)
1. 比较运算符(==):
比较运算符实际比较的是栈上地址值,而实际上我们更加关心该栈单元所存储的值。所以一般会对"=="进行运算符的重载。
对于引用类型,栈上存储对实例对象的引用,堆上存放对象的实际值。通过重载运算符,"=="实现比较对象实际单元值。如上图中student1对象的属性值 21 "zhangsan" 和student2对象的属性值 21 "zhangsan".最终的返回结果为:同一对象
{
class DemoObjectEquals
{
static void Main(string[] args)
{
Student student1 = new Student(21,"zhangsan");
Student student2 = new Student(21, "zhangsan");
if (student1 == student2)
{
Console.WriteLine("同一对象");
}
else
{
Console.WriteLine("不是同一对象");
}
Console.ReadKey();
}
}
class Student
{
private string name;
private int age;
public string Name { get => name; set => name = value; }
public int Age { get => age; set => age = value; }
public static bool operator ==(Student student1, Student student2)
{
// true如果objA是相同的实例作为objB或如果两者均null; 否则为false
if (object.ReferenceEquals(student1, student2))
{
return true;
}
else if (object.ReferenceEquals(student1, null) || (object.ReferenceEquals(student2, null)))
{
return false;
}
return student1.Equals(student2);
}
public static bool operator !=(Student student1, Student student2)
{
return !(student1 == student2);
}
public override bool Equals(object obj)
{
Student student = (Student)obj;
return (this.Age == student.Age && (this.Name == student.Name));
}
public override int GetHashCode()
{
return (this.Age.GetHashCode() + this.Name.GetHashCode());
}
public Student(int age,string name)
{
this.age = age;
this.name = name;
}
}
2.ReferenceEquals()方法:
该方法为静态方法,不能重写。其用于测试两个引用是否指向类的同一实例,特别是两个引用在栈上的值(即在内存中的地址)是否相同。其函数签名如下:
//
// 摘要:
// 确定指定的 System.Object 实例是否是相同的实例。
//
// 参数:
// objA:
// 要比较的第一个对象。
//
// objB:
// 要比较的第二个对象。
//
// 返回结果:
// true如果objA是相同的实例作为objB或如果两者均null; 否则为false。
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public static bool ReferenceEquals(Object objA, Object objB);
3.Equal()虚方法和静态Equals()方法:
前面讲到"=="的重载。当在栈上两实例对象的引用值不相等时,具体对其实例对象相关属性值(对象实际单元值)判断时,通过重写Equal()虚方法来实现当前this对应于待比较objB的实例属性值是否相等。在重写Equal虚方法时,对应的GetHashCode()也要重写。两个方法对应的函数签名如下:
//
// 摘要:
// 确定指定的对象实例是否被视为相等。
//
// 参数:
// objA:
// 要比较的第一个对象。
//
// objB:
// 要比较的第二个对象。
//
// 返回结果:
// 如果对象被视为相等,则为 true,否则为 false。 如果 objA 和 objB 均为 null,此方法将返回 true。
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public static bool Equals(Object objA, Object objB);
---------------------------------------------------------------------------
// 摘要:
// 确定指定的对象是否等于当前对象。
//
// 参数:
// obj:
// 要与当前对象进行比较的对象。
//
// 返回结果:
// 如果指定的对象等于当前对象,则为 true,否则为 false。
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public virtual bool Equals(Object obj);
我们可以看到静态的Equals方法除了函数类型和参数个数不同外,还有一点就是静态方法多了对于Null情况的判定。
2.比较值类型的相等性。
对于值类型的比较,对应int a=1,int b=1.通过if(a==b ){}来判定的是实际栈存储的值是否相等,即1是否等于1.
"=="本身最初还是比较的栈地址值(最初是不等的)如上图:0x4524,0x4525。而.NET本身帮我们做了一些事情。其在System.VauleType类中重载了实例方法Equals(),这样就又回到了上面讲述的引用类型中Equal方法重载的情况。
总结:
最原始的比较都是比较实际地址值的。(未进行任何包装以及重载)
对于值类型和引用类型判断相等时,规则如下:ReferenceEquals用于比较引用(栈上地址值),Equals用于重写并实现值比较(实对象实例内容)。