理解行为&重写GetHashCode()

时间:2014-08-07 22:02:49

标签: c# dictionary

我尝试关注MSDN中的Guidelines并提及This great question,但以下情况似乎与预期不符。

我尝试表示类似于FQN的结构,就好像 P2 之前列出了 P1 一样, P2 只会存在于与 P1 相同的集合中。就像范围的工作方式一样。


关于GetHashCode()

的主题

我有一个类似这样的属性的类。

class data{
   public readonly string p1, p2;
   public data(string p1, string p2) {
       this.p1 = p1;
       this.p2 = p2;
   }
   public override int GetHashCode()
   {
       return this.p1.GetHashCode() ^ this.p2.GetHashCode();
   }
  /*also show the equal for comparison*/
    public override bool Equals(System.Object obj)
    {
        if (obj == null)
            return false;
        data d = obj as data;
        if ((System.Object)d == null)
            return false;
        /*I thought this would be smart*/
        return d.ToString() == this.ToString();
    }
    public override string ToString() {
        return "[" + p1 +"][" + p2+ "]";
    }
}

Dictionary(字典)中,我使用data作为键,因此这会使范围看起来像d1.p1.p2(或者更确切地说是d1的p1的p2,但是您更喜欢想象一下)

Dictionary<data,int> dict = new Dictionary<data,int>();

当d1.p1和另一个d2.p1不同时,我检查了行为,操作正确解析。但是当d1.p1和d2.p1相同且d1和d2的p2不同时,我会观察到以下行为。

data d1 = new data(){ p1="p1", p2="p2"  };
data d2 = new data(){ p1="p1", p2="XX"  };
dict.add(d1, 0);
dict.add(d2, 1);
dict[d1] = 4;

结果是两个元素都 4

  1. 是否正确覆盖了GetHashCode()?
  2. 是否正确覆盖了等于?
  3. 如果这两种情况都很好/为什么会发生这种情况?

  4. 关于词典的主题

    在观察窗口(VS2013)中,我有我的字典键列表给我看,而不是像我通常预期的那样每个索引只有一个键,我的数据对象的每个属性都是单个索引的键。因此,我不确定是否存在问题,或者我只是误解了Watch窗口对象的表示形式。我知道VS将如何显示一个对象,但是,我不确定我希望它如何显示为字典中的一个键。

    1. 我认为GetHashCode()是一个词典的主要&#34;比较&#34;操作,这总是正确的吗?
    2. 什么是真正的&#34;索引&#34;到一个字符串,其中键是一个对象?
    3. 更新

      直接查看每个哈希码后,我注意到它们重复了。然而,词典并不确定索引是否存在。以下是我看到的数据示例。

      1132917379      string: [ABC][ABC]   
      -565659420      string: [ABC][123]  
      -1936108909     string: [123][123]  
      //second loop with next set of strings   
      1132917379      string: [xxx][xxx]  
      -565659420      string: [xxx][yyy]
      //...etc
      

2 个答案:

答案 0 :(得分:1)

  
      
  1. 是否正确覆盖了GetHachCode()?
  2.   

当然,对于某些“正确”的定义。它可能没有被重写 well ,但它不是一个不正确的实现(因为被认为是相等的类的两个实例将散列到相同的值)。当然有了这个要求,你总是可以从GetHashCode返回0并且它将是“正确的”。这当然不会好。

那说你的具体实施并不尽如人意。例如,在您的班级中,字符串的顺序很重要。即new data( "A", "B" ) != new data( "B", "A" )。但是,由于您的GetHashCode实现是对称的,因此它们总是相等。最好以某种方式打破对称性。例如:

public int GetHashCode()
{
    return p1.GetHashCode() ^ ( 13 * p2.GetHashCode() );
}

现在不太可能发生两个不相等的实例的冲突。

  
      
  1. 是否正确覆盖了?
  2.   

嗯,它肯定可以改进。例如,第一个空检查是多余的,在第二个比较中转换为object。整个事情可能会更好地写成:

public bool Equals( object obj )
{
    var other = obj as data;
    if( other == null ) return false;
    return p1 == obj.p1 && p2 == obj.p2;
}

我还删除了对ToString的调用,因为它没有显着简化代码或使其更具可读性。这也是执行比较的低效方式,因为在比较甚至发生之前你必须构造两个新的字符串。只是直接比较成员为您提供更多的早期机会,更重要的是,更容易阅读(实际的相等实现不依赖于字符串表示)。

  
      
  1. 如果这两种情况都很好/为什么会发生这种情况?
  2.   

我不知道,因为您提供的代码不会这样做。它也不会编译。您的data课程有两个readonly字段,您无法使用您在上一个代码段中显示的初始化列表分配这些字段。

我只能推测你所看到的行为,因为你在这里展示的任何内容都不会导致所描述的行为。

我能给出的最好建议是确保你的密钥类不可变。 可变类型与Dictionary 不相称。 Dictionary类不希望对象的哈希码发生变化,因此如果GetHashCode依赖于类的任何可变部分,那么事情很可能会变得非常混乱。

  
      
  1. 我认为GetHachCode()是一个字典的主要“比较”操作,这总是正确的吗?
  2.   

Dictionary仅使用GetHashCode作为“寻址”对象的方式(具体而言,哈希码用于标识项目应放入哪个存储桶)。它不直接用作比较。如果确实如此,它只能用它来确定两个对象不相等,它不能用它来确定它们是否相等。

  
      
  1. 关键是对象的字典真正的“索引”是什么?
  2.   

我不完全确定你在这里问的是什么,但我倾向于说答案是无关紧要的。物品去的地方并不重要。如果你关心那种事情,你可能不应该使用Dictionary

答案 1 :(得分:0)

  

是否正确覆盖了GetHashCode()?

没有。您允许null p1p2null.GetHashCode()传递NullReferenceException GetHashCode null。要么禁止传递GetHashCode,要么让int为空值返回Equals(零可以)。

你也是不加改变的整体;这意味着您创建的每个包含两个相同值的类都将具有零hashCode。这是一个非常普遍的碰撞;通常会将每个哈希码乘以素数以避免这种情况。

  

是否正确覆盖了等于?

没有。您链接到的页面是System.Collections.HashTable使用的非通用System.Collections.Generic.Dictionary。您使用的是通用IEquatable<T>,它使用通用IEquatable<data>。您需要按照您发布的SO问题的已接受答案中的说明实施IEquatable<data>

如果没有定义,Equals(System.Object obj)确实会回退到public class MatPair : IEquatable<MatPair> { public readonly string MatNeedsToExplainWhatThisRepresents; public readonly string MatNeedsToExplainThisToo; public MatPair(string matNeedsToExplainWhatThisRepresents, string matNeedsToExplainThisToo) { if (matNeedsToExplainWhatThisRepresents == null) throw new ArgumentNullException("matNeedsToExplainWhatThisRepresents"); if (matNeedsToExplainThisToo == null) throw new ArgumentNullException("matNeedsToExplainThisToo"); this.MatNeedsToExplainWhatThisRepresents = matNeedsToExplainWhatThisRepresents; this.MatNeedsToExplainThisToo = matNeedsToExplainThisToo; } [Obsolete] public override bool Equals(object obj) { return Equals(obj as MatPair); } public bool Equals(MatPair matPair) { return matPair != null && matPair.MatNeedsToExplainWhatThisRepresents == MatNeedsToExplainWhatThisRepresents && matPair.MatNeedsToExplainThisToo == MatNeedsToExplainThisToo; } public override int GetHashCode() { unchecked { return MatNeedsToExplainWhatThisRepresents.GetHashCode() * 31 ^ MatNeedsToExplainThisToo.GetHashCode(); } } public override string ToString() { return "{" + MatNeedsToExplainWhatThisRepresents + ", " + MatNeedsToExplainThisToo + "}"; } } ,但不依赖于这种行为。此外,将int转换为字符串以进行比较并不是“智能”。任何时候你觉得你应该写一个评论,把某些东西称为“聪明”,你就是犯了错误。

更好地实施“数据”将是:

{{1}}