c# array length




GetHashCode重寫包含泛型數組的對象 (6)

我有一個包含以下兩個屬性的類:

public int Id      { get; private set; }
public T[] Values  { get; private set; }

我已經使它成為IEquatable<T>並覆蓋了IEquatable<T> ,如下所示:

public override bool Equals(object obj)
{
    return Equals(obj as SimpleTableRow<T>);
}

public bool Equals(SimpleTableRow<T> other)
{
    // Check for null
    if(ReferenceEquals(other, null))
        return false;

    // Check for same reference
    if(ReferenceEquals(this, other))
        return true;

    // Check for same Id and same Values
    return Id == other.Id && Values.SequenceEqual(other.Values);
}

當有覆蓋object.Equals我當然也必須覆蓋GetHashCode 。 但是我應該實現什麼代碼? 如何從通用數組中創建哈希碼? 我如何將它與Id整數結合起來?

public override int GetHashCode()
{
    return // What?
}

FWIW,在哈希碼中使用Values的內容非常危險。 如果您能保證永遠不會改變,那麼您應該這樣做。 但是,由於它暴露,我不認為保證它是可能的。 對象的哈希碼永遠不會改變。 否則,它將作為Hashtable或Dictionary中的鍵丟失其值。 考慮使用對像作為Hashtable中的鍵的難以發現的錯誤,其哈希碼因外部影響而發生變化,您無法再在Hashtable中找到它!


假設Id和Values永遠不會改變,並且Values不為null ...

public override int GetHashCode()
{
  return Id ^ Values.GetHashCode();
}

請注意,您的類不是不可變的,因為任何人都可以修改Values的內容,因為它是一個數組。 鑑於此,我不會嘗試使用其內容生成哈希碼。


我只需要添加另一個答案,因為沒有提到一個更明顯(並且最容易實現)的解決方案 - 不包括GetHashCode計算中的集合!

這裡似乎忘記的主要事情是GetHashCode結果的唯一性不是必需的(或者在許多情況下甚至可能)。 不等的對像不必返回不等的哈希碼,唯一的要求是等對象返回相等的哈希碼。 因此,通過該定義, GetHashCode的以下實現對於所有對像都是正確的(假設有正確的Equals實現):

public override int GetHashCode() 
{ 
    return 42; 
} 

當然,這會產生散列表查找中最差的性能,O(n)而不是O(1),但它仍然在功能上是正確的。

考慮到這一點,我在為一個碰巧擁有任何類型的集合作為其一個或多個成員的對象實現GetHashCode時的一般建議是簡單地忽略它們並僅基於其他標量成員計算GetHashCode 。 這將非常有效,除非您在哈希表中放入大量對象,其中所有標量成員具有相同的值,從而產生相同的哈希碼。

儘管散列碼值的分佈減少,但在計算散列碼時忽略集合成員也可以產生性能改進。 請記住,使用哈希代碼可以提高哈希表的性能,因為不需要調用N次,而只需要調用一次GetHashCode和快速哈希表查找。 如果每個對像都有一個包含10,000個項目的內部數組,這些項目都參與哈希碼的計算,那麼良好分佈所帶來的任何好處都可能會丟失。 如果生成它的成本要低得多,那麼最好有一個稍微不那麼分佈的哈希碼。


我會這樣做:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;

由於hashCode有點存儲對象的密鑰(lleeke在哈希表中),我只使用Id.GetHashCode()


由於這個帖子中出現的問題,我發布了另一個回复,顯示如果你弄錯了會發生什麼......主要是你不能使用數組的GetHashCode() ; 正確的行為是,當你運行它時沒有打印警告...切換註釋以修復它:

using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
    static void Main()
    {
        // first and second are logically equivalent
        SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6),
            second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6);

        if (first.Equals(second) && first.GetHashCode() != second.GetHashCode())
        { // proven Equals, but GetHashCode() disagrees
            Console.WriteLine("We have a problem");
        }
        HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>();
        set.Add(first);
        set.Add(second);
        // which confuses anything that uses hash algorithms
        if (set.Count != 1) Console.WriteLine("Yup, very bad indeed");
    }
}
class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>>
{

    public SimpleTableRow(int id, params T[] values) {
        this.Id = id;
        this.Values = values;
    }
    public int Id { get; private set; }
    public T[] Values { get; private set; }

    public override int GetHashCode() // wrong
    {
        return Id.GetHashCode() ^ Values.GetHashCode();
    }
    /*
    public override int GetHashCode() // right
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }
    */
    public override bool Equals(object obj)
    {
        return Equals(obj as SimpleTableRow<T>);
    }
    public bool Equals(SimpleTableRow<T> other)
    {
        // Check for null
        if (ReferenceEquals(other, null))
            return false;

        // Check for same reference
        if (ReferenceEquals(this, other))
            return true;

        // Check for same Id and same Values
        return Id == other.Id && Values.SequenceEqual(other.Values);
    }
}




hashcode