字典.ContainsKey返回False,但是我想要True。

24
namespace Dic
{
public class Key
{
    string name;
    public Key(string n) { name = n; }
}

class Program
{
    static string Test()
    {
        Key a = new Key("A");
        Key b = new Key("A");
        System.Collections.Generic.Dictionary<Key, int> d = new System.Collections.Generic.Dictionary<Key, int>();
        d.Add(a, 1);
        return d.ContainsKey(b).ToString();
    }

    static void Main(string[] args)
    {
        System.Console.WriteLine(Test());
    }
}
}

我应该做哪些更改才能得到 true?

12个回答

47

您想要的是真实的结果 - 但a和b是不同的对象。

您需要在Key类上重写GetHashCode和Equals方法。

public class Key
{
    string name;
    public Key(string n) { name = n; }

    public override int GetHashCode()
    {
        if (name == null) return 0;
        return name.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        Key other = obj as key;
        return other != null && other.name == this.name;
    }
}

7
如果Key确实如此简单,您可以将其定义为一个struct - Toby
7
我为什么还要回答这些问题呢?每次都会有其他人比我先几分钟回答 :) - simendsjo
1
GetHashCode在声明中应返回int类型;) - Mark Seemann
@Toby,结构体如何帮助解决这个问题?请详细说明。 - Ahmed
1
@Galilyou:我认为Toby指的是ValueType.Equals默认通过检查每个结构体的字段来比较它们。然而,它使用反射来实现,因此性能受到影响。在我的看法中,当意图将类型用作字典中的键时,覆盖EqualsGetHashCode通常是更好的方法。 - Dan Tao

10

如果您重写Key.GetHashCode和Key.Equals可能会有所帮助。

Key中:

public override bool Equals(object obj)
{
    var k = obj as Key;
    if (k != null)
    {
        return this.name == k.name;
    }
    return base.Equals(obj);
}

public override int GetHashCode()
{
    return this.name.GetHashCode();
}

9

如果你没有覆盖等号运算符/Equals/GetHashCode的能力,正如其他人所提到的那样(也就是说,你不控制对象的源代码),你可以在字典的构造函数中提供一个 IEqualityComparer<Key> 实现来执行你的相等性检查。

class KeyComparer : IEqualityComparer<Key>
{
    public bool Equals(Key x, Key y)
    {
        return x.Name == y.Name;
    }

    public int GetHashCode(Key obj)
    {
        return obj.Name.GetHashCode();
    }
}

目前,您的键是一个引用对象,因此仅在您告知世界(或字典)以外才能确定平等。


1
注意:当然,要有这样的实现,您希望进行比较的成员必须是外部可访问的,它们不能是私有的。 - Anthony Pegram

7

覆盖一个类的GetHashCode和Equals方法,以使其在字典中正常工作并不是一个很好的方法。字典的行为应该是字典的实现细节,而不是任何用作键的类的实现细节。当您想要在具有不同行为的不同字典中使用该类时,您会遇到麻烦。或者如果您没有访问类源代码。

更好的方法是给字典自己的比较器。例如:

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        var d = new Dictionary<Key, int>(new MyComparer());
        d.Add(new Key("A"), 1);
        Console.WriteLine(d.ContainsKey(new Key("a")));
        Console.ReadLine();
    }
    private class MyComparer : IEqualityComparer<Key> {
        public bool Equals(Key x, Key y) {
            return string.Compare(x.Name, y.Name, true) == 0;
        }
        public int GetHashCode(Key obj) {
            return obj.Name.ToUpper().GetHashCode();
        }
    }
    public class Key {
        public string Name { get; set; }
        public Key(string name) { Name = name; }
    }
}

2
为了使用自己的类作为字典键,您应该重写GetHashCode和Equals方法。否则,它将使用内存地址来检查相等性。
    public class Key
    {
        string name;
        public Key(string n) { name = n; }
public override int GetHashCode() { return name.GetHashCode(); }
public override bool Equals(object obj) { var other = obj as Key; if( other == null ) return false;
return name == other.name; } }

只需要在获取 GetHashCode 中验证 name 不为 null。 - Itay Karo
当然可以。这取决于他是否想要空键,但目前是一个bug :) - simendsjo

1

1. 重写Equals、GetHashCode和'=='运算符。

为了使字典能够检测它们是否相同,Key类必须重写Equals方法。默认实现将仅检查引用。

在这里:

        public bool Equals(Key other)
        {
            return this == other;
        }

        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Key))
            {
                return false;
            }

            return this.Equals((Key)obj);
        }

        public static bool operator ==(Key k1, Key k2)
        {
            if (object.ReferenceEquals(k1, k2))
            {
                return true;
            }

            if ((object)k1 == null || (object)k2 == null)
            {
                return false;
            }

            return k1.name == k2.name;
        }

        public static bool operator !=(Key k1, Key k2)
        {
            if (object.ReferenceEquals(k1, k2))
            {
                return false;
            }

            if ((object)k1 == null || (object)k2 == null)
            {
                return true;
            }

            return k1.name != k2.name;
        }

        public override int GetHashCode()
        {
            return this.name == null ? 0 : this.name.GetHashCode();
        }

2. 如果可能,使用结构体。

对于不可变数据类型,应该使用结构体,因为它们是按值传递的。这意味着您不能意外地将两个不同的值混合到同一个键中。


这段代码不安全(首先是 GetHashCode 中的 NRE),并且由于存在不必要的重复代码,长度比需要的长一些。如果想要一个非冗余实现,请参考我在另一个问题中的写作 - Konrad Rudolph
好的,我修复了错误,并在另一个问题中点赞了你的实现。 - John Gietzen

1

你的问题是

new Key("A").Equals(new Key("A"))==false.

并且

new Key("A").GetHashCode()!=new Key("A").GetHashCode()

修复它,我想它应该可以工作了。要修复它,请重写Equals方法并检查名称值是否相同。如果你重写了Equals,你也应该重写GetHashCode。


1
准确地说,修复“Equals”将不会改变==!=的行为。这些也必须被覆盖(强烈建议!),但这对于“Dictionary”并非必需。 - Konrad Rudolph
@Konrad,Microsoft建议不要覆盖像Key这样的可变类型的==。 - Matthew Flaschen
@Matthew:说实话,我已经停止遵循微软的编码指南有一段时间了,因为我注意到它们提供了许多毫无根据、不合理的建议,这些建议与我的经验相矛盾。在这种情况下,它确实可以被证明是正确的(但为什么没有呢?),但我仍然认为这个建议是反向的。它应该是:如果可能的话,使对象不可变。如果不行,也不要覆盖Equals。是的,这意味着这样的对象不能存储在字典中。那又怎样?Python也有类似的限制和语义,它运行良好。 - Konrad Rudolph
@Matthew:(续)使“Equals”和“operator ==”不一致违反了POLS原则,也没有实际意义。如果它们从一开始就支持运算符重载并允许虚拟运算符,这整个人为的二分法就永远不会出现,“Equals”也永远不会存在。它之所以存在的唯一原因是在.NET中运算符不能是虚拟的。 - Konrad Rudolph
我认为这个问题的原因很简单。程序员期望一对对象的 == 结果不会改变。对于熟悉 C、Java 或未被覆盖的 C# 的程序员来说,这是最不令人惊讶的结果。 - Matthew Flaschen
1
@Matthew:我不同意这种假设。程序员(初学者)随时都会期望有副作用(例如,不可变字符串似乎是一个主要的绊脚石,正如关于String.Replace返回值的问题所证明的那样)。我也不认为这种假设在Equals方面会有所不同。 - Konrad Rudolph

1

你需要重写你的 Key 类的 Equals 和 GetHashCode 方法。


0
你需要重写 Key 类的 Equals 和 GetHashCode 方法。在你的情况下,你可以基于键的名称进行比较(或者如果你的类更复杂,可以基于任何其他唯一属性进行比较)。
public class Key {
    string name;
    public Key(string n) { name = n; }

    public override bool Equals(object obj) {
        Key k = obj as Key;
        if (k == null)
            return false;
        return name.Equals(k.name);
    }

    public override int GetHashCode() {
        return name.GetHashCode();
    }
}

0

然后您需要在Key类上重写GetHashCode和Equals方法。

如果不这样做,您将得到两者的默认实现。这将导致a和b的哈希码很可能不相同(我不知道默认实现是什么样子),并且a肯定不等于b(默认的Equals()实现检查引用相等性)。

在您的情况下,假设"name"不为null,可以实现为

   public class Key
   {
        string name;
        public override int GetHashCode()
        {
             return name.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
              return false;
            }

            Key objAsKey = obj as Key;
            if (objAsKey == null)
            {
              return false;
            }

            return this.name.Equals(objAsKey.Name);
        }
    }

这是否是一个令人满意的哈希表,还有待商榷,但它无疑展示了其原理。


如果 namenull 呢?崩溃。崩溃。崩溃。 - Mathias Lykkegaard Lorenzen
那就是我所说的,我引用一下:“...在你的情况下,假设"name"不是空值...” - Willem van Rumpt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接