實作 Equals 方法
更新:2007 年 11 月
如需實作相等運算子 (==) 的相關資訊,請參閱實作 Equals 和相等運算子 (==) 的方針。
覆寫 GetHashCode 方法,允許型別在雜湊表中正常運作。
不要在 Equals 方法的實作中擲回例外狀況。但是,為 Null 引數傳回 false。
依循定義在 Object.Equals 方法上的規定,如下所示:
x.Equals(x) 會傳回 true。
x.Equals(y) 傳回和 y.Equals(x) 一樣的值。
(x.Equals(y) && y.Equals(z)) 傳回 true 的唯一前提是 x.Equals(z) 傳回 true。
連續叫用 x.Equals(y),只要 x 和 y 所參考的物件沒有被修改,則傳回相同的值。
x.Equals(null) 會傳回 false。
針對某些類型的物件,希望具有數值相等的 Equals 測試,而非參考相等。如果兩個物件具有相同的值,則 Equals 的這種實作將傳回 true (即使它們不是相同的執行個體)。型別的實作器決定由什麼組成物件的值,但是它通常是儲存在物件的執行個體變數 (Instance Variable) 中的部分或全部資料。例如,字串的值是以字串的字元為基礎;String 類別的 Equals 方法會針對任何兩個包含相同順序之完全相同字元的字串執行個體傳回 true。
當基底類別的 Equals 方法提供數值相等時,在衍生類別中 Equals 的多載應該呼叫 Equals 繼承的實作。
如果您是在支援運算子多載化的語言中進行程式設計,以及選擇要為指定的型別多載相等運算子 (==),則該型別應該覆寫 Equals 方法。Equals 方法的這種實作應該與相等運算子一樣傳回相同的結果。遵循此方針將協助您確保使用 Equals (例如 ArrayList 和 Hashtable) 的類別庫程式碼與應用程式碼使用相等運算子的方式一致。
如果您是實作實值型別,應該考慮覆寫 Equals 方法,如此可以比 ValueType 上的 Equals 方法的預設實作獲得更好的效能。如果覆寫 Equals,並且語言支援運算子多載化,您必須為實值型別多載相等運算子。
如果您是實作參考型別,而您的型別看起來像是基底型別,例如 Point、String、BigNumber 等,您應該考慮覆寫參考型別上的 Equals 方法。大多數的參考型別不應該多載相等運算子,即使是它們覆寫 Equals。然而,如果您正在實作具有值語意的參考型別 (例如,複雜的數字型別),您應該覆寫相等運算子。
如果在指定的型別上實作 IComparable 介面,則應該在該型別上覆寫 Equals。
範例
下列程式碼範例將示範如何實作、覆寫呼叫及多載 Equals 方法。
實作 Equals 方法
下列程式碼範例包含兩個 Equals 方法之預設實作的呼叫。
Imports System
Class SampleClass
Public Shared Sub Main()
Dim obj1 As New System.Object()
Dim obj2 As New System.Object()
Console.WriteLine(obj1.Equals(obj2))
obj1 = obj2
Console.WriteLine(obj1.Equals(obj2))
End Sub
End Class
using System;
class SampleClass
{
public static void Main()
{
Object obj1 = new Object();
Object obj2 = new Object();
Console.WriteLine(obj1.Equals(obj2));
obj1 = obj2;
Console.WriteLine(obj1.Equals(obj2));
}
}
上述程式碼的輸出如下:
False
True
覆寫 Equals 方法
下列程式碼範例將示範可覆寫 Equals 方法的 Point 類別,以提供數值相等和衍生自 Point 的 Point3D 類別。因為 Point 類別的 Equals 覆寫是繼承鏈結中首先引入數值相等的項目,所以不會叫用基底類別的 Equals 方法 (繼承自 Object,且會檢查是否有參考相等)。然而,因為 Point 會以提供數值相等的方式實作 Equals,所以 Point3D.Equals 會叫用 Point.Equals。
Namespace Examples.DesignGuidelines.EqualsImplementation
Public Class Point
Protected x As Integer
Protected y As Integer
Public Sub New (xValue As Integer, yValue As Integer)
Me.x = xValue
Me.y = yValue
End Sub
Public Overrides Overloads Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
Return False
End If
Dim p As Point = CType(obj, Point)
Return Me.x = p.x And Me.y = p.y
End Function
Public Overrides Function GetHashCode() As Integer
Return x Xor y
End Function
End Class
Public Class Point3D
Inherits Point
Private z As Integer
Public Sub New (xValue As Integer, yValue As Integer, zValue As Integer)
MyBase.New(xValue, yValue)
Me.z = zValue
End Sub
Public Overrides Overloads Function Equals(obj As Object) As Boolean
Return MyBase.Equals(obj) And z = CType(obj, Point3D).z
End Function
Public Overrides Function GetHashCode() As Integer
Return MyBase.GetHashCode() Xor z
End Function
End Class
End Namespace
using System;
namespace Examples.DesignGuidelines.EqualsImplementation
{
class Point: object
{
protected int x, y;
public Point(int xValue, int yValue)
{
x = xValue;
y = yValue;
}
public override bool Equals(Object obj)
{
// Check for null values and compare run-time types.
if (obj == null || GetType() != obj.GetType())
return false;
Point p = (Point)obj;
return (x == p.x) && (y == p.y);
}
public override int GetHashCode()
{
return x ^ y;
}
}
class Point3D: Point
{
int z;
public Point3D(int xValue, int yValue, int zValue) : base(xValue, yValue)
{
z = zValue;
}
public override bool Equals(Object obj)
{
return base.Equals(obj) && z == ((Point3D)obj).z;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ z;
}
}
}
Point.Equals 方法會檢查,obj 引數是否不是 null,並且會參考與這個物件相同型別的執行個體。如果其中一個檢查失敗,方法會傳回 false。Equals 方法會使用 GetType 方法來判斷兩個物件的執行階段型別是否完全相同。請注意,這裡不使用 typeof (在 Vsual Basic 中為 TypeOf),因為它會傳回靜態型別。如果此方法之前改用 obj is Point 形式的檢查,則在 obj 是衍生自 Point 的類別執行個體的情況下,這項檢查會傳回 true,即使當 obj 和目前的執行個體不是相同的執行階段型別時,也是如此。驗證完兩個物件為相同型別之後,此方法會將 obj 轉換為型別 Point,並傳回比較兩個物件的執行個體變數之結果。
在 Point3D.Equals 中,繼承的 Equals 方法會在完成任何其他動作之前被叫用。繼承的 Equals 方法會驗證 obj 不是 null、該 obj 為與這個物件具有相同類別的執行個體,而且繼承的執行個體變數相符。只有當繼承的 Equals 傳回 true,此方法才會比較衍生類別中所引入的執行個體變數。特別是,除非已經判斷 obj 為型別 Point3D 或衍生自 Point3D 的類別,否則不會執行轉換為 Point3D 的動作。
使用 Equals 方法來比較執行個體變數
在先前範例中,使用相等運算子 (==) 比較個別的執行個體變數。在某些情況下,適合使用 Equals 方法來比較 Equals 實作中的執行個體變數,如下列程式碼範例所示。
Imports System
Class Rectangle
Private a, b As Point
Public Overrides Overloads Function Equals(obj As [Object]) As Boolean
If obj Is Nothing Or Not Me.GetType() Is obj.GetType() Then
Return False
End If
Dim r As Rectangle = CType(obj, Rectangle)
' Use Equals to compare instance variables.
Return Me.a.Equals(r.a) And Me.b.Equals(r.b)
End Function
Public Overrides Function GetHashCode() As Integer
Return a.GetHashCode() ^ b.GetHashCode()
End Function
End Class
using System;
class Rectangle
{
Point a, b;
public override bool Equals(Object obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
Rectangle r = (Rectangle)obj;
// Use Equals to compare instance variables.
return a.Equals(r.a) && b.Equals(r.b);
}
public override int GetHashCode()
{
return a.GetHashCode() ^ b.GetHashCode();
}
}
多載相等運算子 (==) 和 Equals 方法
在某些程式語言中 (如 C#),支援運算子多載化。當型別多載相等運算子 (==) 時,它也應該覆寫 Equals 方法,以提供相同的功能。就多載的相等運算子 (==) 而言,這項處理通常是藉由撰寫 Equals 方法來完成,如下列程式碼範例所示。
public struct Complex
{
double re, im;
public override bool Equals(Object obj)
{
return obj is Complex && this == (Complex)obj;
}
public override int GetHashCode()
{
return re.GetHashCode() ^ im.GetHashCode();
}
public static bool operator ==(Complex x, Complex y)
{
return x.re == y.re && x.im == y.im;
}
public static bool operator !=(Complex x, Complex y)
{
return !(x == y);
}
}
因為 Complex 是 C# struct (一種實值型別),所以已知不會有任何類別將衍生自 Complex。因此,Equals 方法不需要為每一個物件比較 GetType 結果,而是會使用 is 運算子來檢查 obj 參數的型別。
Portions Copyright 2005 Microsoft Corporation.All rights reserved.
Portions Copyright Addison-Wesley Corporation.All rights reserved.
如需設計方針的詳細資訊,請參閱由 Krzysztof Cwalina 和 Brad Abrams 所著,並由 Addison-Wesley 於 2005 年發行的「Framework 設計方針:可重複使用之 .NET 程式庫的慣例、慣用語法和模式」一書。