在這份筆記之中,我們將會進行有類別繼承情況下的衍生類別與基底類別,該如何實作出具有類別成員的實值比較功能。
在基底類別中,我們宣告了類別 TwoDPoint,這個類別的實作說明,可以參考 類型成員 Class Member 的實值相等 Value Equality 這份文章中的解釋,不過,我們在這個基底類別的 Equals 的強型別泛型介面方法實作中,有針對所傳入的物件是否與正在進行比較的物件,其所執行時期的物件型別,進行檢查:
this.GetType() != other.GetType()
也就是說,這兩個比較的型別必須是相同的(也就是必須都為 TwoPoint 這個基底類別),才能夠繼續進行成員的實值比對,若不相同的話,則就會回傳 false,表示實值比對不成立。class TwoDPoint : IEquatable<TwoDPoint>
{
public int X { get; set; }
public int Y { get; set; }
public TwoDPoint(int x, int y)
{
X = x;
Y = y;
}
// 請手動覆寫虛擬 Object.Equals(Object)
// 輸入 over ,空白按鍵 , 選擇 Equals(object obj)
public override bool Equals(object obj)
{
// 這理由該類別的 Equals 方法來檢查兩個物件是否擁有相當的值
return this.Equals(obj as TwoDPoint);
}
// 實作出 IEquatable<TwoDPoint> 的 特定類型的 Equals 方法
// 這裡的傳入參數,已經是強型別 TwoDPoint
public bool Equals(TwoDPoint other)
{
// 若傳入參數為空值 null ,則這次比較不成立
// 這個必須要先做檢查,免得發生例外異常
if (Object.ReferenceEquals(other, null))
{
return false;
}
// 檢查這兩個物件變數是否指向同一個參考物件
if (Object.ReferenceEquals(this, other))
{
return true;
}
// 若這兩個物件變數的執行時期型別不相同,則比較不成立
if (this.GetType() != other.GetType())
{
return false;
}
// 進行該類別物件的欄位實際值的比較
// 這裡不需要做基底類別的欄位值比較,因為,這個類別的基底類別為
// System.Object ,該比較預設使用參考比較
// 若基底類別為客製類別,則需要再呼叫基底類別的 Equals
return (X == other.X) && (Y == other.Y);
}
// 請手動覆寫虛擬 Object.GetHashCode
// 輸入 over ,空白按鍵 , 選擇 GetHashCode()
public override int GetHashCode()
{
// return X * 0x00010000 + Y;
return X.GetHashCode() ^ Y.GetHashCode();
}
// 進行相等運算子的多載定義
// 這裡傳入的參數為強型別
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
{
// 確認傳入參數不為空值
if (Object.ReferenceEquals(lhs, null))
{
// 確認傳入參數不為空值
if (Object.ReferenceEquals(rhs, null))
{
// 這裡實作出 null == null = true.
return true;
}
// 若傳入參數其中一個為空值,比較不成立
return false;
}
// 執行該物件的成員值比較
return lhs.Equals(rhs);
}
// 進行不相等運算子的多載定義
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
{
return !(lhs == rhs);
}
}
接著,當然我們要繼續宣告衍生類別 ThreeDPoint,這個衍生類別繼承了基底類別 TwoDPoint,並且也需要實作出 IEquatable 介面方法。關於要實作出兩個類別的物件成員實值相等的過程,可以參考 類型成員 Class Member 的實值相等 Value Equality
在執行個體的 Equals 方法中,我們先比對了衍生類別才擁有的資料成員,接著,繼續比對基底類別內的資料成員是否相等,若全部都相等,則這兩個衍生成員的物件成員實值比對,就會成立了。
對於衍生類別的
GetHashCode
方法,我們則是把衍生類別內的成員 HashCode與基底類別物件的 HashCode 進行 XOR 邏輯運算,就會得到這個衍生類別物件的 HashCode。// 這裡展示如何繼承一個類別,並且要實作出相關成員相等的程式碼
class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
{
public int Z { get; set; }
// x,y 的參數,交由基底來別來做物件值的初始化
public ThreeDPoint(int x, int y, int z)
: base(x, y)
{
this.Z = z;
}
// 請手動覆寫虛擬 Object.Equals(Object)
// 輸入 over ,空白按鍵 , 選擇 Equals(object obj)
public override bool Equals(object obj)
{
// 這理由該類別的 Equals 方法來檢查兩個物件是否擁有相當的值
return this.Equals(obj as ThreeDPoint);
}
// 實作出 IEquatable<TwoDPoint> 的 特定類型的 Equals 方法
// 這裡的傳入參數,已經是強型別 ThreeDPoint
public bool Equals(ThreeDPoint p)
{
// 若傳入參數為空值 null ,則這次比較不成立
// 這個必須要先做檢查,免得發生例外異常
if (Object.ReferenceEquals(p, null))
{
return false;
}
// 檢查這兩個物件變數是否指向同一個參考物件
if (Object.ReferenceEquals(this, p))
{
return true;
}
// 這裡僅作衍生類別擁有的成員值檢查,
// 至於,基底類別的成員,交由基底類別來檢查成員值
if (Z == p.Z)
{
// 進行該類別物件的欄位實際值的比較
// 這裡因為基底類別為客製類別,
// 所以,需要再呼叫基底類別的 Equals
return base.Equals((TwoDPoint)p);
}
else
{
return false;
}
}
// 請手動覆寫虛擬 Object.GetHashCode
// 輸入 over ,空白按鍵 , 選擇 GetHashCode()
public override int GetHashCode()
{
//return (X * 0x100000) + (Y * 0x1000) + Z;
return Z.GetHashCode() ^ base.GetHashCode();
}
// 進行相等運算子的多載定義
// 這裡傳入的參數為強型別
public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
{
// 確認傳入參數不為空值
if (Object.ReferenceEquals(lhs, null))
{
// 確認傳入參數不為空值
if (Object.ReferenceEquals(rhs, null))
{
// 這裡實作出 null == null = true.
return true;
}
// 若傳入參數其中一個為空值,比較不成立
return false;
}
// 執行該物件的成員值比較
return lhs.Equals(rhs);
}
// 進行不相等運算子的多載定義
public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs)
{
return !(lhs == rhs);
}
}
進行測試
首先,我們進行 衍生類別的物件成員值比較,在這裡,我們建立了兩個衍生類別物件,這兩個物件的資料成員,都具有相同的實值。而 classDerivedObject2 與 classDerivedObject3 這兩個物件變數,則是指向同一個參考物件。
在這裡,這個敘述
classDerivedObject1.Equals(i)
將會使用 object.Equals()
這個方法進行比對,這是因為,這兩個物件的型別不相同,一個是衍生類別,一個是 int;當然,比對的結果,當然也就會不成立。
由於,我們有做兩個物件需要具有相同的型別檢查,因此不論是使用 衍生類別物件.Equals(基底類別物件) 或者 基底類別物件.Equals(衍生類別物件),這樣的實值比對都是不會成立的。
但若您把基底類別的
if (this.GetType() != other.GetType()){return false;}
敘述註解起來,則就會得到不正確的結果了。Console.WriteLine("衍生類別的物件成員值比較");
ThreeDPoint classDerivedObject1 = new ThreeDPoint(3, 4, 5);
ThreeDPoint classDerivedObject2 = new ThreeDPoint(3, 4, 5);
ThreeDPoint classDerivedObject3 = classDerivedObject2;
TwoDPoint classBaseObject3 = new TwoDPoint(3,4);
int i = 5;
Console.WriteLine("classDerivedObject1.Equals(classDerivedObject2) = {0}", classDerivedObject1.Equals(classDerivedObject2));
Console.WriteLine("classDerivedObject1 == classDerivedObject2 = {0}", classDerivedObject1 == classDerivedObject2);
Console.WriteLine("classDerivedObject1.Equals(classDerivedObject3) = {0}", classDerivedObject1.Equals(classDerivedObject3));
// classDerivedObject1.Equals 有兩個多載,
// 一個是這個型別本身(這裡使用強型別參數),
// 另外一個是 object 這個類別(這裡接受 object 型別參數)
Console.WriteLine("classDerivedObject1.Equals(classBaseObject3) = {0}", classDerivedObject1.Equals(classBaseObject3));
Console.WriteLine("classBaseObject3.Equals(classDerivedObject1) = {0}", classBaseObject3.Equals(classDerivedObject1));
Console.WriteLine("Compare to some other type = {0}", classDerivedObject1.Equals(i));
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
上面測試程式碼的執行結果如下:
衍生類別的物件成員值比較
classDerivedObject1.Equals(classDerivedObject2) = True
classDerivedObject1 == classDerivedObject2 = True
classDerivedObject1.Equals(classDerivedObject3) = True
classDerivedObject1.Equals(classBaseObject3) = False
classBaseObject3.Equals(classDerivedObject1) = False
Compare to some other type = False
Press any key for continuing...
最後,我們要來進行 基底類別的物件成員值比較
Console.WriteLine("基底類別的物件成員值比較");
TwoDPoint classBaseObject1 = null;
TwoDPoint classBaseObject2 = null;
Console.WriteLine("兩個物件變數都為空值的比較 : {0}", classBaseObject1 == classBaseObject2);
// 實例化基底類別的執行個體
classBaseObject1 = new TwoDPoint(3, 4);
classBaseObject2 = new TwoDPoint(3, 4);
Console.WriteLine(Environment.NewLine);
Console.WriteLine("classDerivedObject1 is TwoDPoint : {0}", classDerivedObject1 is TwoDPoint);
Console.WriteLine("classBaseObject1 is TwoDPoint : {0}", classBaseObject1 is TwoDPoint);
Console.WriteLine($"{Environment.NewLine}TwoDPoint(3, 4) == TwoDPoint(3, 4)");
Console.WriteLine(" (classBaseObject2 == classDerivedObject1) = {0}", classBaseObject2 == classBaseObject1);
Console.WriteLine($"{Environment.NewLine}底下是基底類別物件與衍生類別物件的比較");
Console.WriteLine("TwoDPoint(3, 4) == ThreeDPoint(3, 4, 5)");
Console.WriteLine(" (classBaseObject2 == classDerivedObject1) = {0}", classBaseObject2 == classDerivedObject1);
Console.WriteLine("ThreeDPoint(3, 4, 5) == TwoDPoint(3, 4)");
Console.WriteLine(" (classDerivedObject1 == classBaseObject2) = {0}", classDerivedObject1 == classBaseObject2);
Console.WriteLine("ThreeDPoint(3, 4, 5) != TwoDPoint(3, 4)");
Console.WriteLine(" (classDerivedObject1 != classBaseObject2) = {0}", classDerivedObject1 != classBaseObject2);
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
這是執行輸出結果
基底類別的物件成員值比較
兩個物件變數都為空值的比較 : True
classDerivedObject1 is TwoDPoint : True
classBaseObject1 is TwoDPoint : True
TwoDPoint(3, 4) == TwoDPoint(3, 4)
(classBaseObject2 == classDerivedObject1) = True
底下是基底類別物件與衍生類別物件的比較
TwoDPoint(3, 4) == ThreeDPoint(3, 4, 5)
(classBaseObject2 == classDerivedObject1) = False
ThreeDPoint(3, 4, 5) == TwoDPoint(3, 4)
(classDerivedObject1 == classBaseObject2) = False
ThreeDPoint(3, 4, 5) != TwoDPoint(3, 4)
(classDerivedObject1 != classBaseObject2) = True
Press any key to exit.