GetHashCode override of object containing generic array

前端 未结 9 1621
情歌与酒
情歌与酒 2020-12-04 05:35

I have a class that contains the following two properties:

public int Id      { get; private set; }
public T[] Values  { get; private set; }
<
相关标签:
9条回答
  • 2020-12-04 05:41

    Provided that Id and Values will never change, and Values is not null...

    public override int GetHashCode()
    {
      return Id ^ Values.GetHashCode();
    }
    

    Note that your class is not immutable, since anyone can modify the contents of Values because it is an array. Given that, I wouldn't try to generate a hashcode using its contents.

    0 讨论(0)
  • 2020-12-04 05:47

    How about something like:

        public override int GetHashCode()
        {
            int hash = Id;
            if (Values != null)
            {
                hash = (hash * 17) + Values.Length;
                foreach (T t in Values)
                {
                    hash *= 17;
                    if (t != null) hash = hash + t.GetHashCode();
                }
            }
            return hash;
        }
    

    This should be compatible with SequenceEqual, rather than doing a reference comparison on the array.

    0 讨论(0)
  • 2020-12-04 05:47

    I would do it this way:

    long result = Id.GetHashCode();
    foreach(T val in Values)
        result ^= val.GetHashCode();
    return result;
    
    0 讨论(0)
  • 2020-12-04 05:50

    I just had to add another answer because one of the more obvious (and easiest to implement) solutions were not mentioned - not including the collection in your GetHashCode calculation!

    The main thing that seemed to have forgotten here is that the uniqueness from the result of GetHashCode isn't required (or in many cases even possible). Unequal objects don't have to return unequal hash codes, the only requirement is that equal objects return equal hash codes. So by that definition, the following implementation of GetHashCode is correct for all objects (assuming there's a correct Equals implementation):

    public override int GetHashCode() 
    { 
        return 42; 
    } 
    

    Of course this would yield the worst possible performance in hashtable lookup, O(n) instead of O(1), but it is still functionally correct.

    With that in mind, my general recommendation when implementing GetHashCode for an object that happens to have any kind of collection as one or more of its members is to simply ignore them and calculate GetHashCode solely based on the other scalar members. This would work pretty well except if you put into a hash table a huge number of objects where all their scalar members have identical values, resulting in identical hash codes.

    Ignoring collection members when calculating the hash code can also yield a performance improvement, despite the decreased distribution of the hash code values. Remember that using a hash code is supposed to improve performance in a hash table by not requiring to call Equals N times, and instead will only require calling GetHashCode once and a quick hash table lookup. If each object has an inner array with 10,000 items which all participate in the calculation of the hash code, any benefits gained by the good distribution would probably be lost. It would be better to have a marginally less distributed hash code if generating it is considerably less costly.

    0 讨论(0)
  • 2020-12-04 05:51

    Because of the problems raised in this thread, I'm posting another reply showing what happens if you get it wrong... mainly, that you can't use the array's GetHashCode(); the correct behaviour is that no warnings are printed when you run it... switch the comments to fix it:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    static class Program
    {
        static void Main()
        {
            // first and second are logically equivalent
            SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6),
                second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6);
    
            if (first.Equals(second) && first.GetHashCode() != second.GetHashCode())
            { // proven Equals, but GetHashCode() disagrees
                Console.WriteLine("We have a problem");
            }
            HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>();
            set.Add(first);
            set.Add(second);
            // which confuses anything that uses hash algorithms
            if (set.Count != 1) Console.WriteLine("Yup, very bad indeed");
        }
    }
    class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>>
    {
    
        public SimpleTableRow(int id, params T[] values) {
            this.Id = id;
            this.Values = values;
        }
        public int Id { get; private set; }
        public T[] Values { get; private set; }
    
        public override int GetHashCode() // wrong
        {
            return Id.GetHashCode() ^ Values.GetHashCode();
        }
        /*
        public override int GetHashCode() // right
        {
            int hash = Id;
            if (Values != null)
            {
                hash = (hash * 17) + Values.Length;
                foreach (T t in Values)
                {
                    hash *= 17;
                    if (t != null) hash = hash + t.GetHashCode();
                }
            }
            return hash;
        }
        */
        public override bool Equals(object obj)
        {
            return Equals(obj as SimpleTableRow<T>);
        }
        public bool Equals(SimpleTableRow<T> other)
        {
            // Check for null
            if (ReferenceEquals(other, null))
                return false;
    
            // Check for same reference
            if (ReferenceEquals(this, other))
                return true;
    
            // Check for same Id and same Values
            return Id == other.Id && Values.SequenceEqual(other.Values);
        }
    }
    
    0 讨论(0)
  • 2020-12-04 05:56

    I know this thread is pretty old, but I wrote this method to allow me to calculate hashcodes of multiple objects. It's been very helpful for this very case. It's not perfect, but it does meet my needs and most likely yours too.

    I can't really take any credit for it. I got the concept from some of the .net gethashcode implementations. I'm using 419 (afterall, it's my favorite large prime), but you can choose just about any reasonable prime (not too small . . . not too large).

    So, here's how I get my hashcodes:

    using System.Collections.Generic;
    using System.Linq;
    
    public static class HashCodeCalculator
    {
        public static int CalculateHashCode(params object[] args)
        {
            return args.CalculateHashCode();
        }
    
        public static int CalculateHashCode(this IEnumerable<object> args)
        {
            if (args == null)
                return new object().GetHashCode();
    
            unchecked
            {
                return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode());
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题