簡體   English   中英

為什么GetHashCode實現與Equals相同的邏輯?

[英]Why should GetHashCode implement the same logic as Equals?

此MSDN頁面中,它說:

警告:

如果覆蓋GetHashCode方法,則還應該覆蓋Equals,反之亦然。 如果在測試兩個對象是否相等時覆蓋的Equals方法返回true,則覆蓋的GetHashCode方法必須為兩個對象返回相同的值

我也看到了許多類似的建議,並且我可以理解,當覆蓋Equals方法時,我也想覆蓋GetHashCode。 據我所知,GetHashCode與哈希表查找一起使用,這與相等性檢查不同。

這是一個示例,可以幫助解釋我要問的問題:

public class Temperature /* Immutable */
{
    public Temperature(double value, TemperatureUnit unit) { ... }

    private double Value { get; set; }
    private TemperatureUnit Unit { get; set; }

    private double GetValue(TemperatureUnit unit)
    {
        /* return value converted into the specified unit */
    }

    ...

    public override bool Equals(object obj)
    {
        Temperature other = obj as Temperature;
        if (other == null) { return false; }
        return (Value == other.GetValue(Unit));
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode() + Unit.GetHashCode();
    }
}

在此示例中,即使兩個溫度對象在內部未存儲相同的內容,也認為它們是相等的(例如295.15 K == 22攝氏度)。 目前,GetHashCode方法將為每個方法返回不同的值。 這兩個溫度對象相等但它們也不相同,因此具有不同的哈希碼是否正確?

當在哈希表中存儲值(例如Dictionary<> ,框架將首先調用GetHashCode()並檢查哈希表中是否已經有該哈希碼的存儲桶 如果存在,它將調用.Equals()來查看新值是否確實等於現有值。 如果不是(這意味着兩個對象不同,但是導致相同的哈希碼),則發生沖突。 在這種情況下,此存儲桶中的項目將存儲為鏈接列表,並且檢索某個值將變為O(n)。

如果您實現GetHashCode() ,但沒有實現Equals() ,該框架將訴諸使用參考平等檢查,這將導致每個實例創建碰撞平等。

如果您實現Equals() ,但沒有實現GetHashCode() ,你可以在這里你有這樣的人相等的兩個對象,但造成了不同的散列碼意味着他們會保持在你的哈希表自己獨立的價值觀碰到的情況。 這可能會使使用您的課程的任何人感到困惑。

至於什么對象被認為是相等的,則取決於您。 如果我根據溫度創建一個哈希表,是否可以使用攝氏溫度或華氏溫度值引用同一項目? 如果是這樣,它們需要得到相同的哈希值, 並且 Equals()需要返回true。

更新:

讓我們退后一步,首先了解一下哈希碼的用途。 在這種情況下,將哈希碼用作識別兩個對象是否最有可能相等的快速方法。 如果我們有兩個具有不同哈希碼的對象,那么我們就知道它們相等。 如果我們有兩個具有相同哈希碼的對象,我們知道它們很可能相等。 我說這很可能是因為int只能用來表示數十億個可能的值,而字符串當然可以包含Charles Dickens的完整著作或任何數量的可能值。 .NET框架中的大部分內容都是基於這些事實,因此使用您的代碼的開發人員將假定事情的工作方式與框架的其余部分保持一致。

如果要有兩個具有不同哈希碼的實例,但是要實現一個返回true的Equals()實現,則您將違反此約定。 比較兩個對象的開發人員隨后可能會使用這些對象之一來引用哈希表中的鍵,並期望獲得現有值。 如果突然之間哈希碼不同,則該代碼可能會導致運行時異常。 或者返回對完全不同的對象的引用。

在您的程序域內295.15k和22C是否相等是您的選擇(我認為不是)。 但是,無論您決定什么,相等的對象都必須返回相同的具有代碼的對象。

警告

如果覆蓋GetHashCode方法,則還應該覆蓋Equals,反之亦然。 如果在測試兩個對象是否相等時重寫的Equals方法返回true,則重寫的GetHashCode方法必須為兩個對象返回相同的值。

這是.NET庫中的約定 它不是在編譯時甚至在運行時都強制執行的,但是.NET庫(以及可能的任何其他外部庫)中的代碼希望此語句始終為真:

如果兩個對象從Equals返回true ,則它們將返回相同的哈希碼

和:

如果兩個對象返回不同的哈希碼,則它們相等

如果您不遵循該約定,那么您的代碼將被破壞。 更糟糕的是,它可能會以難以追蹤的方式中斷(例如,將兩個相同的對象放入字典中,或者從字典中獲得與您期望的對象不同的對象)。

因此,請遵循約定 ,否則您將引起很多悲傷。

在您的特定類中,您需要確定:當單位不同時, Equals返回false,或者不管單位如何, GetHashCode返回相同的哈希碼 您不能同時擁有這兩種方式。

因此,您可以這樣做:

public override bool Equals(object obj)
{
    Temperature other = obj as Temperature;
    if (other == null) { return false; }
    return (Value == other.Value && Unit == other.Unit);
}

或者您這樣做:

public override int GetHashCode()
{
    // note that the value returned from ConvertToSomeBaseUnit
    // should probably be cached as a private member 
    // especially if your class is supposed to immutable
    return Value.ConvertToSomeBaseUnit().GetHashCode();
}

請注意,沒有什么可以阻止您同時實施:

public bool TemperaturesAreEqual(Temperature other)
{
    if (other == null) { return false; }
    return (Value == other.GetValue(Unit));
}

當您想知道兩個溫度是否代表相同的物理溫度時,可以使用該單位。

兩個相等的對象應返回相同的HashCode(兩個不同的對象也可以返回相同的哈希碼,但這是沖突)。

在您的情況下,您的equals和hashcode實現都不是一個好方法。 問題在於對象的“實際值”取決於參數:沒有單個屬性定義對象的值。 您僅存儲初始單位以進行相等比較。

那么,為什么不對Temperature Value有什么內部定義呢?

我會像這樣實現它:

public class Temperature
{
    public Temperature(double value, TemperatureUnit unit) { 
       Value = ConvertValue(value, unit, TemperatureUnit.Celsius);
    }

    private double Value { get; set; }

    private double ConvertValue(double value, TemperatureUnit originalUnit,  TemperatureUnit targetUnit)
    {
       /* return value from originalUnit converted to targetUnit */
    }
    private double GetValue(TemperatureUnit unit)
    {
       return ConvertValue(value, TemperatureUnit.Celsius, unit);
    }    
    public override bool Equals(object obj)
    {
        Temperature other = obj as Temperature;
        if (other == null) { return false; }
        return (Value == other.Value);
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
}

這樣,您的內部Value將定義兩個對象是否相同,並且始終以同一單位表示。

您實際上並不在乎對象具有什么Unit :這沒有任何意義,因為要取回值,您將始終傳遞值。 僅將其傳遞給初始轉換才有意義。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM