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;
}
public override string ToString()
{
return String.Format("X:{0},Y:{1}", this.x, this.y);
}
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;
}
public override string ToString()
{
return String.Format("X:{0},Y:{1}", this.x, this.y);
}
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;
}
public override string ToString()
{
return String.Format("X:{0},Y:{1}", this.x, this.y);
}
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");
}
}
}