0

通过搜索 msdn c# 文档和堆栈溢出,我得到了Dictionary<T,T>应该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()。

有人可以重现这个,并帮助我解释其含义吗?它是单声道的错误实现吗?或者我误解了什么。

4

2 回答 2

2

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

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

MSDN明确表示:

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

于 2013-03-01T11:05:36.493 回答
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 类)(您正在以相反的方式进行操作):

MyObject1)什么时候有两个相等的实例?假设你有:

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)

你希望结果是真的吗?我会说你会。因此,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)

于 2013-03-01T11:24:51.180 回答