检索到的词典键未找到

时间:2013-08-20 16:41:46

标签: c# dictionary readonly-collection

我有一个SortedDictionary声明如下:

SortedDictionary<MyObject,IMyInterface> dict = new SortedDictionary<MyObject,IMyInterface>();

当它填充了值时,如果我从字典中获取任何键,然后尝试立即引用它,我会得到KeyNotFoundException

MyObject myObj = dict.Keys.First();
var value = dict[myObj];     // This line throws a KeyNotFoundException

当我用调试器将鼠标悬停在字典上(错误之后)时,我可以清楚地看到我试图引用的相同键实际上包含在字典中。我正在使用ReadOnlyCollection MyObjects填充字典。或许有些奇怪的事情发生在那里?我尝试重写==运算符和Equals方法以获得我想要的显式比较,但没有这样的运气。这真的无关紧要,因为我实际上直接从Dictionary获取密钥,然后使用相同的密钥查询Dictionary。我无法弄清楚造成这种情况的原因。有没有人见过这种行为?

编辑1

在覆盖Equals时,我也过载了(如MS建议的那样)GetHashCode。以下是对任何感兴趣的人MyObject的实施:

public class MyObject
{
public string UserName { get; set;}
public UInt64 UserID  { get; set;}

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

        // Return true if the fields match:
        return this.Equals((MyObject)obj);
    }

    public bool Equals(MyObject other)
    {
        // Return true if the fields match
        return this.UserID == other.UserID;
    }

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


public static bool operator ==( MyObject a, MyObject b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.UserID == b.UserID
}

public static bool operator !=( MyObject a, MyObject b)
{
    return !(a == b);
}
}

我在调试中注意到的是,如果我为表达式添加一个快速监视(在KeyNotFoundException之后):

dict.ElementAt(0).Key == value;

它返回true。怎么会这样?

编辑2 所以问题最终是因为SortedDictionary(以及Dictionary)不是线程安全的。有一个后台线程正在对字典执行某些操作,这些操作似乎触发了集合的一个手段(向集合中添加项目会执行此操作)。同时,当字典迭代遍历值以查找我的密钥时,集合正在被更改,即使它在那里也找不到我的密钥。

对于那些要求代码的人,对不起,我正在调试我继承的应用程序,我没有意识到这是在一个定时的后台线程上进行的。因此,我以为我复制并粘贴了所有相关代码,但我没有意识到在操作集合的所有内容后面还有另一个线程。

3 个答案:

答案 0 :(得分:1)

似乎问题最终是因为SortedDictionary不是线程安全的。有一个后台线程正在对字典执行某些操作(向集合添加项目),这似乎触发了集合的一个手段。同时,当字典试图遍历值以查找我的密钥时,该集合正在被更改和使用,使得枚举器无效,并且即使它在那里也找不到我的密钥。

答案 1 :(得分:0)

除了重载==Equals之外,请确保使用合适的哈希函数覆盖GetHashCode。特别是,请参阅文档中的此规范:

  
      
  • 如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象没有   比较相等,两个对象的GetHashCode方法不相同   必须返回不同的值。
  •   
  • 对象的GetHashCode方法必须始终返回相同的哈希码,只要对象状态没有修改即可   它确定对象的Equals方法的返回值。注意   这只适用于当前执行的应用程序,   并且如果应用程序是,则可以返回不同的哈希码   又跑了。
  •   
  • 为获得最佳性能,哈希函数应为所有输入生成均匀分布,包括严重群集的输入。   一个含义是对对象状态的小修改应该   导致对结果哈希代码进行大量修改以获得最佳哈希值   表性能。
  •   
  • 哈希函数的计算成本应该低廉。
  •   
  • GetHashCode方法不应抛出异常。
  •   

我同意Jon Skeet's suspicion您在添加为UserID属性后无意中修改了MyObject属性。但由于UserID中唯一对测试平等性很重要的属性是Dictionary(因此这是Dictionary<ulong, IMyInterface>唯一关心的属性),我建议您重构代码以使用简而言之Dictionary<ulong, IMyInterface> dict = new Dictionary<string, IMyInterface>(); ulong userID = dict.Keys.First(); var value = dict[userID]; 代替:

{{1}}

答案 2 :(得分:0)

我怀疑 - 插入后你可能更改密钥的UserID。例如,这将证明问题:

var key = new MyObject { UserId = 10 };
var dictionary = new Dictionary<MyObject, string>();
dictionary[key] = "foo";

key.UserId = 20; // This will change the hash code

var value = dict[key]; // Bang!

您不应更改在基于哈希的集合中用作键的对象的相等/哈希码注意事项中涉及的属性。理想情况下,更改代码以便无法更改 - 只需UserId只读,在构造时初始化。

以上肯定 会导致问题 - 但当然,它可能与你看到的问题不一样。