一、引言
對于對象的比較,C#提供了很多種比較方法,以及==和!=等運算來進行對象的同一性和相等性的比較,但我們在實際的編寫代碼中,可能會混淆各個方法的用法,在此廢話不多說,我們一起來看些這幾種方法的具體用法及含義。
二、Object. Equals (object obj)
這個方法是Object提供的虛方法,對于該方法,我們先差別引用類型和值類型的用法
對于引用類型,如果沒有重寫Equals,則預設比較對象的同一性,即比較引用位址,C#中大多數引用類型都重寫了該方法,例如String類型比較的字元串是否相等
對于值類型,預設比較的是對象的相等性(即struct中所有字段的相等性)因為struct類型繼承于System.ValueType而ValueType類中重寫了Object中的Equals方法,代碼具體實作可通過Reflector工具檢視
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
public class MyNewClass
{
private int item1;
public int Item1
{
get { return item1; }
set { item1 = value; }
}
private float item2;
public float Item2
{
get { return item2; }
set { item2 = value; }
}
public MyNewClass(int i, float j)
{
item1 = i;
item2 = j;
}
public MyNewClass() { }
//重寫了Object.Equals
public override bool Equals(object obj)
{
if (obj == null) return false;
MyNewClass o = obj as MyNewClass;
if (o == null) return false;
return item1 == o.item1 && item2 == o.item2;
}
}
class Program
{
static void Main(string[] args)
{
//沒有重寫Object.Equals的引用類型
MyClass mc1 = new MyClass(1);
MyClass mc2 = new MyClass(1);
MyClass mc3 = mc2;
//值類型
MyStruct stc1 = new MyStruct(1);
MyStruct stc2 = new MyStruct(1);
MyStruct stc3 = new MyStruct(2);
//重寫了Object.Equals的引用類型
MyNewClass nc1 = new MyNewClass(1, 1.0f);
MyNewClass nc2 = new MyNewClass(1, 1.0f);
MyNewClass nc3 = new MyNewClass(2, 1.0f);
//比較沒有重寫Object.Equals的引用類型執行個體對象
Console.WriteLine(mc1.Equals(mc2));//比較引用位址 傳回False
Console.WriteLine(mc2.Equals(mc3));//比較引用位址 傳回True
//比較值類型的執行個體對象
Console.WriteLine(stc1.Equals(stc2));//ValueType重寫了Equals方法,比較的是結構體中的所有字段 傳回True
Console.WriteLine(stc2.Equals(stc3));//比較結構體中的所有字段 傳回False
//比較重寫了Object.Eqauls的引用類型執行個體對象
//比較執行個體對象中所有字段的相等性
Console.WriteLine(nc1.Equals(nc2));//比較對象中執行個體字段相等性 傳回Ture
Console.WriteLine(nc2.Equals(nc3));//傳回False
Console.Read();
}
}
}
輸出結果:
三、Object. Equals (object objA,object objB)
這個方法是Object對象提供的靜态方法
這個方法比較的過程是這樣的:objA如果和objB引用相同執行個體或者兩者同時為null時傳回True,如果不滿足前者情況,則傳回objA.Equals(objB)的結果
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
public class MyNewClass
{
private int item1;
public int Item1
{
get { return item1; }
set { item1 = value; }
}
private float item2;
public float Item2
{
get { return item2; }
set { item2 = value; }
}
public MyNewClass(int i, float j)
{
item1 = i;
item2 = j;
}
public MyNewClass() { }
//重寫了Object.Equals
public override bool Equals(object obj)
{
if (obj == null) return false;
MyNewClass o = obj as MyNewClass;
if (o == null) return false;
return item1 == o.item1 && item2 == o.item2;
}
}
class Program
{
static void Main(string[] args)
{
MyClass mc1 = new MyClass(1);
MyClass mc2 = new MyClass(1);
MyClass mc3 = mc2;
MyStruct stc1 = new MyStruct(1);
MyStruct stc2 = new MyStruct(1);
object stc3 = stc2;
MyNewClass nc1 = new MyNewClass(1, 1.0f);
MyNewClass nc2 = new MyNewClass(1, 1.0f);
Console.WriteLine(object.Equals(null, null));//兩者都為null 傳回True
Console.WriteLine(object.Equals(mc1, mc2));//傳回的是mc1.Equals(mc2)的結果 傳回False
Console.WriteLine(object.Equals(mc2, mc3));//傳回的是mc2.Equals(mc3)的結果 傳回True
//對于值類型,調用object.Equals靜态方法時,會發生裝箱操作
//實際比較時,傳回是MyStruct結構體中重寫的Equals方法
Console.WriteLine(object.Equals(stc1, stc2));//傳回的是stc1.Equals(stc2)的結果 傳回True
//stc2發生兩次裝箱過程
//1、object stc3=stc2 stc2在GC堆上裝箱,并将一個位址傳回給stc3
//2、調用object.Equals(stc2,stc3),stc2在GC又裝箱一次,并将引用位址傳回給第一個形參
//兩次裝箱後,引用位址不同,實際比較時,傳回的是MyStruct結構體中重寫的Equals方法
Console.WriteLine(object.Equals(stc2, stc3));//傳回的是stc2.Equals(stc3)的結果,傳回True
Console.WriteLine(object.Equals(nc1, nc2));//傳回的是nc1.Equals(nc2)的結果,傳回True
Console.Read();
}
}
}
輸出結果:
四、Object. ReferenceEquals (object objA,object objB)
這個方法是Object對象提供的靜态方法,該方法隻比較objA和objB是否引用同一個對象執行個體,且該方法對值類型的比較永遠傳回False.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
class Program
{
static void Main(string[] args)
{
MyClass m1 = new MyClass(1);
MyClass m2 = new MyClass(1);
MyClass m3 = m2;
MyStruct s1 = new MyStruct(1);
MyStruct s2 = new MyStruct(1);
MyStruct s3 = s2;
Console.WriteLine(object.ReferenceEquals(m1, m2));//比較引用位址 傳回False
Console.WriteLine(object.ReferenceEquals(m2, m3));//比較引用位址 傳回True
Console.WriteLine(object.ReferenceEquals(s1, s2));//s1與s2裝箱後引用的位址不一緻,比較引用值永遠傳回False
Console.WriteLine(object.ReferenceEquals(s3, s2));//傳回False
Console.Read();
}
}
}
輸出結果:
五、==和!= 運算符重載
“==”和“!=”對于引用類型和值類型比較方式主要有以下不同
1、 對于引用類型,如果沒有重載”==”和“!=”操作符的情況下,預設比較的是引用位址,很多.NET引用類型都重載了”==“和”!=“操作符,例如String類的”==“比較的是兩個字元串是否相同
2、 對于.NET的基元值類型(short、Int16、Int32、Boolean等)在調用“==“和”!=”,CLR會實作内置的比較方式
3、 對于沒有同重載“==”和“!=”的值類型,調用“==”和“!=”會引起編譯器錯誤
通常我們設計一個類時,如果重寫了Object.Equals(object o)時,一般也會重載“==“和”!=“操作符,并且重載“==”操作符調用重寫的Equals方法.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public struct MyNewStruct
{
public int item;
public MyNewStruct(int i)
{
item = i;
}
public static bool operator ==(MyNewStruct m1, MyNewStruct m2)
{
return m1.Equals(m2);
}
public static bool operator !=(MyNewStruct m1, MyNewStruct m2)
{
return !m1.Equals(m2);
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
public class MyNewClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyNewClass(int i)
{
item = i;
}
//重寫了Object.Equals方法
public override bool Equals(object obj)
{
if (obj == null) return false;
if (this.GetType() != obj.GetType()) return false;
MyNewClass m = obj as MyNewClass;
return item.Equals(m.item);
}
//重載“==”操作符編譯器要求同時必須要重載"!="
public static bool operator ==(MyNewClass m1, MyNewClass m2)
{
return m1.Equals(m2);
}
public static bool operator !=(MyNewClass m1, MyNewClass m2)
{
return !m1.Equals(m2);
}
}
class Program
{
static void Main(string[] args)
{
//基元值類型
Int32 i = 3;
Int32 j = 3;
//自定義值類型(沒能重載”==”和“!=”)
MyStruct s1 = new MyStruct(1);
MyStruct s2 = new MyStruct(1);
//自定義值類型(重載了“==”和“!=”)
MyNewStruct ns1 = new MyNewStruct(1);
MyNewStruct ns2 = new MyNewStruct(1);
//沒有重載“==”和“!=”的引用類型
MyClass c1 = new MyClass(1);
MyClass c2 = new MyClass(1);
MyClass c3 = c2;
//重載了“==”和”!=“的引用類型
MyNewClass nc1 = new MyNewClass(1);
MyNewClass nc2 = new MyNewClass(1);
Console.WriteLine(i == j);//基元值類型==比較 傳回True
//自定義值類型(漢有重載==)調用==比較時,編譯器提示錯誤
//Console.WriteLine(s1 == s2);
Console.WriteLine(ns1 == ns2);//自定義結構體重載了==操作符,傳回 True
Console.WriteLine(c1 == c2);//沒有重載==操作符的引用類型比較,比較引用位址 傳回 False
Console.WriteLine(c2 == c3);//沒有重載==操作符的引用類型比較,比較引用位址 傳回 True
Console.WriteLine(nc1 == nc2);//重載了==操作符的引用類型比較,調用nc1.Equals(nc2) 傳回True
Console.Read();
}
}
}
輸出結果:
此外我們要注意上面一段在代碼在編譯時,編譯器提示有幾個警告:
一個類型在重寫了Equals方法或者重載了運算符“==、!=“之後,之是以編譯器警告要同時定義GetHashCode,是因為在System.Collections.Hashtable類型、System.Collections.Genceric.Dictionary類型以及其他的一些集合中,要求兩個對象為了相等,必須具胡相同的哈稀碼,是以如果重寫了Equals,編譯器會提醒你,還應重寫GetHashCode,確定相等性算法和對象哈稀算法一緻,對于哈稀算法,在此篇不做讨論