在 GetHashCode 实现中处理集合
Handling collections in GetHashCode implementation
我正在努力根据此答案中的 HashCode 结构实现 GetHashCode() here。由于我的 Equals 方法将考虑使用 Enumerable.SequenceEqual() 的集合,因此我需要在我的 GetHashCode() 实现中包含这些集合。
首先,我使用 Jon Skeet 的嵌入式 GetHashCode() 实现来测试 HashCode 结构实现的输出。使用下面的以下测试,这按预期工作 -
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() 计算中的集合。这需要对 MyObjectEmbeddedGetHashCode 中的 GetHashCode() 实现进行少量添加。
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 结构中有点困难。在此示例中,当将类型 List 的集合传递到 Hash 方法时,T 是 List,因此尝试将 obj 转换为 ICollection 或 IEnumberable 不起作用。我可以成功转换为 IEnumerable,但它会导致装箱,我发现我不得不担心排除实现 IEnumerable 的字符串等类型。
在这种情况下,有没有办法可靠地将 obj 转换为 ICollection 或 IEnumerable?
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;
}
}
您可以通过多种方式解决收款问题:
- 使用非通用接口,例如
ICollection
或 IEnumerable
.
- 为
Hash()
方法添加重载,例如Hash<T>(IEnumerable<T> list) { ... }
也就是说,恕我直言,最好只保留 struct HashCode
并将特定于集合的代码放在实际的 GetHashCode()
方法中。例如:
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;
}
如果您确实想要 struct HashCode
类型的全功能版本,在我看来您引用的同一页面似乎有一个:
成员的命名不同,但它与 struct HashCode
类型的想法基本相同,但对其他复杂类型进行了重载(如我上面的建议 #2)。您可以使用它,或者只是将其中的技术应用到您的 struct HashCode
实现中,保留其中使用的命名约定。
我正在努力根据此答案中的 HashCode 结构实现 GetHashCode() here。由于我的 Equals 方法将考虑使用 Enumerable.SequenceEqual() 的集合,因此我需要在我的 GetHashCode() 实现中包含这些集合。
首先,我使用 Jon Skeet 的嵌入式 GetHashCode() 实现来测试 HashCode 结构实现的输出。使用下面的以下测试,这按预期工作 -
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() 计算中的集合。这需要对 MyObjectEmbeddedGetHashCode 中的 GetHashCode() 实现进行少量添加。
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 结构中有点困难。在此示例中,当将类型 List 的集合传递到 Hash 方法时,T 是 List,因此尝试将 obj 转换为 ICollection 或 IEnumberable 不起作用。我可以成功转换为 IEnumerable,但它会导致装箱,我发现我不得不担心排除实现 IEnumerable 的字符串等类型。
在这种情况下,有没有办法可靠地将 obj 转换为 ICollection 或 IEnumerable?
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;
}
}
您可以通过多种方式解决收款问题:
- 使用非通用接口,例如
ICollection
或IEnumerable
. - 为
Hash()
方法添加重载,例如Hash<T>(IEnumerable<T> list) { ... }
也就是说,恕我直言,最好只保留 struct HashCode
并将特定于集合的代码放在实际的 GetHashCode()
方法中。例如:
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;
}
如果您确实想要 struct HashCode
类型的全功能版本,在我看来您引用的同一页面似乎有一个:
成员的命名不同,但它与 struct HashCode
类型的想法基本相同,但对其他复杂类型进行了重载(如我上面的建议 #2)。您可以使用它,或者只是将其中的技术应用到您的 struct HashCode
实现中,保留其中使用的命名约定。