简体   繁体   English

为这个结构体实现 GetHashCode 的正确方法

[英]Right way to implement GetHashCode for this struct

I want to use a date range (from one date to another date) as a key for a dictionary, so I wrote my own struct:我想使用日期范围(从一个日期到另一个日期)作为字典的键,所以我编写了自己的结构:

   struct DateRange
   {
      public DateTime Start;
      public DateTime End;

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

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

What's the best way to implement GetHashCode so no two objects of a differing range will generate the same hash?实现 GetHashCode 以便不同范围的两个对象不会生成相同的哈希值的最佳方法是什么? I want hash collisions to be as unlikely as possible, though I understand Dictionary<> will still check the equality operator which I will also implement, but didn't want to pollute the example code too much.我希望哈希冲突尽可能不可能发生,尽管我知道 Dictionary<> 仍然会检查我也将实现的相等运算符,但不想过多地污染示例代码。 Thanks!谢谢!

You can use the method from Effective Java as Jon Skeet shows here .您可以使用 Effective Java 中的方法,正如 Jon Skeet 在此处展示的那样。 For your specific type:对于您的特定类型:

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;
    }
}

In C# 7 you can do this:C# 7你可以这样做:

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

The ValueTuple is available in .NET Framework 4.7 and .NET Core , or via NuGet . ValueTuple.NET Framework 4.7.NET Core可用,或通过NuGet 提供

Not sure how well it performs, but I would be surprised if any custom code would beat it.不确定它的性能如何,但如果任何自定义代码能打败它,我会感到惊讶。

I would trust Microsoft's implementation of GetHashCode() at the tuples and use something like this without any stupid magic:我会相信微软在元组中实现 GetHashCode() 并使用这样的东西,没有任何愚蠢的魔法:

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

Not to reanimate the dead, but I came here looking for something, and for newer C# versions you can do不是为了让死者复活,但我来这里是为了寻找一些东西,对于较新的 C# 版本,您可以这样做

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

The source can currently be found here: https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/HashCode.cs源目前可以在这里找到: https : //github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/HashCode.cs

In my preliminary tests (using Jon Skeets micro benchmarking framework) it appears to be very similar if not the same as the accepted answer, in terms of performance.在我的初步测试中(使用 Jon Skeets 微基准测试框架),就性能而言,它似乎与公认的答案非常相似,如果不一样的话。

Since DateTime.GetHashCode is internally based on Ticks, what about this:由于 DateTime.GetHashCode 是内部基于 Ticks 的,那么这个呢:

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

Or, since you seem to be interested by the date parts (year, month, day), not the whole thing, this implementation uses the number of days between the two dates and should give almost no collision:或者,由于您似乎对日期部分(年、月、日)而不是全部感兴趣,因此此实现使用两个日期之间的天数,并且几乎不会产生冲突:

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

Just as it might help someone in the future that uses visual studio pro (not sure if this also exist in community edition)就像它可能会帮助将来使用 Visual Studio Pro 的人一样(不确定社区版中是否也存在这种情况)

  • Select the desired properties (in your case all)选择所需的属性(在您的情况下全部)
  • Press refacoring (CTRL + . or right click "Quick actions an refactorings")按重构(CTRL + . 或右键单击“快速操作和重构”)
  • Now you can select to implement Equals or GetHashcode (and probably it always takes the best known MS way to do it)现在您可以选择实现 Equals 或 GetHashcode(并且可能总是采用最知名的 MS 方式来实现)

Something like this:) with a different prime number:)像这样:) 具有不同的质数:)

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;
    }
}

This is not the fastest implementation but it produces a good hash code.这不是最快的实现,但它产生了一个很好的哈希码。 Joshua bloch indicates that as well and you also calculate the performance, ^ is usually faster. Joshua bloch 也指出,并且您还计算了性能,^ 通常更快。 correct me if im wrong.如果我错了纠正我。

See Jon Skeets impl for c# :参见 Jon Skeets impl for c# :

Combining Jon Skeet's answer and comment on the question (so please, no voting on this, just consolidating):结合 Jon Skeet 的回答和对该问题的评论(所以请不要对此投票,只是巩固):

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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM