Right way to implement GetHashCode for this struct

15,508

Solution 1

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

Solution 2

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.

Solution 3

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

Solution 4

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

Solution 5

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.

Share:
15,508
Mike Christensen
Author by

Mike Christensen

Founder and Chief Architect of KitchenPC.com, the world's most powerful recipe search engine. The technology behind KitchenPC is open-source, and available on GitHub.

Updated on June 24, 2022

Comments

  • Mike Christensen
    Mike Christensen about 2 years

    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!

  • Jesse C. Slicer
    Jesse C. Slicer over 12 years
    -1: while this is good, it's a pretty close to direct lift from here: stackoverflow.com/questions/263400/… without attribution.
  • DarthVader
    DarthVader over 12 years
    very funny. attribution was to Joshua bloch which that links get this from.. thanks for the downvote.
  • Jesse C. Slicer
    Jesse C. Slicer over 12 years
    Your four retribution downvotes are also totally unnecessary.
  • DarthVader
    DarthVader over 12 years
    i dont want to make it personal.but your comment was pointless, and i did give the credits to joshua bloch, who has pointed this hashcode impl. in his book.
  • Joe White
    Joe White over 12 years
    Why are people downvoting this and upvoting Mark Byers' answer, when they're essentially the same answer?
  • DarthVader
    DarthVader over 12 years
    Because I m on the dark side. i guess that s why.
  • Jesse C. Slicer
    Jesse C. Slicer over 12 years
    I downvoted because, while perhaps originally discussed on Joshua Bloch's book, the C# code presented is clearly Jon Skeet's implementation (including the comment), which was not credited. Mark Byers' answer DID credit the code source.
  • Jesse C. Slicer
    Jesse C. Slicer over 12 years
    Now there you go. Removing my downvote. Next time, don't make Obi-Wan more powerful than we could possibly imagine.
  • Jesse C. Slicer
    Jesse C. Slicer over 12 years
    Of course, looking at it now, seems like it would be a good idea to override 'Equals()', 'operator==' and 'operator!=' too.
  • Max Kilovatiy
    Max Kilovatiy about 8 years
    Could you please explain, why do you use 17 and 23?
  • Martin
    Martin almost 8 years
    @MaxKvt because they're prime numbers
  • The incredible Jan
    The incredible Jan about 7 years
    @Martin And where is the benefit from multiplication with prime numbers?
  • Herman
    Herman about 6 years
    @TheincredibleJan My guess is that it improves the distribution of values after the modulo is applied (in this case modulo is implicit when int overflows). I'm not sure about this but I think (x * prime) mod y will not generate a collision for values of x between 0 and y.
  • l33t
    l33t almost 5 years
    No!! You must absolutely NOT create heap objects in GetHashCode(). This code will eventually kill application performance.
  • Benji Altman
    Benji Altman almost 4 years
    I'm not sure how new this solution is, but when I tried that Visual Studio gave me a code hint of System.HashCode.Combine(Start, End).
  • dynamichael
    dynamichael over 3 years
    Note: System.HashCode is only available in .Net Core
  • Mike Christensen
    Mike Christensen over 3 years
    Would be curious to know how it compares to the accepted answer in terms of perf..
  • VisualBean
    VisualBean over 3 years
    I checked it out. And it appears to be VERY similar over 10000000 runs.
  • Mike Christensen
    Mike Christensen over 3 years
    Cool, I bet the code is basically the same and the compiler inlines the function..
  • WDUK
    WDUK almost 3 years
    If every hash has 17 and a factor of 23 then surely they can be factored out between all hashes wouldn't it be the same as just adding the hash codes of start and end together and be done with it ?
  • dynamichael
    dynamichael over 2 years
    The Tuple class will add to the heap. Instead (with C# 8 up), use just (x, y).GetHashCode(), which goes on the stack it's a value type.
  • WDUK
    WDUK about 2 years
    Don't you need to implement an interface aswell for hash sets and dictionaries to use it?