单词实现Dictionary <t,t>使用.Equals(obj o)代替.GetHashCode()</t,t>

时间:2013-03-01 11:02:50

标签: c# generics dictionary comparison unique

通过搜索msdn c#文档和堆栈溢出,我得到了明确的印象:Dictionary<T,T>应该使用GetHashCode()来检查密钥唯一性并进行查找。

  

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

我使用mono(在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()。

有人可以重现这一点,并帮我解释其含义是什么?在单声道中是不正确的实现?或者我误解了什么。

2 个答案:

答案 0 :(得分:2)

  

我得到了词典应该使用的清晰印象   .GetHashCode()用于检查密钥唯一性

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

MSDN清楚地说:

  

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

答案 1 :(得分:1)

这样做:

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

在一般情况下是错误的,因为您可能最终得到KeyType2等于的实例,StringBuilderSomeOtherClassAnythingYouCanImagine和什么不是实例。

你完全应该这样做:

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结果将是identitcal。

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)