4

I want to use the following object as Dictionary key. If the Category and Target are equal, the key are equal. Any solution?

public class TargetKey
{
    public TargetKey(Categories category_arg, String target_arg)
    {
        catetory = category_arg;
        target = target_arg;
    }
    private Categories catetory;
    public Categories Catetory
    {
        get { return catetory; }
        //set { catetory = value; }
    }
    private String target;
    public String Target
    {
        get { return target; }
        //set { target = value; }
    }
}

Bad Solution

It seems GetHashCode() is called first, if the hash equals, then Equals() is called. So I add the following 2 methods:

    public override bool Equals(object obj)
    {
        TargetKey other = obj as TargetKey;
        return other.Catetory == this.Catetory && other.Target == this.Target;
    }

    public override int GetHashCode()
    {
        return 0;  //this will leads to ONLY 1 bucket, which defeat the idea of Dictionary.
    }

Refined Solution

    public override bool Equals(object obj)
    {
        TargetKey other = obj as TargetKey;
        return other.Catetory == this.Catetory && other.Target == this.Target;
    }

    public override int GetHashCode()
    {
        Int32 hash = this.Target.GetHashCode() + this.Catetory.GetHashCode(); 
        // This will introduce some more buckets. Though may not be as many as possible.
        return hash;
    }
smwikipedia
  • 61,609
  • 92
  • 309
  • 482
  • 1
    Because your object is mutable, this would not be advisable. Supposing somebody changes the value of `target` or `category` when it is in use as a dictionary key? It's going to break the dictionary. For this reason, you should only ever use immutable objects as dictionary keys... so you'll have to drop the setters. – spender Mar 25 '11 at 09:56
  • Thanks, I removed set method now. – smwikipedia Mar 25 '11 at 09:57
  • 1
    Arrghh! this is a terrible idea. you will now be storing everything in the same bucket in the dictionary, so you may as well just use a list and call Contains() as your implementation will have to check EVERY item in the dictionary for every check rather than just the items in the same bucket as the hashcode. – Sam Holder Mar 25 '11 at 10:43
  • 2
    You should post your solution as an answer so the community can vote it down, rather than as part of your question. – Sam Holder Mar 25 '11 at 10:45
  • Read the [MSDN](http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx) documentation of GetHashCode and of Equals. – Sam Holder Mar 25 '11 at 10:51
  • 1
    Whilst your refined solution is better please read [this question and answers](http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode) and [this](http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx) article to see why there may be issues with it. You might at least want to wrap it in an unchecked block. – Sam Holder Mar 25 '11 at 11:02

4 Answers4

4

Override and implement Equals() and GetHashCode() based on your Category and Target and this will allow them to be used for comparison as the key of the dictionary.

Here is a suggested implementation, but what exactly needs to be done will depend on if they can be null or not. I have assumed that they can be in the implementation below as there is no null check in the constructor:

public class TargetKey
{
    public TargetKey(Categories category_arg, String target_arg)
    {
        Catetory = category_arg;
        Target = target_arg;
    }
    private Categories catetory;
    public Categories Catetory
    {
        get { return catetory; }
    }
    private String target;
    public String Target
    {
        get { return target; }
    }

    public bool Equals (TargetKey other)
        {
        if (ReferenceEquals (null, other))
            {
            return false;
            }
        if (ReferenceEquals (this, other))
            {
            return true;
            }
        return Equals (other.catetory, catetory) && Equals (other.target, target);
        }

    public override bool Equals (object obj)
        {
        if (ReferenceEquals (null, obj))
            {
            return false;
            }
        if (ReferenceEquals (this, obj))
            {
            return true;
            }
        if (obj.GetType () != typeof (TargetKey))
            {
            return false;
            }
        return Equals ((TargetKey) obj);
        }

    public override int GetHashCode ()
        {
        unchecked
            {
            return ((catetory != null ? catetory.GetHashCode () : 0)*397) ^ (target != null ? target.GetHashCode () : 0);
            }
        }
}
Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • 1
    As an aside, Resharper (and probably other products too) will write all this boilerplate code in about 3 or 4 mouse clicks. – spender Mar 25 '11 at 10:00
  • 1
    @spender, resharper **did** write that code in 3 or 4 clicks :) – Sam Holder Mar 25 '11 at 10:04
  • No offense but is this kind of an overkill? – smwikipedia Mar 25 '11 at 10:22
  • @swmikipedia, it might seem verbose but it checks for equality in all situations. If you can guarentee your code won't encounter some of those situations then by all means don't check them. but this covers all bases, and as was pointed out the code was automatically generated, so it didn't take a long time to write – Sam Holder Mar 25 '11 at 10:33
  • 1
    the 397 is just a random prime number used to distribute the hashcode more effectively I think. there are many options for gethashcode implementation, that is just one. – Sam Holder Mar 25 '11 at 10:41
1

You could override GetHashCode and Equals.

Those two methods are used by Dictionary to determine if two keys are equal. It's also the way it looks up the value of a key. See the documentation of GetHashCode about how a Dictionary will do that.

[edit]
A (very) crude way to look at a hash code is as the index of a big array.

bool ContainsKey(object key)
{
    int hashCode = key.GetHashCode();
    object foundKey = this.keys[hashCode];
    return key.Equals(foundKey);
}

Note that this is an extremely simplified way of the implementation of a dictionary. A real dictionary will not have a huge array. A real dictionary will do null checking. A real dictionary can handle different keys with the same hash code, although does kind of keys will impact the performance. etc.

Patrick Huizinga
  • 1,342
  • 11
  • 25
  • Thanks. How exactly does a dictionary use the GetHashCode and Equals? I only override the Equals method and return true if both Category and Target strings are equal. But it doesn't work. – smwikipedia Mar 25 '11 at 10:19
  • 1
    The dictionary will choose a bucket for the object based on the hashcode. it will then use the hash code of the other object to find the bucket of things to check, and will check each one using equals to see if they are the same (it may only do this if there is more than 1 item in the bucket, I'm not certain). That is why overriding equals alone doesn't work, because the bucket containing the equal instance is not found (as the hashcodes are different) so the equality method is never checked. So you need to override both. – Sam Holder Mar 25 '11 at 10:36
  • So, the hashing is just to limit the number of items I need to search. Many thanks for your excellent explanation. I refined my solution. – smwikipedia Mar 25 '11 at 10:53
0

If the key is part of the class then use KeyedCollection. It is a Dictionary where the key is derived from the object. Under the covers it is Dictionary. Don't have to repeat the key in the Key and Value. Why take a chance the key is not the same in the Key as the Value. Don't have to duplicate the same information in memory.

You did not define Category or Categories so used string.

KeyedCollection Class

Indexer to expose the composite key

using System.Collections.ObjectModel;

namespace KeyCollStringString
{
    class Program
    {
        static void Main(string[] args)
        {
            StringStringO ss1 = new StringStringO("Sall","John");
            StringStringO ss2 = new StringStringO("Sall", "John");
            if (ss1 == ss2) Console.WriteLine("same");
            if (ss1.Equals(ss2)) Console.WriteLine("equals");
            // that are equal but not the same I don't override = so I have both features

            StringStringCollection stringStringCollection = new StringStringCollection();
            // dont't have to repeat the key like Dictionary
            stringStringCollection.Add(new StringStringO("Ringo", "Paul"));
            stringStringCollection.Add(new StringStringO("Mary", "Paul"));
            stringStringCollection.Add(ss1);
            //this would thow a duplicate key error
            //stringStringCollection.Add(ss2);
            //this would thow a duplicate key error
            //stringStringCollection.Add(new StringStringO("Ringo", "Paul"));
            Console.WriteLine("count");
            Console.WriteLine(stringStringCollection.Count.ToString());
            // reference by ordinal postion (note the is not the long key)
            Console.WriteLine("oridinal");
            Console.WriteLine(stringStringCollection[0].GetHashCode().ToString());
            // reference by index
            Console.WriteLine("index");
            Console.WriteLine(stringStringCollection["Mary", "Paul"].GetHashCode().ToString());
            Console.WriteLine("foreach");
            foreach (StringStringO ssO in stringStringCollection)
            {
                Console.WriteLine(string.Format("HashCode {0} String1 {1} String2 {2} ", ssO.GetHashCode(), ssO.String1, ssO.String2));
            }
            Console.WriteLine("sorted by date");
            foreach (StringStringO ssO in stringStringCollection.OrderBy(x => x.String1).ThenBy(x => x.String2))
            {
                Console.WriteLine(string.Format("HashCode {0} String1 {1} String2 {2} ", ssO.GetHashCode(), ssO.String1, ssO.String2));
            }
            Console.ReadLine();
        }
        public class StringStringCollection : KeyedCollection<StringStringS, StringStringO>
        {
            // This parameterless constructor calls the base class constructor 
            // that specifies a dictionary threshold of 0, so that the internal 
            // dictionary is created as soon as an item is added to the  
            // collection. 
            // 
            public StringStringCollection() : base(null, 0) { }

            // This is the only method that absolutely must be overridden, 
            // because without it the KeyedCollection cannot extract the 
            // keys from the items.  
            // 
            protected override StringStringS GetKeyForItem(StringStringO item)
            {
                // In this example, the key is the part number. 
                return item.StringStringS;
            }

            //  indexer 
            public StringStringO this[string String1, string String2]
            {
                get { return this[new StringStringS(String1, String2)]; }
            }
        }

        public struct StringStringS
        {   // required as KeyCollection Key must be a single item
            // but you don't reaaly need to interact with Int32Int32s
            public readonly String String1, String2;
            public StringStringS(string string1, string string2) { this.String1 = string1.Trim(); this.String2 = string2.Trim(); }
        }
        public class StringStringO : Object
        {
            // implement you properties
            public StringStringS StringStringS { get; private set; }
            public String String1 { get { return StringStringS.String1; } }
            public String String2 { get { return StringStringS.String2; } }
            public override bool Equals(Object obj)
            {
                //Check for null and compare run-time types.
                if (obj == null || !(obj is StringStringO)) return false;
                StringStringO item = (StringStringO)obj;
                return (this.String1 == item.String1 && this.String2 == item.String2);
            }
            public override int GetHashCode() 
            {
                int hash = 17;
                // Suitable nullity checks etc, of course :)
                hash = hash * 23 + String1.GetHashCode();
                hash = hash * 23 + String1.GetHashCode();
                return hash;
            }
            public StringStringO(string string1, string string2)
            {
                StringStringS stringStringS = new StringStringS(string1, string2);
                this.StringStringS = stringStringS;
            }
        }
    }
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176
-1

Just use generic dictionary collection (C# Code)

Dictionary<TargetKey> objMyCollection = new Dictionary<TargetKey>.();

Use ContainsKey() method of objMyCollection like :

if(objMyCollection.ContainsKey(new TargetKey()) 
{
    MessageBox.Show("Obj find");
}
else
{
    MessageBox.Show("key not found add new one");
}
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Unless you override `Equals` and `GetHashCode`, two instances of `TargetKey` with same values for `category` and `target` will not be equal. -1 – spender Mar 25 '11 at 10:05