开发者

Use an Object as Dictionary key

开发者 https://www.devze.com 2023-02-19 12:16 出处:网络
I want to use the following object as Dictionary key. If the Category and Target are equal, the key are equal. Any solution?

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 { targ开发者_StackOverflow中文版et = 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;
    }


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


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.


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


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");
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号