【问题标题】:Retrieved Dictionary Key Not Found未找到检索到的字典键
【发布时间】:2013-08-20 16:41:46
【问题描述】:

我有一个 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;

它返回真。这怎么可能?

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

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

【问题讨论】:

  • 您需要覆盖 Equals(和GetHashCode),尽管您可能还想重载它。请展示您的课程 - 或者更确切地说,展示问题的简短但完整的程序。
  • 要明确:您首先观察到这一点,没有任何 Equals/==/GetHashCode 的覆盖/重载?因为 thos 的错误实现将是一个简单的答案。
  • @JonSkeet - 你说得对,我打错了。我确实覆盖 Equals(和GetHashCode)方法。我添加了相关代码。
  • @HenkHolterman 好问题!我在覆盖 EqualsGetHashCode 方法之前就看到了问题,当我发现问题时就沿着这条路走。
  • @JoelB:但它们不是一个简短但完整的程序,是吗?我说的是一个完整的程序(最好是控制台应用程序),我可以复制、粘贴、编译、运行并查看您所看到的问题。

标签: c# dictionary readonly-collection


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    以上肯定导致问题 - 当然,它可能与您看到的问题不同。

    【讨论】:

    • 点了,如果我保持我被覆盖的GetHashCode 实现,我肯定会包括针对这种情况的保护。在这种情况下,这不是问题,因为我在覆盖 GetHashCode 之前遇到了问题
    • @JoelB:你当时是否覆盖了Equals?这是我能想到的唯一解释,但由于我们目前无法重现这个问题,我们只能猜测。同样,如果您能提供一种重现此方法的方法,我们更有可能为您提供帮助。
    【解决方案3】:

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

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

    我同意Jon Skeet's suspicion 的观点,即在将UserID 属性添加为键之后,您会以某种方式无意中修改它。但由于在MyObject 中测试相等性的唯一重要属性是UserID(因此这是Dictionary 关心的唯一属性),我建议重构您的代码以使用简单的Dictionary&lt;ulong, IMyInterface&gt; 代替:

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

    【讨论】:

    • @JoelB 请展示您对==EqualsGetHashCode 的实现。我们或许能够发现错误。
    猜你喜欢
    • 2023-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 2012-09-06
    • 2014-01-03
    • 1970-01-01
    • 2023-03-05
    相关资源
    最近更新 更多