C# 为什么C不为集合实现GetHashCode?

C# 为什么C不为集合实现GetHashCode?,c#,java,collections,hashcode,gethashcode,C#,Java,Collections,Hashcode,Gethashcode,我正在将一些东西从Java移植到C#。在Java中,ArrayList的hashcode取决于其中的项目。在C语言中,我总是从列表中得到相同的哈希代码 为什么会这样 对于我的一些对象,hashcode需要不同,因为它们的list属性中的对象使对象不相等。我希望hashcode对于对象的状态总是唯一的,并且只有当对象相等时才等于另一个hashcode。我错了吗?为什么太哲学化了。创建helper方法(可能是扩展方法)并根据需要计算hashcode。可能是XOR元素的哈希代码哈希代码不可能在大多数非

我正在将一些东西从Java移植到C#。在Java中,
ArrayList
hashcode
取决于其中的项目。在C语言中,我总是从
列表中得到相同的哈希代码

为什么会这样


对于我的一些对象,hashcode需要不同,因为它们的list属性中的对象使对象不相等。我希望hashcode对于对象的状态总是唯一的,并且只有当对象相等时才等于另一个hashcode。我错了吗?

为什么太哲学化了。创建helper方法(可能是扩展方法)并根据需要计算hashcode。可能是XOR元素的哈希代码

哈希代码不可能在大多数非平凡类的所有变体中都是唯一的。在C#中,列表相等的概念与Java中的不同(请参阅),因此哈希代码的实现也不同-它反映了C#列表相等。

是的,您错了。在Java和C#中,相等意味着拥有相同的哈希代码,但相反(不一定)是真的


有关更多信息,请参阅。

您只错了一部分。当你认为相等的hashcode意味着相等的对象,但是相等的对象必须有相等的hashcode,这意味着如果hashcode不同,那么对象也一样,你肯定错了

为了正确工作,哈希代码必须是不可变的–对象的哈希代码永远不能更改

如果对象的哈希代码确实发生了更改,则包含该对象的任何词典都将停止工作

因为集合不是不可变的,所以它们不能实现
GetHashCode


相反,它们继承了默认的
GetHashCode
,它为对象的每个实例返回一个(希望是)唯一的值。(通常基于内存地址)

核心原因是性能和人性人们倾向于认为哈希运算速度快,但通常需要至少遍历一次对象的所有元素

示例:如果在哈希表中使用字符串作为键,则每个查询的复杂性都是O(| s |)-使用2倍长的字符串,这将花费至少两倍的成本。想象一下,这是一个完整的树(只是一个列表列表)-哎呀:-)

如果完整的深度散列计算是一个集合的标准操作,那么很大一部分编程人员会在无意中使用它,然后责怪框架和虚拟机太慢对于像完全遍历这样昂贵的东西,程序员必须意识到复杂性是至关重要的。实现这一点的唯一方法是确保您必须编写自己的代码。这也是一个很好的威慑:——)

另一个原因是更新战术。动态计算和更新散列与每次进行完整计算相比,需要根据手头的具体情况进行判断调用

Immutability只是一种学术上的逃避——人们使用散列作为一种更快地检测变化的方法(例如文件散列),也使用散列来检测不断变化的复杂结构。除了101个基本功能外,哈希还有更多的用途关键是,对于复杂对象的哈希,使用什么必须是基于具体情况的判断调用。


使用对象的地址(实际上是一个句柄,因此在GC之后不会更改)由于散列实际上是任意可变对象的散列值保持不变的情况:-)C这样做的原因是它很便宜,并且再次促使人们自己计算。

散列代码必须依赖于所使用的等式定义,因此如果
a==B
a.GetHashCode()==B.GetHashCode()
(但不一定是相反的;
A.GetHashCode()==B.GetHashCode()
不包含
A==B

默认情况下,值类型的相等定义基于其值,引用类型的相等定义基于其标识(即,默认情况下引用类型的实例仅等于其自身),因此,值类型的默认哈希代码取决于它包含的字段的值*,而引用类型的默认哈希代码则取决于标识。事实上,因为我们理想情况下希望非相等对象的哈希代码不同,特别是在低阶位(最有可能影响重新哈希的值),我们通常希望两个等价但不相等的对象具有不同的哈希

由于对象将保持与自身相等,因此也应该清楚的是,
GetHashCode()
的默认实现将继续具有相同的值,即使对象发生了变化(即使对于可变对象,标识也不会发生变化)

现在,在某些情况下,引用类型(或值类型)重新定义相等。这方面的一个例子是字符串,例如
“ABC”==“AB”+“C”
。虽然比较了两个不同的字符串实例,但它们被认为相等。在本例中
GetHashCode()
必须被重写,以便该值与定义相等的状态(在本例中为包含的字符序列)相关

虽然对同样不可变的类型执行此操作更为常见,但出于各种原因,
GetHashCode()
并不依赖于不可变性
在面对易变性时必须保持一致-更改我们在确定散列时使用的值,并且散列必须相应地更改。但是,请注意,如果我们使用此易变对象作为使用散列的结构的键,这是一个问题,因为对该对象进行变异会改变其应停止的位置红色,而不将其移动到该位置(在集合中对象的位置取决于其值的任何其他情况下也是如此-例如,如果我们对列表进行排序,然后对列表中的一个项目进行变异,则列表将不再排序)。
ArrayList a = new ArrayList();
ArrayList b = new ArrayList();
for(int i = 0; i != 10; ++i)
{
  a.Add(i);
  b.Add(i);
}
return a == b;//returns false
ArrayList c = new ArrayList();
for(short i = 0; i != 10; ++i)
{
  c.Add(i);
}
public class ValueEqualList : ArrayList, IEquatable<ValueEqualList>
{
  /*.. most methods left out ..*/
  public Equals(ValueEqualList other)//optional but a good idea almost always when we redefine equality
  {
    if(other == null)
      return false;
    if(ReferenceEquals(this, other))//identity still entails equality, so this is a good shortcut
      return true;
    if(Count != other.Count)
      return false;
    for(int i = 0; i != Count; ++i)
      if(this[i] != other[i])
        return false;
    return true;
  }
  public override bool Equals(object other)
  {
    return Equals(other as ValueEqualList);
  }
  public override int GetHashCode()
  {
    int res = 0x2D2816FE;
    foreach(var item in this)
    {
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
  }
}
public class ArrayListEqComp : IEqualityComparer<ArrayList>
{//we might also implement the non-generic IEqualityComparer, omitted for brevity
  public bool Equals(ArrayList x, ArrayList y)
  {
    if(ReferenceEquals(x, y))
      return true;
    if(x == null || y == null || x.Count != y.Count)
      return false;
    for(int i = 0; i != x.Count; ++i)
      if(x[i] != y[i])
        return false;
    return true;
  }
  public int GetHashCode(ArrayList obj)
  {
    int res = 0x2D2816FE;
    foreach(var item in obj)
    {
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
  }
}