1

To be more precise, I have something like this:

Dictionary<KeyValuePair<MyState, MyAction>, float> Dict = 
     new Dictionary<KeyValuePair<MyState, MyAction>, float>();

Later on, I have a self defined KeyValuePair object;

KeyValuePair<MyState, MyAction> kvp = 
    new KeyValuePair<MyState, MyAction>(new MyState(...), new MyAction(...));

My question is: if the kvp state and actions have the exactly same values with a pair in Dict (in Dict exists a KeyValuePair with an identical MyState with the kvp MyAction and also, the MyAction from Dict has the exactly same values with the MyAction in kvp). The only difference are that the reference is different.

Long story short, having 2 KeyValuePairs objects, both with the same value (different reference), how can I get the float Value from Dict, without having to iterate the whole dictionary just to manually compare each key.key and key.value just to see if the key is actually the same:

foreach(var StAct in Dict)
    if(StAct.Key.Key.Equals(kvp.Key) && 
        StAct.Key.Value.Equals(kvp.value))
  //where StAct.Key.Key is the MyState object and StAct.Key.Value is the MyAction object
    {
        MessageBox.Show(StAct.Value + "");
        break;
    }
Simply Me
  • 1,579
  • 11
  • 23
  • @ergonaut Soo, I have to override the KeyValuePair method? I did override the Equals method in MyState and MyAction, but how to properly override it for KVP? – Simply Me Nov 05 '15 at 17:44
  • @ergonaut KVP is likely .Net type and hence can't get its `Equals` to be changed. OP needs to either construct own type suitable for Key in dictionary (one with correct GetHashCode/Equals) or provide correct Equals for each of the component of the pair as covered in many existing questions. – Alexei Levenkov Nov 05 '15 at 17:48
  • You should not have to do anything - `KeyValuePair` is a struct, so it has a value type equality by default, which means it should just work. – MarcinJuraszek Nov 05 '15 at 17:49
  • If you have potentially same key in a dictionary, which value do you expect to retrieve? Are you expecting your dictionary to return multiple values, they way you do it in your foreach? I don't think a dictionary is meant for that. – blogbydev Nov 05 '15 at 18:06
  • No, its a single value. Actually I missed a break; my fault. And yes, it's only a value. – Simply Me Nov 05 '15 at 20:15

3 Answers3

2

You need to ensure that inside your MyState object, which serves as the key to the Dictionary, you have correctly overridden both the Equals() and GetHashCode() methods.

As you have noted, the default behaviour for classes is to check reference equality so if you want some different behaviour, you must provide that yourself.

public class MyState
{
    public override bool Equals(object obj)
    {
        // your equality implementation goes here
    }

    public override int GetHashCode()
    {
        // your hashcode implementation goes here
    }
}

Creating a well-behaved GetHashCode() method isn't necessarily trivial but you can find some good advice about how to do that in this answer: https://stackoverflow.com/a/371348/5438433

Once you have done that, you can simply write:

if(dict.ContainsKey(kvp.Key)) {.....}
Community
  • 1
  • 1
TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • I did override the Equals method. So basically, I need to implement a good GetHashCode, or else it will not work for me? Meaning... If I don't override GetHashCode, it won't work as I intend it to do? – Simply Me Nov 05 '15 at 18:04
  • @SimplyMe. Yes, if you override Equals() you should always override GetHashCode() too. – TheInnerLight Nov 05 '15 at 18:06
  • from what I understand, if two objects of the same type have the same value for the hashCode, it uses the Equals to see if they are equal as value? (ex: all MyState objects have the same hash code). – Simply Me Nov 05 '15 at 18:12
  • @SimplyMe Yes, that's right. In actuality the Dictionary/HashSet will probably use some modulo function of the hash code to decide on the appropriate bin so collisions requiring checks using Equals() will occur more often than raw GetHashCode() collisions. – TheInnerLight Nov 05 '15 at 18:18
  • if this is the case, I might just use a return 1,2 or 3 in the has and rely on the Equals to do the job. I will test it right away. – Simply Me Nov 05 '15 at 20:16
  • well, that worked just fine, saved a lot of time and learnt something new (@hashCode). Thanks! – Simply Me Nov 05 '15 at 21:58
  • @SimplyMe if you return the same hash code for all instances, you are guaranteeing that the dictionary will perform poorly, as it will do a linear search and test all keys for equality. That's exactly what you're trying to avoid ("without having to iterate the whole dictionary just to manually compare each key.key and key.value just to see if the key is actually the same"). – phoog Nov 06 '15 at 00:11
0

You need to implement GetHashCode() and Equals() on your KeyValuePair<>-class. These are the methods Dictionary<> uses to find a key.

The rule is that you have to override GetHashCode() if you override Equals(), because you have to ensure that two "equal" objects will always produce the same hash code. This is absolutely required for Dictionary<> to work correctly.

Dictionary<> first looks for the right key by searching for its hash code, and only then it uses Equals() to make sure it really found the right one.

However, you can have the same hash code for objects which are not equal. It is perfectly legal to implement GetHashCode() by always returning 1, for example, because it satisfies the rule that equal objects have the same hash code. However, it will make your Dictionary<> operations terribly slow - basically, the Dictionary<> will now have to search through all of its entries to find the right key, just as if it was a simple list.

What you probably want for your KeyValuePair<> is that two KeyValuePair<> objects are equal if both their keys and their values are equal in turn. You can implement that like this:

public class KeyValuePair<K, V>
{
    private K m_key;
    private V m_value;

    // [...] existing implementation left out

    public override bool Equals(object obj)
    {
        KeyValuePair<K, V> other = obj as KeyValuePair<K, V>;
        if(other == null)
            return false;
        return object.Equals(this.m_key, other.m_key) && object.Equals(this.m_value, other.m_value);
    }

    public override int GetHashCode()
    {
        int hashCode = 0;
        if(m_key != null)
            hashCode += m_key.GetHashCode();
        if(m_value != null)
            hashCode = hashCode * 31 + m_value.GetHashCode();
        return hashCode;
    }
}

Note that this requires that the K and V also implement Equals() and GetHashCode() in a way that makes sense for you.

Medo42
  • 3,821
  • 1
  • 21
  • 37
  • He cannot change anything within the implementation of KeyValuePair, KeyValuePair is a built-in .NET type. The overrides to Equals and GetHashCode must be done in his own objects. – TheInnerLight Nov 05 '15 at 18:29
  • You are right, I misread the OP. He said "I have a self defined KeyValuePair object", but I read that as "I have a self defined KeyValuePair type" somehow. I'll delete my answer. – Medo42 Nov 05 '15 at 20:46
0

Posting a detailed example :

public class Foo
{
    public int Id { get; set; }
    public string  Name { get; set; }

    public override bool Equals(object obj)
    {   
        return this.Id==((Foo)obj).Id
            && this.Name==((Foo)obj).Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode() + Name.ToLower().GetHashCode();
    }
}

public class Bar
{
    public int SomeOtherId { get; set; }
    public string SomeOtherName { get; set; }

    public override bool Equals(object obj)
    {
        return this.SomeOtherId == ((Bar)obj).SomeOtherId
            && this.SomeOtherName == ((Bar)obj).SomeOtherName;
    }
    public override int GetHashCode()
    {
        return SomeOtherId.GetHashCode() + SomeOtherName.ToLower().GetHashCode();
    }
}

//Usage
var dict = new Dictionary<KeyValuePair<Foo, Bar>, float>();
dict.Add(new KeyValuePair<Foo, Bar>(new Foo { Id = 1, Name = "Foo" }, new Bar {SomeOtherId = 1, SomeOtherName = "Bar"}), 10);

Console.WriteLine(dict[new KeyValuePair<Foo, Bar>(new Foo { Id = 1, Name = "Foo" }, new Bar {SomeOtherId = 1, SomeOtherName = "Bar"})]);

The answer is in accordance with the Title of the post. However, it still does not follow, if there are same keys in a dictionary(although different reference) because when you try to add such key(after you have applied the Equals & GetHashCode), it wont allow you to insert these same keys.

blogbydev
  • 1,445
  • 2
  • 17
  • 29
  • Why would I even need more keys. The pair are already unique, and merged with any float, there won't be any collisions. Yes, all the answeres out here helped a lot and I just want to try them right now to see if it validates properly. – Simply Me Nov 05 '15 at 20:18