简体   繁体   中英

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? 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. Thanks!

You can use the method from Effective Java as Jon Skeet shows here . 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:

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

The ValueTuple is available in .NET Framework 4.7 and .NET Core , or via 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:

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

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

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.

Since DateTime.GetHashCode is internally based on Ticks, what about this:

    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)

  • Select the desired properties (in your case all)
  • Press refacoring (CTRL + . or right click "Quick actions an refactorings")
  • Now you can select to implement Equals or GetHashcode (and probably it always takes the best known MS way to do it)

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. correct me if im wrong.

See Jon Skeets impl for c# :

Combining Jon Skeet's answer and comment on the question (so please, no voting on this, just consolidating):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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