为此结构实现GetHashCode的正确方法

时间:2011-10-18 21:03:17

标签: c# .net hash

我想使用日期范围(从一个日期到另一个日期)作为字典的键,所以我编写了自己的结构:

   struct DateRange
   {
      public DateTime Start;
      public DateTime End;

      public DateRange(DateTime start, DateTime end)
      {
         Start = start.Date;
         End = end.Date;
      }

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

实现GetHashCode的最佳方法是什么,所以没有两个不同范围的对象会生成相同的哈希?我希望哈希冲突尽可能不可能,但我理解Dictionary<>仍将检查我将实现的相等运算符,但不想过多地污染示例代码。谢谢!

8 个答案:

答案 0 :(得分:19)

您可以使用Effective Java中的方法,因为Jon Skeet显示here。对于您的特定类型:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        hash = hash * 23 + Start.GetHashCode();
        hash = hash * 23 + End.GetHashCode();
        return hash;
    }
}

答案 1 :(得分:7)

我相信微软在元组中实现GetHashCode()并使用类似的东西而没有任何愚蠢的魔法:

public override int GetHashCode()
{
    Tuple.Create(x, y).GetHashCode();
}

答案 2 :(得分:3)

由于DateTime.GetHashCode在内部基于Ticks,所以:

    public override int GetHashCode()
    {
        return unchecked((int)(Start.Ticks ^ End.Ticks));
    }

或者,既然您似乎对日期部分(年,月,日)感兴趣,而不是整个事情,这个实现使用两个日期之间的天数,并且几乎不会发生冲突:

        public override int GetHashCode()
        {
            return unchecked((int)Start.Date.Year * 366 + Start.Date.DayOfYear + (End.Date - Start.Date).Days);
        }

答案 3 :(得分:3)

不是为了让死者复活,而是我来这里是为了寻找一些东西,对于较新的 C# 版本,您可以这样做

public override int GetHashCode()
{
    return HashCode.Combine(Start, End);
}

目前可以在这里找到来源:https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/HashCode.cs

在我的初步测试中(使用 Jon Skeets 微基准测试框架),在性能方面,它似乎与公认的答案非常相似。

答案 4 :(得分:2)

C# 7中,您可以这样做:

public override int GetHashCode() => (Start, End).GetHashCode();

ValueTuple.NET Framework 4.7.NET Core中或通过NuGet可用。

不确定其性能如何,但是如果有任何自定义代码会击败它,我会感到惊讶。

答案 5 :(得分:2)

就像将来可能会帮助使用Visual Studio Pro的人一样(不确定社区版中是否还存在这种情况)

  • 选择所需的属性(在您的情况下全部选择)
  • 按重新整理(Ctrl +。或右键单击“重构的快速操作”)
  • 现在您可以选择实现Equals或GetHashcode(并且可能总是采用最著名的MS方法)

答案 6 :(得分:1)

像这样:)有不同的素数:)

public override int GetHashCode()
{
    unchecked  
    {
        int hash = 23;
        // Suitable nullity checks etc, of course :)
        hash = hash * 31 + Start.GetHashCode();
        hash = hash * 31 + End.GetHashCode();
        return hash;
    }
}

这不是最快的实现,但它产生了一个很好的哈希码。 Joshua bloch表示同样也计算了性能,^通常更快。如果我错了,请纠正我。

请参阅Jon Skeets impl了解c#:

答案 7 :(得分:0)

结合Jon Skeet的answer并对question发表评论(所以请不要对此进行投票,只需合并):

struct DateRange
{
    private readonly DateTime start;

    private readonly DateTime end;

    public DateRange(DateTime start, DateTime end)
    {
        this.start = start.Date;
        this.end = end.Date;
    }

    public DateTime Start
    {
        get
        {
            return this.start;
        }
    }

    public DateTime End
    {
        get
        {
            return this.end;
        }
    }

    public static bool operator ==(DateRange dateRange1, DateRange dateRange2)
    {
        return dateRange1.Equals(dateRange2);
    }

    public static bool operator !=(DateRange dateRange1, DateRange dateRange2)
    {
        return !dateRange1.Equals(dateRange2);
    }

    public override int GetHashCode()
    {
        // Overflow is fine, just wrap
        unchecked
        {
            var hash = 17;

            // Suitable nullity checks etc, of course :)
            hash = (23 * hash) + this.start.GetHashCode();
            hash = (23 * hash) + this.end.GetHashCode();
            return hash;
        }
    }

    public override bool Equals(object obj)
    {
        return (obj is DateRange)
            && this.start.Equals(((DateRange)obj).Start)
            && this.end.Equals(((DateRange)obj).End);
    }
}