Handling collections in GetHashCode implementation
我正在基于这个答案中的hashcode结构来实现gethashcode()。因为我的equals方法将考虑使用Enumerable.SequenceEqual()的集合,所以我需要在getHashCode()实现中包含这些集合。
作为起点,我使用jon skeet的嵌入式gethashcode()实现来测试hashcode结构实现的输出。使用下面的测试,可以按预期工作-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | private class MyObjectEmbeddedGetHashCode { public int x; public string y; public DateTimeOffset z; public List<string> collection; public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + x.GetHashCode(); hash = hash * 31 + y.GetHashCode(); hash = hash * 31 + z.GetHashCode(); return hash; } } } private class MyObjectUsingHashCodeStruct { public int x; public string y; public DateTimeOffset z; public List<string> collection; public override int GetHashCode() { return HashCode.Start .Hash(x) .Hash(y) .Hash(z); } } [Test] public void GetHashCode_CollectionExcluded() { DateTimeOffset now = DateTimeOffset.Now; MyObjectEmbeddedGetHashCode a = new MyObjectEmbeddedGetHashCode() { x = 1, y ="Fizz", z = now, collection = new List<string>() { "Foo", "Bar", "Baz" } }; MyObjectUsingHashCodeStruct b = new MyObjectUsingHashCodeStruct() { x = 1, y ="Fizz", z = now, collection = new List<string>() { "Foo", "Bar", "Baz" } }; Console.WriteLine("MyObject::GetHashCode(): {0}", a.GetHashCode()); Console.WriteLine("MyObjectEx::GetHashCode(): {0}", b.GetHashCode()); Assert.AreEqual(a.GetHashCode(), b.GetHashCode()); } |
下一步是在getHashCode()计算中考虑集合。这需要在myObjecteEmbeddedGetHashCode中对getHashCode()实现进行少量添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + x.GetHashCode(); hash = hash * 31 + y.GetHashCode(); hash = hash * 31 + z.GetHashCode(); int collectionHash = 17; foreach (var item in collection) { collectionHash = collectionHash * 31 + item.GetHashCode(); } hash = hash * 31 + collectionHash; return hash; } } |
然而,这在hashcode结构中有点困难。在本例中,当类型列表的集合传递到hash方法时,t是list,因此尝试将obj强制转换为ICollection或IEnumerable不起作用。我可以成功地强制转换为IEnumerable,但这会导致装箱,我发现我必须担心排除诸如实现IEnumerable的字符串之类的类型。
在这种情况下,是否有可靠地将obj强制转换为ICollection或IEnumerable的方法?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public struct HashCode { private readonly int hashCode; public HashCode(int hashCode) { this.hashCode = hashCode; } public static HashCode Start { get { return new HashCode(17); } } public static implicit operator int(HashCode hashCode) { return hashCode.GetHashCode(); } public HashCode Hash<T>(T obj) { // I am able to detect if obj implements one of the lower level // collection interfaces. However, I am not able to cast obj to // one of them since T in this case is defined as List<string>, // so using as to cast obj to ICollection<T> or IEnumberable<T> // doesn't work. var isGenericICollection = obj.GetType().GetInterfaces().Any( x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)); var c = EqualityComparer<T>.Default; // This works but using IEnumerable causes boxing. // var h = c.Equals(obj, default(T)) ? 0 : ( !(obj is string) && (obj is IEnumerable) ? GetCollectionHashCode(obj as IEnumerable) : obj.GetHashCode()); var h = c.Equals(obj, default(T)) ? 0 : obj.GetHashCode(); unchecked { h += this.hashCode * 31; } return new HashCode(h); } public override int GetHashCode() { return this.hashCode; } } |
您可以通过以下几种方式解决收集问题:
也就是说,imho最好不要使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public override int GetHashCode() { HashCode hash = HashCode.Start .Hash(x) .Hash(y) .Hash(z); foreach (var item in collection) { hash = hash.Hash(item); } return hash; } |
如果你想要一个完整功能的
成员的命名是不同的,但是它基本上与