【问题标题】:Dictionary.ContainsKey return False, but a want TrueDictionary.ContainsKey 返回 False,但想要 True
【发布时间】:2011-03-02 04:37:07
【问题描述】:
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());
    }
}
}

我应该改变什么才能实现?

【问题讨论】:

    标签: c# .net dictionary


    【解决方案1】:

    你想要真的 - 但 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;
        }
    }
    

    【讨论】:

    • 另外,如果Key 真的那么简单,您可以将其定义为struct
    • 为什么我还要费心回答这些问题。每个人都比我早了几分钟 :)
    • GetHashCode 应该在声明中返回一个int ;)
    • @Toby 以及 struct 的东西如何帮助解决这个问题?请详细说明
    • @Galilyou:我认为 Toby 指的是 ValueType.Equals 默认情况下通过检查每个字段来比较结构。但是,它使用反射来做到这一点,因此性能会受到影响。当打算将类型用作字典中的键(在我看来)时,覆盖 EqualsGetHashCode 通常是更好的方法。
    【解决方案2】:

    如果您覆盖 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();
    }
    

    【讨论】:

      【解决方案3】:

      如果您没有能力像其他人提到的那样覆盖相等运算符/Equals/GetHashCode(例如,您不控制对象的源代码),您可以在构造函数中提供IEqualityComparer&lt;Key&gt; 实现执行相等性检查的字典。

      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();
          }
      }
      

      就目前而言,您的 Key 是一个引用对象,因此除非您以其他方式告诉世界(或字典),否则仅在引用上确定相等性。

      【讨论】:

      • 注意:当然,要实现这样的实现,您希望比较的成员必须是外部可访问的,它们不能是私有的。
      【解决方案4】:

      重写类的 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; }
          }
      }
      

      【讨论】:

        【解决方案5】:

        为了使用您自己的类作为字典键,您应该覆盖 GetHashCode 和 Equals。否则它将使用内存地址来检查是否相等。

        公共类密钥 { 字符串名称; 公钥(字符串 n){ 名称 = n; } 公共覆盖 int GetHashCode() { 返回名称.GetHashCode(); } 公共覆盖布尔等于(对象obj) { var other = obj 作为键; 如果(其他 == 空) 返回假; 返回名称 == 其他名称; } }

        【讨论】:

        • 只需要在getHashCode中验证name不为空
        • 是的,当然。取决于他是否想要空键,但到目前为止这是一个错误:)
        【解决方案6】:

        您需要覆盖 Key 类的 Equals 和 GetHashCode 方法。

        【讨论】:

          【解决方案7】:

          你的问题是

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

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

          修复它,我认为它应该可以工作。要修复它,请覆盖 Equals 方法并检查名称值是否相同。如果要覆盖 Equals,还应该覆盖 GetHashCode。

          【讨论】:

          • 准确地说,修复Equals 不会改变==!= 的行为。这些也必须被覆盖(强烈推荐!)但Dictionary 不需要这样做。
          • @Konrad, Microsoft advises against 覆盖 == 用于 Key 等可变类型。
          • @Matthew:老实说,我很久以前就不再关注微软的编码指南了,当时我注意到他们提出了许多与我的经验相矛盾的毫无根据、不合理的建议。在这种特殊情况下,它可以实际上是合理的(但不是!为什么?)但我仍然认为建议是倒退的。它应该是:尽可能使对象不可变。如果不是,也不要覆盖Equals。是的,这意味着这些对象不能存储在字典中。所以呢? Python 具有相同的限制,语义非常相似,并且运行良好。
          • @Matthew:(续)使Equalsoperator == 不一致违反POLS,并没有真正起到任何作用。如果他们从一开始就支持运算符重载并允许虚拟运算符,那么整个人为的二分法就不会出现,Equals 也不会存在。首先它存在的唯一原因是运算符在 .NET 中不能是虚拟的。
          • @Matthew:我会反驳这个假设。程序员(初学者)期待无处不在的副作用(不可变字符串似乎是一个主要的绊脚石,正如关于String.Replace 的返回值的问题所证明的那样)。我也看不出这个假设对于Equals 会有什么不同。
          【解决方案8】:

          1.覆盖等于、获取哈希码和“==”运算符。

          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。如果可能,请使用结构。

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

          【讨论】:

          • 这段代码不安全(NRE in GetHashCode 一方面)并且由于不必要的代码重复而比需要的时间长一些。对于非冗余实现,请查看my write-up on another question
          • 好的,我修正了错误,并为您在另一个问题上的实现 +1 了。
          【解决方案9】:

          它们在内部具有相同的值,但 a != b 因为它们是 2 个不同的变量。

          【讨论】:

            【解决方案10】:

            您需要覆盖 Key 类的 EqualsGetHashCode 方法。在您的情况下,您可以根据键的名称(如果您的类更复杂,则可以根据任何其他唯一属性)进行比较。

            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();
                }
            }
            

            【讨论】:

              【解决方案11】:

              然后你需要覆盖 Key 类的 GetHashCode 和 Equals。

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

              在您的情况下,假设“名称”不是空值,它可以实现为

                 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 呢?繁荣。碰撞。砰。
              • 这就是为什么我说,我引用:“......在你的情况下,假设“name”不是空......”
              【解决方案12】:

              在这种情况下,ContainsKey 将 Key 作为对象进行比较,并检查对象本身是否相同——它们不是。您需要实现 IComparable 或覆盖 Key.Equals 或类似的东西以使其执行您想要的操作。

              【讨论】:

              • 小心 - Dictionary 是根据哈希映射实现的,因此(与 SortedDictionarySortedList 不同)实现 IComparable 具有任何no 效果。跨度>
              猜你喜欢
              • 1970-01-01
              • 2013-07-11
              • 1970-01-01
              • 2011-06-02
              • 2012-06-29
              • 2013-02-26
              • 1970-01-01
              • 2014-10-21
              • 2018-06-30
              相关资源
              最近更新 更多