C#中的对象都继承自System.Object对象,分为引用类型和值类型两种,所以对象的相等比较而言就分两种,一种是比较引用,一种是比较值。System.Object默认提供了三个方法来进行对象的相等比较:静态的ReferenceEquals()和Equals()的两个版本,加上“==”运算符共有四种来进行对象相等比较的方法。
相等比较的方法:静态的ReferenceEquals()、Equals()静态方法、Equals()虚方法(子类可以去重写)和“==”运算符。
相等比较分类:引用类型比较(类的实例)、值类型比较(基本数据类型,结构或者枚举的实例)。
但对于引用类型和值类型而言,同一个方法它们的内部比较逻辑是不一样的,下面进行下简单的介绍。
一、引用类型相等比较
1、静态的ReferenceEquals()
ReferenceEquals()是一个静态方法,比较两个对象是否引用自同一个地址,是则返回true,否则返回false
调用方法:ReferenceEquals(obj1,obj2)
比较原则:1)、obj1和obj2同为null,则返回true
2)、obj1和obj2只有一个为null,则返回false
3)、obj1和obj2均不为null时,比较两个对象的引用地址,是则返回true,不是则返回false
例子:
SomeClass x,y;
x = new SomeClass();
y = new SomeClass();
z = y;
Boolean result1 = ReferenceEquals(null,null); //return true
Boolean result2 = ReferenceEquals(null,x); //return false
Boolean result3 = ReferenceEquals(x,y); //return false
Boolean result4 = ReferenceEquals(y,z); //return true
2、虚拟的Equals()方法
System.Object()的虚拟的Equals()方法也是比较引用的,但是因为它是虚拟的,所以继承的子类可以重写该方法以实现按值来比较对象,在重写Equals()方法时最好重写对象的GetHashCode()方法.

namespace ConsoleApp { class Program { static void Main(string[] args) { Point p1 = new Point(5, 2); Point p2 = new Point(5, 2); Point3D p3 = new Point3D(5, 2, 1); Point3D p4 = new Point3D(5, 2, 1); if (p1.Equals(p2)) Console.WriteLine("p1 is equals p2"); if (p3.Equals(p4)) Console.WriteLine("p3 is equals p4"); if (!p1.Equals(p3)) Console.WriteLine("p1 is not equals p3"); if (!p3.Equals(p1)) Console.WriteLine("p3 is not equals p1"); } } public class Point { private Int32 x; private Int32 y; public Point() { this.x = 0; this.y = 0; } public Point(Int32 _x, Int32 _y) { this.x = _x; this.y = _y; } public Int32 X { get { return x; } set { x = value; } } public Int32 Y { get { return y; } set { y = value; } } public override bool Equals(object obj) { if (obj == null) return false; if (this.GetType() != obj.GetType()) return false; return Equals((Point)obj); } public override int GetHashCode() { return x ^ y; //return base.GetHashCode(); } public override string ToString() { return String.Format("X:{0},Y:{1}", this.x, this.y); //return base.ToString(); } private bool Equals(Point p) { return (this.x == p.x) && (this.y == p.y); } } public class Point3D : Point { private Int32 z; public Int32 Z { get { return this.z; } set { z = value; } } public Point3D() : base() { this.z = 0; } public Point3D(Int32 _x, Int32 _y, Int32 _z) : base(_x, _y) { this.z = _z; } public override bool Equals(object obj) { if (obj == null) return false; if (GetType() != obj.GetType()) return false; Point3D p3d = obj as Point3D; if (p3d.z != this.z) return false; return base.Equals(obj); } public override int GetHashCode() { return base.GetHashCode() ^ z; } public override string ToString() { return String.Format("X:{0},Y:{1},Z:{2}", base.X, base.Y, Z); } } }
3、静态的Equals()方法
Eauals()静态方法的比较原则是按照引用的方式比较,再调用对象的Equals()方法的实例版本进行比较,所以在重写对象的Equals()方法时,其实已经间接的重写了静态的Equals()方法。
调用方法:Equals(obj1,obj2)
比较原则:1)、obj1和obj2均为null,则返回true
2)、obj1和obj2中只有一个为null,则返回false
3)、如果obj1和obj2两个引用不指向同一个对象,则返回false
4)、如果obj1和obj2两个引用指向同一个对象,则调用它们的Equals()方法的实例版本进行比较
4、“==”比较运算符
在默认情况下,==运算符对引用类型比较的是两个对象指向的引用是否是同一个对象,但是作为一个自定义的复杂类,可以自己重写适合自己的“==”运算符,在重写“==”时必须同时重写“!=”运算符。
例1:在没有重写“==”时,我们看下两个类的“==”的比较结果(比较是否指向同一个引用)

namespace ConsoleApp { class Program { static void Main(string[] args) { Point p1 = new Point(5, 2); Point p2 = new Point(5, 2); Point p21 = p1; if (p1.Equals(p2)) Console.WriteLine("p1 is equals p2"); if (!(p1 == p2)) Console.WriteLine("p1 is not == p2"); if (p1 == p21) Console.WriteLine("p1 is == p21"); } } public class Point { private Int32 x; private Int32 y; public Point() { this.x = 0; this.y = 0; } public Point(Int32 _x, Int32 _y) { this.x = _x; this.y = _y; } public Int32 X { get { return x; } set { x = value; } } public Int32 Y { get { return y; } set { y = value; } } public override bool Equals(object obj) { if (obj == null) return false; if (this.GetType() != obj.GetType()) return false; return Equals((Point)obj); } public override int GetHashCode() { return x ^ y; //return base.GetHashCode(); } public override string ToString() { return String.Format("X:{0},Y:{1}", this.x, this.y); //return base.ToString(); } private bool Equals(Point p) { return (this.x == p.x) && (this.y == p.y); } } }
运算结果:
例2:我们重写“==”运算符(比较两个对象对应值是否相等),这时再看下结果

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApp { class Program { static void Main(string[] args) { Point p1 = new Point(5, 2); Point p2 = new Point(5, 2); Point p21 = p1; if (p1.Equals(p2)) Console.WriteLine("p1 is equals p2"); if (p1 == p2) Console.WriteLine("p1 is == p2"); if (p1 == p21) Console.WriteLine("p1 is == p21"); } } public class Point { private Int32 x; private Int32 y; public Point() { this.x = 0; this.y = 0; } public Point(Int32 _x, Int32 _y) { this.x = _x; this.y = _y; } public Int32 X { get { return x; } set { x = value; } } public Int32 Y { get { return y; } set { y = value; } } public override bool Equals(object obj) { if (obj == null) return false; if (this.GetType() != obj.GetType()) return false; return Equals((Point)obj); } /// <summary> /// 重写相等运算符 /// </summary> public static Boolean operator ==(Point p1, Point p2) { return (p1.x == p2.x) && (p1.y == p2.y); } /// <summary> /// 重写不相等运算符 /// </summary> public static Boolean operator !=(Point p1, Point p2) { return !(p1.x == p2.x) && (p1.y == p2.y); } public override int GetHashCode() { return x ^ y; //return base.GetHashCode(); } public override string ToString() { return String.Format("X:{0},Y:{1}", this.x, this.y); //return base.ToString(); } private bool Equals(Point p) { return (this.x == p.x) && (this.y == p.y); } } }
运算结果:
二、值类型相等比较
1、静态的ReferenceEquals()
ReferenceEquals()方法用于比较引用,在比较之前,C#会先通过装箱技术对每个值类型参数进行分别装箱,这样ReferenceEquals()方法进行比较时得到的结果永远时false,所以用ReferenceEquals()来比较值类型是没有什么意义的。
2、虚拟的Equals()方法、静态的Equals()方法和“==”运算符
对于值类型,这三个方法默认都是进行值比较的。

namespace ConsoleApp { class Program { static void Main(string[] args) { Int32 a = 5; Int32 b = 5; if (a.Equals(b)) Console.WriteLine("a is equals b"); if (a == b) Console.WriteLine("a is == b"); if (Equals(a, b)) Console.WriteLine("a is equals b"); if (!ReferenceEquals(a, b)) Console.WriteLine("a is not ReferenceEquals b"); } } }