当密钥存在时,C#Dictionary抛出KeyNotFoundException

时间:2013-01-20 20:00:01

标签: c# dictionary nullreferenceexception keynotfoundexception

我正在存储一个2D数组,它表示矢量之间的距离矩阵为Dictionary<DistanceCell, double>。我对DistanceCell的实现有两个字符串字段,表示要比较的向量。

class DistanceCell
{
    public string Group1 { get; private set; }
    public string Group2 { get; private set; }

    public DistanceCell(string group1, string group2)
    {
        if (group1 == null)
        {
            throw new ArgumentNullException("group1");
        }

        if (group2 == null)
        {
            throw new ArgumentNullException("group2");
        }

        this.Group1 = group1;
        this.Group2 = group2;
    }
}

由于我将此课程用作关键字,因此我覆盖了Equals()GetHashCode()

public override bool Equals(object obj)
{
    // False if the object is null
    if (obj == null)
    {
        return false;
    }

    // Try casting to a DistanceCell. If it fails, return false;
    DistanceCell cell = obj as DistanceCell;
    if (cell == null)
    {
        return false;
    }

    return (this.Group1 == cell.Group1 && this.Group2 == cell.Group2) 
           || (this.Group1 == cell.Group2 && this.Group2 == cell.Group1);
}

public bool Equals(DistanceCell cell)
{
    if (cell == null)
    {
        return false;
    }

    return (this.Group1 == cell.Group1 && this.Group2 == cell.Group2) 
           || (this.Group1 == cell.Group2 && this.Group2 == cell.Group1);
}

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

    // If either is null, return false.
    // Cast a and b to objects to check for null to avoid calling this operator method
    // and causing an infinite loop.
    if ((object)a == null || (object)b == null)
    {
        return false;
    }

    return (a.Group1 == b.Group1 && a.Group2 == b.Group2) 
           || (a.Group1 == b.Group2 && a.Group2 == b.Group1);
}

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

public override int GetHashCode()
{
    int hash;
    unchecked
    {
        hash = Group1.GetHashCode() * Group2.GetHashCode();
    }

    return hash;
}

如您所见,DistanceCell的要求之一是Group1Group2可以互换。因此,对于两个字符串xyDistanceCell("x", "y")必须等于DistanceCell("y", "x")。这就是我使用乘法实现GetHashCode()的原因,因为DistanceCell("x", "y").GetHashCode()必须等于DistanceCell("y", "x").GetHashCode()

我遇到的问题是它在大约90%的时间内正常工作,但在剩余的时间内会抛出KeyNotFoundExceptionNullReferenceException。从字典中获取密钥时抛出前者,后者在我使用foreach循环遍历字典时抛出它,然后它检索一个为空的密钥,然后尝试调用Equals()。我怀疑这与我的GetHashCode()实现中的错误有关,但我并不积极。还要注意,由于算法的性质,在检查时不应该存在字典中不存在密钥的情况。该算法在每次执行时都采用相同的路径。

更新

我只想更新所有问题已解决的问题。事实证明它与我的Equals()或GetHashCode()实现无关。我做了一些大量的调试,发现我得到一个KeyNotFoundException的原因是因为键首字母中不存在键,这很奇怪,因为我肯定是它被添加了。问题是我用多个线程将字符串添加到字典中,根据这个,c#Dictionary类不是线程安全的。因此,添加()失败的时机必须是完美的,因此密钥永远不会添加到字典中。我相信这也可以解释foreach循环有时如何提出一个空键。来自多个线程的Add()必须弄乱字典中的一些内部结构并引入一个空键。

感谢每一位人士的帮助!对不起,它最终完全是我的错。

3 个答案:

答案 0 :(得分:4)

看看Eric Lippert的this blog post

它表示即使您更改了对象的内容,GetHashCode的结果也不会改变。原因是Dictionary使用存储桶来加快索引速度。更改GetHasCode结果后,Dictionary将无法为您的对象找到正确的存储桶,这可能导致“找不到键”

我可能错了,但值得测试。

答案 1 :(得分:2)

我相信你缺少的是这个答案 Using an object as a generic Dictionary key

您可能没有说明您实现IEquatable接口

答案 2 :(得分:0)

对我而言,您的代码看起来是正确的。 (它可能是(微)优化的,但它看起来好像在所有情况下都是正确的。)你可以在创建和使用Dictionary<DistanceCell, double>的地方提供一些代码吗?事情变坏了吗?问题可能在于您没有向我们展示过的代码。