-1

I've made a class that creates a dictionary using custom classes as the keys and values, and stores it via xml serialization. Then clicking "load" de-serializes the xml file and loads the data.

The problem is with the following code. I keep getting a KeyNotFound exception here after de-serializing and trying to load the dictionary:

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

However, when I step through the code and examine the dictionary, it looks just as it should, with the Skill key clearly present.

Here's the really weird part: Using ContainsKey returns null, but using GetKeys GIVES ME THE KEY

Wut. Please help.

Ken White
  • 123,280
  • 14
  • 225
  • 444
steelfeathers
  • 31
  • 1
  • 6
  • Are you sure that they are equal strings (if the keys are strings at all)? Perhaps different casing? Where are you getting the error, at `perksTier1[skill]`? – Ron Beyer May 28 '15 at 02:52
  • Maybe there are some trailing spaces before or after the key value that you cannot see in the inspector? I get this error quite often when de-serializes into object from a byte array. Try skill.Trim() and see what happen? – Tran Nguyen May 28 '15 at 02:54
  • 2
    If you are using some other class you wrote as keys, how did you implement GetHashCode and Equals? Those methods must be implemented [properly](http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx) for a dictionary to function as expected. – Mike Zboray May 28 '15 at 02:57
  • You *can* provide an implementation of `IEquailityComparer` to the dictionary in lieu of defining equality on the class itself. – Preston Guillot May 28 '15 at 03:00
  • *Visual Studio* is not a language. It's an IDE. *Visual Studio* can not cause issues with the problem you describe. If you're going to program, it's pretty important to know the difference between the language you're using and the IDE you're using to write it. – Ken White May 28 '15 at 03:01
  • 1
    Also, Ken...the crack about VS was a joke. See the c# tag? I know the difference between an IDE and a language. – steelfeathers May 28 '15 at 03:11
  • Why would I need to use GetHashCode and Equals? Just doing Dictionary perkTier1 = new Dictionary>(); does the trick. It's only after serialization and de-serialization that I have an issue. – steelfeathers May 28 '15 at 03:14
  • Because class default equality comparison is instance equality, and a deserialized instance of a class is not the same instance as the one in your dictionary. – Preston Guillot May 28 '15 at 03:16
  • Preston, could you explain that a bit more? Perhaps with a code snippet example? I have a feeling the problem is something along those lines, but instances are a bit beyond my skill level. EDIT: After de-serializing the xml file to skillTree, I call skill = skillTree.Skill and perkTier1 = skillTree.PerkTier1. Wouldn't that reset the instance of the dictionary? – steelfeathers May 28 '15 at 03:21

4 Answers4

1

You mention ContainsKey but you are using Contains. They are not the same. Contains is checking for the value in the dictonary. ContainsKey Method returns true or false. IF you are getting NULL, you are using the incorrect method.

Black Frog
  • 11,595
  • 1
  • 35
  • 66
0

Huzzah! Found it. See this post for the long details: GetHashCode and Equals implementation in Dictionary C#

To fix the imported dictionary, I had to override the Equals and GetHashCode() methods in my Skill class:

    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;
    }

(Thank you to Mike and Preston for pointing me in the right direction)

Community
  • 1
  • 1
steelfeathers
  • 31
  • 1
  • 6
  • You should read the link from Mike's comment, this implementation may appear to work as expected, but it will produce false positives. (You also should avoid that unsafe cast.) – Preston Guillot May 28 '15 at 04:11
0

Your key is a custom object. By default, the dictionary is going to use an object reference comparison to determine if the given instance of a key is present in the Keys collection of the dictionary. Since it's using a reference comparison, the references are not the same as they will point to different references (even though they may "look" the same because they have the same values).

You need to tell the dictionary how it should test for equality. This is typically done in the object being used as the key, in this case your Skill object, by providing a custom Equals method or by implementing an EqualityComparer<Skill> object that defines the "rules" for determining if two instances are equal.

Scott Dorman
  • 42,236
  • 12
  • 79
  • 110
0

Based on your explanation you are using custom class as your key.

When using reference types as key you need to make sure that you use a reference to the same object that is being referenced by the dictionary key.

For example if you have this type

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

Then you use it this way

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));

This will return false because lookup reference is pointing to an object which is different from the object that has been created in the loop, notice that the SkillName has the same value ("0" in this case).

If you change the code to the following

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));

You will notice that the console line is printing true now. This is because the lookup reference is pointing to the same object that the dictionary has.

How does this related to your question? Its the serialization and de-serialization. If you create an object of type Skill like this

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

Then serialize it using a serializer of your choice

Serializer.Serialize(skill1,streem);

Then you de-serialize it like this

Skill skill2 = Serializer.Deserialize(stream);

And print skill1 == skill2 you will find that it will return false, this is because what the serializer did when de-serializing the object is that it created new object and populated it with the same values that the first object had.

The rule is very simple reference equality is based on pointing to the same object, not on two different objects having the same property values.

Wish I have hit the spot given that you didn't provide enough information

  • Good explanation, thank you. (I didn't include my full code because it's really long, not including the separate Perk, Skill, and serialization classes) – steelfeathers May 28 '15 at 04:32