C# 为什么ValueType.GetHashCode()的实现方式是这样的?

C# 为什么ValueType.GetHashCode()的实现方式是这样的?,c#,gethashcode,C#,Gethashcode,从ValueType.cs **Action: Our algorithm for returning the hashcode is a little bit complex. We look ** for the first non-static field and get it's hashcode. If the type has no ** non-static fields, we return the hashcode of the type. W

ValueType.cs

**Action: Our algorithm for returning the hashcode is a little bit complex. We look ** for the first non-static field and get it's hashcode. If the type has no ** non-static fields, we return the hashcode of the type. We can't take the ** hashcode of a static member because if that member is of the same type as ** the original type, we'll end up in an infinite loop. **措施:我们返回哈希代码的算法有点复杂。我们看 **对于第一个非静态字段,获取其哈希代码。如果类型没有 **非静态字段,则返回类型的哈希代码。我们不能坐飞机 **静态成员的哈希代码,因为如果该成员与 **原始类型,我们将在一个无限循环中结束。 今天,当我使用KeyValuePair作为字典中的键(它存储xml属性名(enum)和它的值(string)),并期望它根据所有字段计算其hashcode时,我被这一点咬了一口,但根据实现,它只考虑了键部分

示例(来自Linqpad的c/p):

void Main()
{
var kvp1=新的KeyValuePair(“foo”、“bar”);
var kvp2=新的KeyValuePair(“foo”、“baz”);
//真的
(kvp1.GetHashCode()==kvp2.GetHashCode()).Dump();
}

我猜第一个非静态字段是指declaratin顺序中的第一个字段,无论出于何种原因在源代码中更改变量顺序时,这也可能会导致问题,并且相信它不会在语义上改变代码。

即使字段顺序改变,它仍然应该遵守
GetHashCode
的约定:在该进程的生命周期内,相同的值将具有相同的哈希代码

特别是:

  • 不相等的值不必具有不相等的哈希代码
  • 散列代码不必在各个进程之间保持一致(您可以更改实现、重新生成,并且所有操作都应该仍然有效-基本上,您不应该持久化散列代码)

现在我并不是说ValueType的实现是一个好主意——它将以各种方式导致性能下降。。。但是我不认为它实际上是坏的。

好吧,任何
GetHashCode()
的实现都有利弊。这些当然是我们在实现自己的类型时要考虑的问题,但是在
ValueType.GetHashCode()
的情况下,有一个特别的困难,因为它们没有太多关于具体类型的实际细节的信息。当然,当我们创建一个抽象类或一个打算作为类的基础的抽象类时,这种情况经常发生,这些抽象类将在状态方面添加更多的内容,但是在这些情况下,我们有一个明显的解决方案,就是只使用
object.GetHashCode()
的默认实现,除非派生类愿意在那里重写它

使用
ValueType.GetHashCode()
它们没有这种奢侈,因为值类型和引用类型之间的主要区别在于,尽管人们普遍谈论堆栈与堆的实现细节,但事实上,对于值类型等价性与值相关,而对于对象类型等价性与标识相关(即使对象通过覆盖
Equals()
GetHashCode()
来定义不同形式的等价,引用等价的概念仍然存在并且仍然有用

因此,对于
Equals()
方法,实现是显而易见的;检查两个对象的类型是否相同,如果是,则检查所有字段是否相等(实际上,在某些情况下,有一种优化会进行逐位比较,但这是基于相同基本思想的优化)

如何处理
GetHashCode()
?根本没有完美的解决方案。他们可以做的一件事是在每个字段上进行某种mult,然后添加或移位,然后执行xor。这可能会生成一个非常好的hashcode,但如果有很多字段,可能会很昂贵(不用说,它不建议有很多字段的值类型,实现者必须考虑到它们仍然可以,甚至确实有可能是有意义的时候,虽然我真的无法想象一个时间,在那里它既有意义,也有意义的散列它。”。如果他们知道某些字段在不同的实例之间几乎没有差异,他们可以忽略这些字段,并且仍然有一个相当好的哈希代码,同时速度也相当快。最后,他们可以忽略大多数字段,并希望他们不忽略的字段在大多数情况下值都不同。他们选择后者的最极端版本

(在没有实例字段的情况下做什么是另一个问题,也是一个很好的选择,这样的值类型等于相同类型的所有其他实例,并且它们有一个与之匹配的哈希代码)

因此,如果对第一个字段相同(或返回相同的哈希代码)的许多值进行哈希运算,那么它的实现就很糟糕,但其他实现在其他情况下也会很糟糕(Mono将所有字段的哈希代码进行异或运算,在您的情况下效果更好,在其他情况下效果更差)

更改字段顺序并不重要,因为hashcode非常明确地表示为仅在进程的生命周期内保持有效,不适用于大多数情况,因为在这些情况下,它们可以被持久化(在某些缓存情况下很有用,如果在代码更改后找不到正确的内容,则不会造成伤害)

所以,不是很好,但是没有什么是完美的。它表明,在使用对象作为一个键时,必须始终考虑“相等”的两个方面。
public class KVPCmp<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>, IEqualityComparer
{
  bool IEqualityComparer.Equals(object x, object y)
  {
      if(x == null)
        return y == null;
      if(y == null)
        return false;
      if(!(x is KeyValuePair<TKey, TValue>) || !(y is KeyValuePair<TKey, TValue>))
        throw new ArgumentException("Comparison of KeyValuePairs only.");
      return Equals((KeyValuePair<TKey, TValue>) x, (KeyValuePair<TKey, TValue>) y);
  }
  public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
  {
      return x.Key.Equals(y.Key) && x.Value.Equals(y.Value);
  }
  public int GetHashCode(KeyValuePair<TKey, TValue> obj)
  {
      int keyHash = obj.GetHashCode();
      return ((keyHash << 16) | (keyHash >> 16)) ^ obj.Value.GetHashCode();
  }
  public int GetHashCode(object obj)
  {
      if(obj == null)
        return 0;
      if(!(obj is KeyValuePair<TKey, TValue>))
       throw new ArgumentException();
      return GetHashCode((KeyValuePair<TKey, TValue>)obj);
  }
}
公共类KVPCmp:IEqualityComparer,IEqualityComparer
{
布尔IEqualityComparer.Equals(对象x,对象y)
{
如果(x==null)
返回y==null;
如果(y==null)
返回false;
如果(!(x是KeyValuePair)| |!(y是KeyValuePair))
抛出新ArgumentException(“仅比较KeyValuePairs”);
返回等于((KeyValuePair)x,(KeyValuePair)y);
}
公共布尔等于(键值对x、键值对y)
{
返回x.Key.Equals(y.Key)和&x.Value.Equals(y.Value);
}
public int GetHashCode(KeyValuePair obj)
{
int keyHash=obj.GetHa
public class KVPCmp<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>, IEqualityComparer
{
  bool IEqualityComparer.Equals(object x, object y)
  {
      if(x == null)
        return y == null;
      if(y == null)
        return false;
      if(!(x is KeyValuePair<TKey, TValue>) || !(y is KeyValuePair<TKey, TValue>))
        throw new ArgumentException("Comparison of KeyValuePairs only.");
      return Equals((KeyValuePair<TKey, TValue>) x, (KeyValuePair<TKey, TValue>) y);
  }
  public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
  {
      return x.Key.Equals(y.Key) && x.Value.Equals(y.Value);
  }
  public int GetHashCode(KeyValuePair<TKey, TValue> obj)
  {
      int keyHash = obj.GetHashCode();
      return ((keyHash << 16) | (keyHash >> 16)) ^ obj.Value.GetHashCode();
  }
  public int GetHashCode(object obj)
  {
      if(obj == null)
        return 0;
      if(!(obj is KeyValuePair<TKey, TValue>))
       throw new ArgumentException();
      return GetHashCode((KeyValuePair<TKey, TValue>)obj);
  }
}
// (Updated example based on good comment!)
struct Control
{
    string name;
    int x;
    int y;
}