[英]GetHashCode override of object containing generic array
我有一個包含以下兩個屬性的類:
public int Id { get; private set; }
public T[] Values { get; private set; }
我已IEquatable<T>
和重寫所述object.Equals
這樣的:
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?
}
由於這個帖子中出現的問題,我發布了另一個回復,顯示如果你弄錯了會發生什么......主要是你不能使用數組的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);
}
}
FWIW,在哈希碼中使用Values的內容非常危險。 如果您能保證永遠不會改變,那么您應該這樣做。 但是,由於它暴露,我不認為保證它是可能的。 對象的哈希碼永遠不會改變。 否則,它將作為Hashtable或Dictionary中的鍵丟失其值。 考慮使用對象作為Hashtable中的鍵的難以發現的錯誤,其哈希碼因外部影響而發生變化,您無法再在Hashtable中找到它!
由於hashCode有點存儲對象的密鑰(lleeke在哈希表中),我只使用Id.GetHashCode()
怎么樣的:
public override int GetHashCode()
{
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;
}
這應該與SequenceEqual
兼容,而不是在數組上進行參考比較。
我只需要添加另一個答案,因為沒有提到一個更明顯(並且最容易實現)的解決方案 - 不包括GetHashCode
計算中的集合!
這里似乎忘記的主要事情是GetHashCode
結果的唯一性不是必需的(或者在許多情況下甚至可能)。 不等的對象不必返回不等的哈希碼,唯一的要求是等對象返回相等的哈希碼。 因此,根據該定義, GetHashCode
的以下實現對於所有對象都是正確的(假設有正確的Equals
實現):
public override int GetHashCode()
{
return 42;
}
當然,這將在哈希表查找中產生最差的性能,O(n)而不是O(1),但它仍然在功能上是正確的。
考慮到這一點,我在為一個碰巧擁有任何類型集合作為其一個或多個成員的對象實現GetHashCode
時的一般建議是簡單地忽略它們並僅基於其他標量成員計算GetHashCode
。 這可以很好地工作,除非你在哈希表中放入大量的對象,其中所有的標量成員具有相同的值,從而產生相同的哈希碼。
盡管哈希碼值的分布減少,但在計算哈希碼時忽略收集成員也可以產生性能改進。 請記住,使用散列碼應該通過不需要調用,以提高哈希表的性能Equals
N次,而是只需要調用GetHashCode的一次快速的哈希表查找。 如果每個對象都有一個包含10,000個項目的內部數組,這些項目都參與哈希碼的計算,那么良好分布所帶來的任何好處都可能會丟失。 如果生成它的成本要低得多,那么使用稍微分散的哈希代碼會更好。
public override int GetHashCode() {
return Id.GetHashCode() ^ Values.GetHashCode();
}
評論和其他答案有幾個好處。 如果對象用作字典中的鍵,則OP應考慮值是否將用作“鍵”的一部分。 如果是這樣,那么它們應該是哈希碼的一部分,否則就不是。
另一方面,我不確定為什么GetHashCode方法應該鏡像SequenceEqual。 它意味着計算哈希表的索引,而不是完全相等的決定因素。 如果使用上述算法存在許多哈希表沖突,並且如果它們在值的序列中不同,則應選擇考慮序列的算法。 如果順序並不重要,請節省時間,不要將其考慮在內。
我知道這個線程已經很老了,但是我寫了這個方法來允許我計算多個對象的哈希碼。 對於這種情況,它非常有用。 它並不完美,但它確實滿足了我的需求,而且很可能也符合你的需求。
我真的不能相信它。 我從一些.net gethashcode實現中得到了這個概念。 我正在使用419(畢竟,這是我最喜歡的大素數),但你可以選擇任何合理的素數(不是太小......不是太大)。
所以,這是我如何得到我的哈希碼:
using System.Collections.Generic;
using System.Linq;
public static class HashCodeCalculator
{
public static int CalculateHashCode(params object[] args)
{
return args.CalculateHashCode();
}
public static int CalculateHashCode(this IEnumerable<object> args)
{
if (args == null)
return new object().GetHashCode();
unchecked
{
return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode());
}
}
}
我會這樣做:
long result = Id.GetHashCode();
foreach(T val in Values)
result ^= val.GetHashCode();
return result;
假設Id和Values永遠不會改變,並且Values不為null ...
public override int GetHashCode()
{
return Id ^ Values.GetHashCode();
}
請注意,您的類不是不可變的,因為任何人都可以修改Values的內容,因為它是一個數組。 鑒於此,我不會嘗試使用其內容生成哈希碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.