【问题标题】:Mono implementation of Dictionary<T,T> using .Equals(obj o) instead of .GetHashCode()使用 .Equals(obj o) 而不是 .GetHashCode() 的 Dictionary<T,T> 的 Mono 实现
【发布时间】:2013-03-01 11:02:49
【问题描述】:

通过搜索 msdn c# 文档和堆栈溢出,我得到一个清晰的印象,Dictionary&lt;T,T&gt; 应该使用GetHashCode() 来检查键唯一性并进行查找。

Dictionary 泛型类提供从一组键到一组值的映射。字典中的每个添加都包含一个值及其关联的键。使用它的键检索一个值非常快,接近 O(1),因为 Dictionary 类是作为哈希表实现的。 ... 检索的速度取决于为 TKey 指定的类型的散列算法的质量。

我使用单声道(在 Unity3D 中),在我的工作中得到一些奇怪的结果后,我进行了这个实验:

public class DictionaryTest
{
    public static void TestKeyUniqueness()
    {
    //Test a dictionary of type1
    Dictionary<KeyType1, string> dictionaryType1 = new Dictionary<KeyType1, string>();
    dictionaryType1[new KeyType1(1)] = "Val1";
    if(dictionaryType1.ContainsKey(new KeyType1(1)))
    {
        Debug.Log ("Key in dicType1 was already present"); //This line does NOT print
    }

    //Test a dictionary of type1
    Dictionary<KeyType2, string> dictionaryType2 = new Dictionary<KeyType2, string>();
    dictionaryType2[new KeyType2(1)] = "Val1";
    if(dictionaryType2.ContainsKey(new KeyType2(1)))
    {
        Debug.Log ("Key in dicType2 was already present"); // Only this line prints
    }
  }
}
//This type implements only GetHashCode()
public class KeyType1
{
    private int var1;

    public KeyType1(int v1)
    {
        var1 = v1;
    }

    public override int GetHashCode ()
    {
        return var1;
    }
}
//This type implements both GetHashCode() and Equals(obj), where  Equals uses the hashcode.
public class KeyType2
{
    private int var1;

    public KeyType2(int v1)
    {
        var1 = v1;
    }

    public override int GetHashCode ()
    {
        return var1;
    }

    public override bool Equals (object obj)
    {
        return GetHashCode() == obj.GetHashCode();
    }
}

只有当使用KeyType2 类型时,键才被认为是相等的。对我来说,这表明 Dictionary 使用 Equals(obj) - 而不是 GetHashCode()。

有人可以重现这个,并帮我解释一下是什么意思吗?它是单声道的错误实现吗?还是我误解了什么。

【问题讨论】:

    标签: c# generics dictionary comparison unique


    【解决方案1】:

    我清楚地知道 Dictionary 应该使用 .GetHashCode() 用于检查键的唯一性

    是什么让你这么想? GetHashCode 不返回唯一值。

    MSDN 明确表示:

    字典需要一个相等的实现 判断键是否相等。您可以指定一个实现 IEqualityComparer 通用接口通过使用构造函数 接受比较器参数;如果您不指定实现, 默认的通用相等比较器 EqualityComparer.Default 是 用过的。如果类型 TKey 实现 System.IEquatable 泛型 接口,默认相等比较器使用该实现。

    【讨论】:

    • MSDN 还指出:“Dictionary 泛型类提供了从一组键到一组值的映射。字典中的每个添加都包含一个值及其关联的键。通过使用它的键检索一个值非常快,接近 O(1),因为 Dictionary 类是作为哈希表实现的。“我将其解释为哈希用于唯一性和查找。
    • @sune 它仅用于查找。 GetHasCode,顾名思义,只是一个有助于对项目进行排序以便更快查找的哈希。有关 GetHashCode 的用法,请参阅 stackoverflow.com/questions/7425142/…
    【解决方案2】:

    这样做:

    public override bool Equals (object obj)
    {
        return GetHashCode() == obj.GetHashCode();
    }
    

    在一般情况下是错误的,因为您最终可能会得到与StringBuilderSomeOtherClassAnythingYouCanImagine 相同的KeyType2 实例等于

    你应该完全这样做:

    public override bool Equals (object obj)
    {
        if (obj is KeyType2) {
            return (obj as KeyType2).var1 == this.var1;
        } else
            return false;
    }
    

    当您尝试覆盖 Equals 和固有的 GetHashCode 时,您必须确保以下几点(给定 MyObject 类)以 此顺序(您正在以相反的方式进行操作):

    1) MyObject 的 2 个实例何时相等?假设你有:

    public class MyObject {
         public string Name { get; set; }
         public string Address { get; set; }
         public int Age { get; set; }
    
         public DateTime TimeWhenIBroughtThisInstanceFromTheDatabase { get; set; }
    }
    

    并且您在某个数据库中有 1 条记录需要映射到此类的实例。 并且您约定将存储从数据库中读取记录的时间 在TimeWhenIBroughtThisInstanceFromTheDatabase:

    MyObject obj1 = DbHelper.ReadFromDatabase( ...some params...);
    // you do that at 14:05 and thusly the TimeWhenIBroughtThisInstanceFromTheDatabase
    // will be assigned accordingly
    
    // later.. at 14:07 you read the same record into a different instance of MyClass
    MyObject obj2 = DbHelper.ReadFromDatabase( ...some params...);
    // (the same)
    
    // At 14:09 you ask yourself if the 2 instances are the same
    bool theyAre  = obj1.Equals(obj2)
    

    您希望结果为 true 吗?我会说你会。 因此,Equals 的覆盖应该是这样的:

    public class MyObject {
        ...
        public override bool Equals(object obj) {
            if (obj is MyObject) {
                var that = obj as MyObject;
                return (this.Name == that.Name) && 
                       (this.Address == that.Address) &&
                       (this.Age == that.Age);
                       // without the syntactically possible but logically challenged:
                       // && (this.TimeWhenIBroughtThisInstanceFromTheDatabase == 
                       //     that.TimeWhenIBroughtThisInstanceFromTheDatabase)
            } else 
                return false;
        }
        ...
    }
    

    2) 确保只要 2 个实例相等(如您实施的 Equals 方法所示) 他们的 GetHashCode 结果将是相同的。

    int hash1 = obj1.GetHashCode();
    int hash2 = obj2.GetHashCode();
    bool theseMustBeAlso = hash1 == hash2;
    

    最简单的方法是(在示例场景中):

       public class MyObject {
        ...
        public override int GetHashCode() {
           int result;
           result = ((this.Name != null) ? this.Name.GetHashCode() : 0) ^
                    ((this.Address != null) ? this.Address.GetHashCode() : 0) ^
                    this.Age.GetHashCode();
           // without the syntactically possible but logically challenged:
           // ^ this.TimeWhenIBroughtThisInstanceFromTheDatabase.GetHashCode()
        }
        ...
    } 
    

    请注意: - 字符串可以为空,.GetHashCode() 可能会因NullReferenceException 而失败。 - 我使用了 ^ (XOR)。只要遵守黄金法则(第 2 条),您就可以使用任何您想要的东西。 - x ^ 0 == x(对于任何 x)

    【讨论】:

    • 感谢您的回答。显然,您实现 Equals 和 GetHashCode 的方式比我的更正确。我的代码只是作为测试 Dictionary 是否使用 GetHashCode() 的示例。它似乎没有。
    猜你喜欢
    • 2014-09-16
    • 2011-12-27
    • 1970-01-01
    • 2011-09-12
    • 1970-01-01
    • 2012-10-23
    • 2013-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多