1

我有一个SortedDictionary这样的声明:

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

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

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

当我使用调试器将鼠标悬停在字典上(出现错误后)时,我可以清楚地看到我试图引用的相同键实际上包含在字典中。我正在使用ReadOnlyCollectionof填充字典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;

它返回真。怎么会这样?

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

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

4

3 回答 3

1

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

于 2013-08-20T20:59:41.967 回答
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只读,在构造时初始化。

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

于 2013-08-20T17:29:33.360 回答
0

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

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

我同意Jon Skeet的怀疑UserID,即在将属性添加为键后,您会以某种方式无意中修改属性。但由于唯一对测试相等性很重要的属性MyObjectUserID(因此这是唯一Dictionary关心的属性),我建议重构您的代码以使用简单的Dictionary<ulong, IMyInterface>代替:

Dictionary<ulong, IMyInterface> dict = new Dictionary<string, IMyInterface>();
ulong userID = dict.Keys.First();
var value = dict[userID];
于 2013-08-20T16:43:36.213 回答