2017年9月13日 星期三

C# : 繼承類型 Inheritance Type 的成員實值相等

在這份筆記之中,我們將會進行有類別繼承情況下的衍生類別與基底類別,該如何實作出具有類別成員的實值比較功能。
在基底類別中,我們宣告了類別 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.

沒有留言:

張貼留言