[C#] 011、012. 區別對待 == 和 Equals 、覆寫Equals時也要覆寫GetHashCode (==表示參考位址,Equals表示值)

讀【157個完美化C#的建議】一書的理解筆記 - 011、012

重點:==用於參考比較(記憶體),Equals用於值的比較。Dictionary用HashCode比較

流程說明

1. == 與 Equals 差異

2. 覆寫Equals 範例
3. HashCode是什麼,覆寫範例
4. 結論

1. == 與 Equals 差異

以下範例可以得知在Object的屬性中,== 一般預設為比較參考位址 ,要確切的比較任何型別的值,保險作法是用Equals

//== 範例- 引用
object A = 1;
object B = 1;
bool result_TestOne = (A == B);//False A 跟 B 是獨立物件,引用不相等----引用
bool result_TestThre = A.Equals(B);//  A 與 B 值都是1,所以相等---------值
A = B;
bool result_TestTwo = (A == B);//True A 引用了 B ,所以引用相等 --------引用
bool result_TestFour = A.Equals(B);//  A 與 B 值都是1,所以相等 --------值

 

2. 覆寫Equals 範例

我們建立【個人】的類別,如下:

/// <summary>
/// 個人
/// </summary>
public class Person
{
    /// <summary>
    /// 身分證
    /// </summary>
    public string IDCard { get; private set; }

    /// <summary>
    /// 出生時建立唯一身分證字號
    /// </summary>
    /// <param name="inputId"></param>
    public Person(string inputId)
    {
        this.IDCard = inputId;
    }

    /// <summary>
    /// Equals 用於比較值 ==用於比較參考(記憶體位址)
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public override bool Equals(object obj)
    {
        //比較身分證上的ID
        return this.IDCard == (obj as Person).IDCard;
    }
}

其中覆寫的Equals 的程式碼如下:

再傳進一個類別時,會強制轉為型別Person(※正確做法請參考 本系列003篇(IS與AS的用法),這邊為了講解省略細節)

並且進行.IDCard的屬性比較。

/// <summary>
/// Equals 用於比較值 ==用於比較參考(記憶體位址)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
    //比較身分證上的ID
    return this.IDCard == (obj as Person).IDCard;
}

實際主程式會執行以下(類別直接比較會對記憶體比較,但我們覆寫了Equals):

//== 範例 何時覆寫 Equals
Person person_A = new Person("123");
Person person_B = new Person("123");

//True 因為值相同
bool isEqualAB = person_A.Equals(person_B);
//False 因為記憶體位址不同
bool isMemoryAB = (person_A == person_B);

3. HashCode是什麼,覆寫範例

請參考以下,我們建立一個Dictionary < , > 

static Dictionary<Person, PersonDetail> PersonValues = new Dictionary<Person, PersonDetail>();

其中Person 為範例2的宣告,PersonDetail 為個人的文件檔案,如下:

/// <summary>
/// 個人文件資料
/// </summary>
public class PersonDetail
{
           public string FileName { get; set; }
       
}

並且建立以下Function

static void AddPerson()
{
    //123這個人
    Person person_123 = new Person("123");
    //123的個人文件
    PersonDetail detail_123 = new PersonDetail() { FileName = "123文件" };
    //加入字典中
    PersonValues.Add(person_123, detail_123);
    //True 表示字典裡有這個人(Person 類別)的資料 
    bool get = PersonValues.ContainsKey(person_123);
}

 

在主程式執行以下程式的GetKey會發現 得到False ,但是我們一開始就將值123放入了,為何會有這個結果呢?

原因就是HashCode 不一樣

//==========  GetHash Code 到底是什麼 : 每個物件真正的代碼
//靜態建立
AddPerson();
//再次建立123這個人
Person person_123 = new Person("123");
//理論上AddPerson()已經將資料加入靜態函示中所以要從字典找得到
//結果False
bool get = PersonValues.ContainsKey(person_123);

令工程師難以接受,必須有以下覆寫HashCode的做法

我們將Person 類別覆寫GetHashCode,完整Code如下:

/// <summary>
/// 個人
/// </summary>
public class Person
{
    /// <summary>
    /// 身分證
    /// </summary>
    public string IDCard { get; private set; }

    /// <summary>
    /// 出生時建立唯一身分證字號
    /// </summary>
    /// <param name="inputId"></param>
    public Person(string inputId)
    {
        this.IDCard = inputId;
    }

    /// <summary>
    /// Equals 用於比較值 ==用於比較參考(記憶體位址)
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public override bool Equals(object obj)
    {
        //比較身分證上的ID
        return this.IDCard == (obj as Person).IDCard;
    }

    /// <summary>
    /// 標準的Equal 必須覆寫Hash Code
    /// 沒有HashCode Dictionary 就會找不到對應的值
    /// </summary>
    /// <returns></returns>
    public override int GetHashCode()
    {
        //覆寫IDCard 所以 GetHashCode 也要覆寫 IDCard.GetHashCode()
        return this.IDCard.GetHashCode();
    }
}

再次執行以下代碼得到 True ,Dictionary 也就可以正常使用了,享受精準又快速的Dictionary的查詢吧~。

//結果True
bool get = PersonValues.ContainsKey(person_123);

 

4. 結論

  比較對象 覆寫目的
Equals 資料一致性、正確性
== 參考(記憶體)
HashCode Dictionary 唯一Key

軟體工程中默許 Equals 比較值、==比較記憶體

github連結(Vs2015) : 點我下載