【问题标题】:Understanding behavior & overriding GetHashCode()理解行为和覆盖 GetHashCode()
【发布时间】:2014-08-09 00:01:25
【问题描述】:

我尝试关注 MSDN 中的 Guidelines,也提到了 This great question,但以下内容似乎与预期不符。

我试图表示一个类似于 FQN 的结构,其中好像 P1 列在 P2 之前,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 (dict) 中,我使用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. 是否正确覆盖了 Equals?
  3. 如果他们都很好,这种行为是如何/为什么会发生的?

关于字典的主题

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

  1. 我认为 GetHashCode() 是字典的主要“比较”操作,这总是正确的吗?
  2. 键是对象的字典的真正“索引”是什么?

更新

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

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

【问题讨论】:

  • 你的GetHashCodes1 的哈希码与自身进行异或,所以它总是返回0。
  • s1 来自哪里?
  • 1.无法重现。 2. 提供实际编译的代码。 3.字典在哈希冲突的情况下使用相等,所以你需要同时实现。
  • 我不是要你修复我的代码。除此之外没有其他代码。我有一个关于如何实现这些东西的问题。

标签: c# dictionary


【解决方案1】:
  1. GetHachCode() 是否被正确覆盖?

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

也就是说,您的特定实现不如预期的那么好。例如,在您的班级中,字符串的顺序很重要。 IE。 new data( "A", "B" ) != new data( "B", "A" )。但是,这些将始终哈希相等,因为您的 GetHashCode 实现是对称的。最好以某种方式打破对称性。例如:

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

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

  1. 是否正确覆盖了 Equal?

嗯,肯定可以改进的。例如,第一个 null 检查是多余的,在第二个比较中转换为 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. 如果他们都很好,这种行为是如何/为什么会发生的?

我不知道,因为您提供的代码不会执行此操作。它也不会编译。你的 data 类有两个 readonly 字段,你不能使用你在上一个代码 sn-p 中显示的初始化列表来分配它们。

我只能推测您所看到的行为,因为您在此处显示的任何内容都不会导致所描述的行为。

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

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

Dictionary 仅使用GetHashCode 作为“寻址”对象的一种方式(具体而言,哈希码用于识别项目应放置在哪个桶中)。它不一定直接将其用作比较。而如果是,它只能用它来判断两个对象不相等,不能用它来判断它们是否相等。

  1. 在键是对象的字典中,真正的“索引”是什么?

我不完全确定你在这里问什么,但我倾向于说答案是没关系。物品去哪里并不重要。如果您关心这类事情,您可能不应该使用Dictionary

【讨论】:

  • 最后一部分更像是一个 VS 调试器问题。真的很随意,不,索引并不重要。感谢您的回复。我会试试你提到的。还。我不确定为什么人们说该类无法编译?我只是弥补了这个问题,但我可以将它复制到控制台项目中并且它可以工作。奇怪。
【解决方案2】:

GetHashCode() 是否被正确覆盖?

没有。您允许将null 传递给p1p2,而null.GetHashCode() 会抛出NullReferenceException,这在GetHashCode 中是不允许的。要么禁止传递null,要么让GetHashCode 为空值返回int(零即可)。

您也在对未更改的整数进行异或运算;这意味着您创建的每个包含两个相同值的类的 hashCode 都为零。这是一种非常常见的碰撞;通常将每个哈希码乘以一个素数来避免这种情况。

Equals 是否被正确覆盖?

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

如果未定义,IEquatable&lt;data&gt; 确实会回退到 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 + "}";
    }
}

【讨论】:

    猜你喜欢
    • 2012-07-13
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    相关资源
    最近更新 更多