我正在研究如何为类构建最好的HashCode,我看到了一些算法。我看到了这个:Hash Code implementation,似乎是.NET类的HashCode方法是相似的(参见反映代码)。
所以问题是,为什么不创建上面的静态类来自动构建HashCode,只需传递我们认为是“键”的字段。
// Old version, see edit
public static class HashCodeBuilder
{
public static int Hash(params object[] keys)
{
if (object.ReferenceEquals(keys, null))
{
return 0;
}
int num = 42;
checked
{
for (int i = 0, length = keys.Length; i < length; i++)
{
num += 37;
if (object.ReferenceEquals(keys[i], null))
{ }
else if (keys[i].GetType().IsArray)
{
foreach (var item in (IEnumerable)keys[i])
{
num += Hash(item);
}
}
else
{
num += keys[i].GetHashCode();
}
}
}
return num;
}
}
并像这样使用它:
// Old version, see edit
public sealed class A : IEquatable<A>
{
public A()
{ }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as A);
}
public bool Equals(A other)
{
if(object.ReferenceEquals(other, null))
? false
: Key1 == other.Key1 && Key2 == other.Key2;
}
public override int GetHashCode()
{
return HashCodeBuilder.Hash(Key1, Key2);
}
}
总是自己的方法会更简单,不是吗?我错过了什么?
根据所有评论,我得到了以下代码:
public static class HashCodeBuilder
{
public static int Hash(params object[] args)
{
if (args == null)
{
return 0;
}
int num = 42;
unchecked
{
foreach(var item in args)
{
if (ReferenceEquals(item, null))
{ }
else if (item.GetType().IsArray)
{
foreach (var subItem in (IEnumerable)item)
{
num = num * 37 + Hash(subItem);
}
}
else
{
num = num * 37 + item.GetHashCode();
}
}
}
return num;
}
}
public sealed class A : IEquatable<A>
{
public A()
{ }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as A);
}
public bool Equals(A other)
{
if(ReferenceEquals(other, null))
{
return false;
}
else if(ReferenceEquals(this, other))
{
return true;
}
return Key1 == other.Key1
&& Key2 == other.Key2;
}
public override int GetHashCode()
{
return HashCodeBuilder.Hash(Key1, Key2);
}
}
答案 0 :(得分:11)
您的Equals方法已被破坏 - 假设具有相同哈希码的两个对象必须相等。事实并非如此。
你的哈希码方法看起来很好看,但实际上可以做一些工作 - 见下文。它意味着在任何时候调用任何值类型值和创建一个数组,但除此之外它没关系(正如SLaks指出的那样,集合处理存在一些问题)。您可能需要考虑编写一些通用的重载,以避免常见情况下的性能损失(可能是1,2,3或4个参数)。您可能还想使用foreach
循环而不是简单的for
循环,只是为了惯用。
你可以对事物进行相同的排序,但它会稍微更难和更混乱。
编辑:对于哈希代码本身,您只需要添加值。我怀疑你试图做这类事情:
int hash = 17;
hash = hash * 31 + firstValue.GetHashCode();
hash = hash * 31 + secondValue.GetHashCode();
hash = hash * 31 + thirdValue.GetHashCode();
return hash;
但是将哈希值乘以31,它不会添加 31.目前,对于相同的值,您的哈希码将始终返回相同的值,无论它们是否相同“按照相同的顺序,这是不理想的。
编辑:对于使用的哈希码,似乎存在一些混淆。我建议任何不确定的人都会阅读Object.GetHashCode
的文档,然后阅读Eric Lippert的文档blog post about hashing and equality。
答案 1 :(得分:2)
这就是我正在使用的:
public static class ObjectExtensions
{
/// <summary>
/// Simplifies correctly calculating hash codes based upon
/// Jon Skeet's answer here
/// http://stackoverflow.com/a/263416
/// </summary>
/// <param name="obj"></param>
/// <param name="memberThunks">Thunks that return all the members upon which
/// the hash code should depend.</param>
/// <returns></returns>
public static int CalculateHashCode(this object obj, params Func<object>[] memberThunks)
{
// Overflow is okay; just wrap around
unchecked
{
int hash = 5;
foreach (var member in memberThunks)
hash = hash * 29 + member().GetHashCode();
return hash;
}
}
}
使用示例:
public class Exhibit
{
public virtual Document Document { get; set; }
public virtual ExhibitType ExhibitType { get; set; }
#region System.Object
public override bool Equals(object obj)
{
return Equals(obj as Exhibit);
}
public bool Equals(Exhibit other)
{
return other != null &&
Document.Equals(other.Document) &&
ExhibitType.Equals(other.ExhibitType);
}
public override int GetHashCode()
{
return this.CalculateHashCode(
() => Document,
() => ExhibitType);
}
#endregion
}
答案 2 :(得分:1)
您应该尝试将其投放到keys[i].GetType().IsArray
(使用IEnumerable
关键字),而不是调用as
。
您可以通过使用代表集合注册静态字段列表like I do here来修复Equals
方法,而无需重复字段列表。
这也避免了每次调用的数组分配。
但请注意,我的代码不处理集合属性。