【问题标题】:Key exists, but giving KeyNotFound exception密钥存在,但给出 KeyNotFound 异常
【发布时间】:2015-05-28 02:47:29
【问题描述】:

我创建了一个类,它使用自定义类作为键和值创建字典,并通过 xml 序列化存储它。然后点击“加载”反序列化xml文件并加载数据。

问题在于以下代码。在反序列化并尝试加载字典后,我在这里不断收到 KeyNotFound 异常:

foreach (Perk p in perksTier1[skill])
{
    string s = p.Name.ToString();
    if (!lboxTier1.Items.Contains(s))
        lboxTier1.Items.Add(s);
}

但是,当我单步执行代码并检查字典时,它看起来就像它应该的那样,并且清楚地显示了 Skill 键。

这是真正奇怪的部分: 使用 ContainsKey 返回 null,但使用 GetKeys GIVES ME THE KEY

呜呜。请帮忙。

【问题讨论】:

  • 你确定它们是相等的字符串(如果键是字符串的话)?也许不同的外壳?你在哪里得到错误,perksTier1[skill]
  • 可能在检查器中看不到的键值之前或之后有一些尾随空格?当从字节数组反序列化为对象时,我经常收到此错误。试试 Skill.Trim() 看看会发生什么?
  • 如果你使用你写的其他类作为键,你是如何实现 GetHashCode 和 Equals 的?必须实现这些方法properly 才能使字典按预期运行。
  • 可以向字典提供IEquailityComparer<TKey> 的实现,而不是在类本身上定义相等性。
  • 另外,Ken...关于 VS 的破解是个笑话。看到 c# 标签了吗?我知道 IDE 和语言之间的区别。

标签: c# dictionary


【解决方案1】:

您提到了ContainsKey,但您使用的是Contains。它们是不相同的。包含正在检查字典中的值。 ContainsKey Method 返回真或假。如果您得到 NULL,则说明您使用的方法不正确。

【讨论】:

  • ListBox.Items 不是字典,他似乎在谈论perksTier1 上的索引器。
  • 字典定义如下: Dictionary>
【解决方案2】:

嘘!找到了。有关详细信息,请参阅此帖子:GetHashCode and Equals implementation in Dictionary C#

要修复导入的字典,我必须在 Skill 类中重写 Equals 和 GetHashCode() 方法:

    public override bool Equals(object obj)   
    {
        Skill newskill;
        newskill = (Skill)obj;
        return (obj.GetHashCode() == newskill.GetHashCode());

    }
    public override int GetHashCode()    
    {
        int temp = name.GetHashCode();
        return temp;
    }

(感谢 Mike 和 Preston 为我指明了正确的方向)

【讨论】:

  • 您应该阅读 Mike 评论中的链接,此实现可能会按预期工作,但会产生误报。 (你也应该避免这种不安全的演员阵容。)
【解决方案3】:

您的密钥是一个自定义对象。默认情况下,字典将使用对象引用比较来确定给定的键实例是否存在于字典的Keys 集合中。由于它使用引用比较,因此引用并不相同,因为它们将指向不同的引用(即使它们可能“看起来”相同,因为它们具有相同的值)。

您需要告诉字典它应该如何测试相等性。这通常在用作键的对象中完成,在这种情况下是您的 Skill 对象,通过提供自定义 Equals 方法或通过实现定义用于确定两个实例是否相等的“规则”的EqualityComparer<Skill> 对象。

【讨论】:

    【解决方案4】:

    根据您的解释,您使用自定义类作为键。

    当使用引用类型作为键时,您需要确保使用对字典键所引用的同一对象的引用。

    例如,如果你有这种类型

    public class Skill
        {
            public string SkillName
            {
                get;
                set;
            }
        }
    

    那你就这样用吧

    Dictionary<Skill, object> dic = new Dictionary<Skill, object>();
                for (int i = 0; i < 5; i++)
                {
                    dic.Add(new Skill() { SkillName = i.ToString() }, new object());
                }
    
                Skill lookup = new Skill() { SkillName = "0"};
    
                Console.WriteLine(dic.ContainsKey(lookup));
    

    这将返回 false,因为查找引用指向的对象与循环中创建的对象不同,请注意 SkillName 具有相同的值(在本例中为“0”)。

    如果把代码改成下面这样

    Dictionary<Skill, object> dic = new Dictionary<Skill, object>();
                for (int i = 0; i < 5; i++)
                {
                    dic.Add(new Skill() { SkillName = i.ToString() }, new object());
                }
    
                Skill lookup = dic.Keys.FirstOrDefault(sk => sk.SkillName == "0");
    
                Console.WriteLine(dic.ContainsKey(lookup));
    

    您会注意到控制台行现在打印为 true。这是因为查找引用指向的是字典所具有的同一个对象。

    这与您的问题有什么关系?它的序列化和反序列化。如果你像这样创建一个 Skill 类型的对象

    Skill skill1 = new Skill(){SkillName = "C#"};
    

    然后使用您选择的序列化程序对其进行序列化

    Serializer.Serialize(skill1,streem);
    

    然后你像这样反序列化它

    Skill skill2 = Serializer.Deserialize(stream);
    

    然后打印skill1 == skill2你会发现它会返回false,这是因为序列化程序在反序列化对象时所做的是它创建了新对象并使用与第一个对象相同的值填充它。

    规则非常简单,引用相等是基于指向同一个对象,而不是基于具有相同属性值的两个不同对象。

    鉴于您没有提供足够的信息,希望我能说到点子上

    【讨论】:

    • 很好的解释,谢谢。 (我没有包含我的完整代码,因为它真的很长,不包括单独的 Perk、Skill 和序列化类)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-11
    • 2018-04-19
    • 2011-01-27
    • 2015-08-23
    • 2021-09-29
    相关资源
    最近更新 更多