ferpega
ferpega

Reputation: 3224

C# Generic Dictionary TryGetValue doesn't find keys

I have this simple example:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<MyKey, string> data = new Dictionary<MyKey, string>();
            data.Add(new MyKey("1", "A"), "value 1A");
            data.Add(new MyKey("2", "A"), "value 2A");
            data.Add(new MyKey("1", "Z"), "value 1Z");
            data.Add(new MyKey("3", "A"), "value 3A");

            string myValue;
            if (data.TryGetValue(new MyKey("1", "A"), out myValue))
                Console.WriteLine("I have found it: {0}", myValue );

        }
    }

    public struct MyKey
    {
        private string row;
        private string col;

        public string Row { get { return row; } set { row = value; } }
        public string Column { get { return col; } set { col = value; } }

        public MyKey(string r, string c)
        {
            row = r;
            col = c;
        }
    }
}

This is working fine. But if I change the MyKey struct by a MyKey class in this way:

public class MyKey

Then method TryGetValue doesn't find any key in spite of the key is out there.

I am sure I am missing something obvious but I don't know what.

Any idea ?

Thanks


** Solution **

(please, see accepted solution for better GetHashCode resolution)

I have redefined MyKey class like this, and all is working fine now:

public class MyKey
{
    private string row;
    private string col;

    public string Row { get { return row; } set { row = value; } }
    public string Column { get { return col; } set { col = value; } }

    public MyKey(string r, string c)
    {
        row = r;
        col = c;
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is MyKey)) return false;

        return ((MyKey)obj).Row == this.Row && ((MyKey)obj).Column == this.Column;
    }

    public override int GetHashCode()
    {            
        return (this.Row + this.Column).GetHashCode();
    }    
}

Thanks to all people answered this.

Upvotes: 11

Views: 13704

Answers (3)

Arion
Arion

Reputation: 31239

You need to override Equals() and GetHashCode() in the class MyKey

Maybe something like this:

GetHashCode()

public override int GetHashCode()
{
   return GetHashCodeInternal(Row.GetHashCode(),Column.GetHashCode());
}
//this function should be move so you can reuse it
private static int GetHashCodeInternal(int key1, int key2)
{
    unchecked
    {
        //Seed
        var num = 0x7e53a269;

        //Key 1
        num = (-1521134295 * num) + key1;
        num += (num << 10);
        num ^= (num >> 6);

        //Key 2
        num = ((-1521134295 * num) + key2);
        num += (num << 10);
        num ^= (num >> 6);

        return num;
    }
}

Equals

public override bool Equals(object obj)
{
    if (obj == null)
        return false;
    MyKey p = obj as MyKey;
    if (p == null)
        return false;

    // Return true if the fields match:
    return (Row == p.Row) && (Column == p.Column);
}

Upvotes: 7

Ignacio Soler Garcia
Ignacio Soler Garcia

Reputation: 21855

Because classes are compared by default using reference comparison.

If you compare two objects you are doing a object.ReferenceEquals(obj1, obj2)

If you compare two structs you are doing a value comparison (like when you compare two ints for example).

If you want to compare two MyKey objects you need to implement you own Equals and GetHashCode method and it will be used automatically by the dictionary.

Upvotes: 4

MarcinJuraszek
MarcinJuraszek

Reputation: 125620

Struct is value type and Class is reference type, so when you use struct all values inside it are compared but when you use class instead only the object reference is checked.

You can change that behavior for certain classes by overriding Equals() method. You can also override == operator if you want. See samples on Guidelines for Overloading Equals() and Operator == (C# Programming Guide).

Edit:

your Equals() method should look like that:

public override bool Equals(System.Object obj)
    {
        MyKey p = obj as MyKey;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (row == p.row) && (col == p.col);
    }

Upvotes: 3

Related Questions