【问题标题】:"the given key was not present in the dictionary" error when using a self-defined class as key使用自定义类作为键时出现“字典中不存在给定键”错误
【发布时间】:2012-12-03 18:31:53
【问题描述】:

我有这样的代码:

if (CounterForEachRelatedTagDict.Select(x => x.Key).Contains(tag.Key))
   CounterForEachRelatedTagDict[tag.Key] += tag.Value;

是否有可能 IF 语句返回 true,同时 CounterForEachRelatedTagDict[tag.Key] 返回“给定键不在字典中”错误? tagKeyValuePair<MyClass,int>

CounterForEachRelatedTagDict 是这样发起的:

Dictionary<MyClass, int> CounterForEachRelatedTagDict = new Dictionary<MyType, int>();

MyClass是这样的

public class MyClass
{
    public string name {get;set;}
    public Guid Id { get; set; }
    ...
}

这对我来说几乎是不合理的......

【问题讨论】:

  • 什么是 tag2 对象 - 编辑:看起来这是一个错字。
  • @frinkenstein789 MyType 是一个类,也许我应该把它改成 MyClass

标签: c#


【解决方案1】:

问题是您的EqualGetHashCode 方法与MyType 不同步。

当您使用 CounterForEachRelatedTagDict.Select(x =&gt; x.Key).Contains(tag.Key) 时,您正在使用 Equals 对所有键执行线性搜索,以比较您正在搜索的每个键。

当您在Dictionary 中使用ContainsKey、索引器或许多其他方法之一来查找键时,您首先使用GetHashCode 散列键,然后它只使用Equals 来查找该桶中的(希望很少有对象)是相同的。

发生的情况是,您有两个对象,first.Equals(second) 返回 true,但 GetHashCode 返回两个不同的值。非常重要的是,当在Dictionary 中使用对象作为键时,Equals 为其返回true 的任何两个对象也必须为GetHashCode 返回相同的整数。理想情况下,不同的对象应该尽可能返回不同的哈希码,但这并不总是可能的(具有相同哈希码的不同对象称为“冲突”)。

请注意,这种查找键的方法,虽然它确实强制您确保所有用作键的对象都具有GetHashCode 的合理实现(来自object 的默认实现很少适用)这个算法*非同寻常*高效(使用高效的散列算法),这是值得的。使用ContainsKey 或字典的索引器比遍历每个键并比较它要快得多,这是您的Select 代码需要做的,以避免使用GetHashCode

所以,回答你的问题,是的,CounterForEachRelatedTagDict.Select(x =&gt; x.Key).Contains(tag.Key) 很有可能找到一个项目,而索引器却找不到。

【讨论】:

    【解决方案2】:

    首先:您可以使用ContainsKey 方法代替Linq 查询。

    第二:您必须覆盖MyTypeGetHashCodeEquals。这就是 Dictionary 查找和比较键的方式。

    查看以下类似问题:Dictionary.ContainsKey return False, but a want TrueUsing an object as a generic Dictionary key

    【讨论】:

      【解决方案3】:

      要将您的类型用作字典键,您应该重写两个方法:GetHashCodeEquals

      默认情况下(如果您不覆盖GetHashCode)您类型的每个对象(即使具有相同的字段值)都将返回唯一值。这意味着您将只能找到您将放入字典中的完全相同的“参考”。考虑以下两种类型:MyType1 不会覆盖 GetHashCodeEquals,而 MyType2 会:

      class MyType1
      {
        public MyType1(int id, string name) {Id = id; Name = name;}
        public int Id {get; private set;}
        public string Name {get; private set;}
      }
      
      
      internal class MyType2
      {
          public MyType2(int id, string name)
          {
              Id = id;
              Name = name;
          }
      
          public int Id { get; private set; }
          public string Name { get; private set; }
      
          bool Equals(MyType2 other)
          {
              return Id == other.Id && string.Equals(Name, other.Name);
          }
      
          public override bool Equals(object obj)
          {
              if (ReferenceEquals(null, obj)) return false;
              if (ReferenceEquals(this, obj)) return true;
              if (obj.GetType() != this.GetType()) return false;
              return Equals((MyType2) obj);
          }
      
          public override int GetHashCode()
          {
              unchecked
              {
                  return (Id*397) ^ Name.GetHashCode();
              }
          }
      }
      
      var d1 = new Dictionary<MyType1, int>();
      d1[new MyType1(1, "1")] = 1;
      d1[new MyType1(1, "1")]++; // will throw withKeyNotFoundException
      
      var d2 = new Dictionary<MyType2, int>();
      d1[new MyType2(1, "1")] = 1;
      d1[new MyType2(1, "1")]++; // Ok, we'll find appropriate record in dictionary
      

      【讨论】:

      • 非常感谢!我只想知道:(Id*397) ^ Name.GetHashCode()是什么意思?为什么一定是397?顺便说一句,在我的情况下,Id 是一个 Guid。
      • 实际上这段代码是由 Resharper 生成的,但是这些东西的重点是创建更健壮的哈希函数,因为像 Id.GetHashCode() ^ Name.GetHashCode() 这样的默认实现会经常产生冲突.在您的情况下(使用 Guid),情况并非如此,但对于整数字段,这是防止冲突的好选择。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      相关资源
      最近更新 更多