覆寫 Equals() 和運算子 == 的方針 (C# 程式設計手冊)
更新:2007 年 11 月
在 C# 中,相等代表著兩種不同的意義,可能是參考相等或實值相等。實值相等是我們對相等的一般認知,意即兩個物件包含相同的值。例如,兩個值為 2 的整數即視為實值相等。參考相等表示不存在兩個可比較的物件,而是有兩個物件參考,並且兩者都參考相同的物件。這只要用簡單的指派就可以看出來,如下列範例所示:
System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b); //returns true
在這個程式碼中,只存在一個物件,但對該物件有多個參考:a 和 b。因為他們都參考相同物件,所以就具有參考相等。如果兩個物件有參考相等的關係,則兩者的實值也相等,但實值相等並不保證參考也相等。
若要檢查參考是否相等,請使用 ReferenceEquals。若要檢查實值是否相等,請使用 Equals。
覆寫 Equals
因為 Equals 是虛擬方法,而任何類別都可以覆寫其實作。任何表示值的類別,也就是幾乎所有實值型別,或被視為群組的一組值,例如複數類別,都應覆寫 Equals。如果型別實作 IComparable,便應覆寫 Equals。
Equals 的新實作應實現 Equals 的所有保證條件:
x.Equals(x) 會傳回 true。
x. Equals (y) 會傳回與 y. Equals (x) 相同的值。
如果 (x. Equals (y) && y. Equals (z)) 傳回 true,則 x. Equals (z) 也會傳回 true。
只要 x 和 y 所參考的物件沒有經過修改,後續叫用 x. Equals (y) 就會傳回相同的值。
x. Equals (null) 會傳回 false (僅限於不可為 Null 的實值型別)。如需詳細資訊,請參閱 可為 Null 的型別 (C# 程式設計手冊))。
Equals 的新實作不應擲回例外狀況。建議任何覆寫 Equals 的類別同時也覆寫 Object.GetHashCode。另外也建議任何類別在實作 Equals (object) 的同時,也應該替本身的型別實作 Equals (type) 以增強效能。例如:
class TwoDPoint : System.Object
{
public readonly int x, y;
public TwoDPoint(int x, int y) //constructor
{
this.x = x;
this.y = y;
}
public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Point return false.
TwoDPoint p = obj as TwoDPoint;
if ((System.Object)p == null)
{
return false;
}
// Return true if the fields match:
return (x == p.x) && (y == p.y);
}
public bool Equals(TwoDPoint p)
{
// If parameter is null return false:
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return (x == p.x) && (y == p.y);
}
public override int GetHashCode()
{
return x ^ y;
}
}
任何可以在基底類別上呼叫 Equals 的衍生類別,在完成比較前也應這麼做。在下列範例中,Equals 會呼叫基底類別 Equals,該類別則會檢查 null 參數,並比較參數的型別與衍生類別的型別。這時,衍生類別的 Equals 實作將必須檢查衍生類別上所宣告的新資料欄位:
class ThreeDPoint : TwoDPoint
{
public readonly int z;
public ThreeDPoint(int x, int y, int z)
: base(x, y)
{
this.z = z;
}
public override bool Equals(System.Object obj)
{
// If parameter cannot be cast to ThreeDPoint return false:
ThreeDPoint p = obj as ThreeDPoint;
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return base.Equals(obj) && z == p.z;
}
public bool Equals(ThreeDPoint p)
{
// Return true if the fields match:
return base.Equals((TwoDPoint)p) && z == p.z;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ z;
}
}
覆寫運算子 ==
根據預設,運算子 == 會藉由判斷兩個參考是否表示相同的物件,以測試參考是否相等。因此,參考型別 (Reference Type) 並不需要實作運算子 == 來取得這項功能。當型別為不可變,也就是包含在執行個體中的資料無法變更時,以多載運算子 == 比較實值相等 (而非參考相等) 會相當有用,因為做為不可變的物件,只要具有相同的值,都會被視為是相同的。覆寫非不可變之型別中的運算子 ==,並不是個好主意。
多載運算子 == 實作不應擲回例外狀況。任何多載運算子 == 的型別也應多載運算子 !=。例如:
//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.x == b.x && a.y == b.y && a.z == b.z;
}
public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
return !(a == b);
}
注意事項: |
---|
多載運算子 == 時常見的錯誤,就是使用 (a == b)、(a == null) 或 (b == null) 來檢查參考相等。這樣會改成建立對多載運算子 == 的呼叫,並會造成無限的迴圈。請使用 ReferenceEquals 或將型別轉換為 Object,避免產生這種迴圈。 |