未找到检索到的词典密钥

本文关键字:密钥 检索 | 更新日期: 2023-09-27 18:28:19

我有一个像这样声明的SortedDictionary

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

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

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

当我用调试器将鼠标悬停在字典上(错误发生后)时,我可以清楚地看到我试图引用的同一个键实际上包含在字典中。我正在使用MyObjectsReadOnlyCollection填充字典。也许那里发生了什么奇怪的事情?我尝试重写==运算符和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)不是线程安全的。有一个后台线程正在对字典执行一些操作,这些操作似乎触发了对集合的调用(向集合中添加项可以做到这一点)。与此同时,当字典遍历值以找到我的键时,集合正在更改,即使它在那里也找不到我的键。

很抱歉,所有要求提供此应用程序代码的人,我目前正在调试我继承的一个应用程序,但我没有意识到这是在定时后台线程上进行的。因此,我以为我复制并粘贴了所有相关的代码,但我没有意识到在操作集合的所有东西后面都有另一个线程在运行。

未找到检索到的词典密钥

问题似乎是因为SortedDictionary不是线程安全的。有一个后台线程正在对字典执行一些操作(向集合中添加项),这似乎触发了对集合的调用。与此同时,当字典试图遍历值以找到我的键时,集合被更改并重新使用,导致枚举器无效,即使它在那里也找不到我的键。

我有一个怀疑-插入后您可能正在更改密钥的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只读,在构造时初始化。

上面的肯定会导致问题,但当然,这可能与您看到的问题不同。

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

  • 如果两个对象比较为相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象没有如果比较相等,则两个对象的GetHashCode方法不会必须返回不同的值
  • 只要对象状态没有修改,对象的GetHashCode方法就必须始终返回相同的哈希代码它确定对象的Equals方法的返回值。笔记这仅适用于应用程序的当前执行,并且如果应用程序再次运行
  • 为了获得最佳性能,散列函数应该为所有输入生成均匀分布,包括严重聚集的输入。这意味着对对象状态的小修改应该导致对生成的哈希代码进行大量修改以获得最佳哈希表性能
  • 哈希函数的计算成本应该很低
  • GetHashCode方法不应引发异常

我同意Jon Skeet的怀疑,即在UserID属性被添加为密钥后,您无意中修改了它。但是,由于在MyObject中测试相等性唯一重要的属性是UserID(因此这是Dictionary唯一关心的属性),我建议重构您的代码,改用简单的Dictionary<ulong, IMyInterface>

Dictionary<ulong, IMyInterface> dict = new Dictionary<string, IMyInterface>();
ulong userID = dict.Keys.First();
var value = dict[userID];