Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

Multi-key Generic Dictionary Class for C#

Rate me:
Please Sign up or sign in to vote.
4.38/5 (5 votes)
20 May 2011CPOL1 min read 19.3K   6   4
MultiKeyDictionary is a C# class that wraps and extends the Generic Dictionary object provided by Microsoft in .NET 2.0 and above.

MultiKeyDictionary is a C# class that wraps and extends the Generic Dictionary object provided by Microsoft in .NET 2.0 and above. This allows a developer to create a generic dictionary of values and reference the value list through two keys instead of just the one provided by the Microsoft implementation of the Generic Dictionary<...>. You can see my article on CodeProject (here), however this code is more up-to-date and bug free.

While writing a massive socket management application, I needed the ability to keep a list of sockets that I could identify by either their remote endpoint (IPEndPoint) or by a string that represented the internal ID of the socket. Thus was born the MultiKeyDictionary class.

Using the MultiKeyDictionary class is simple: instantiate the class, specifying the primary key, sub key, and value types in the generic constructor, then start adding your values and keys.

Example

For this example, let's say I wanted to create a dictionary which stores the string representation of a number, i.e., 'Zero', 'One', 'Two', etc. Now, I want to access that list of items via an integer representation and a binary (in string format) representation.

C#
// Adding 'Zero' to dictionary with primary int key of '0'
dictionary.Add(0, "Zero");
// Associating binary sub key of '0000' with primary int key of '0'
dictionary.Associate("0000", 0);

//Adding 'Three' to dictionary with primary
//int key of '3' and a binary sub key of '0011'");
dictionary.Add(3, "0011", "Three");

// Getting value for binary sub key 0000
val = dictionary["0000"]; // val will be Zero
// Getting value for int primary key 0
val = dictionary[0]; // val will be Zero

The Code

Here is the code that I am using to do all this fancy stuff... feel free to steal it.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Aron.Weiler
{
    /// <summary>
    /// Multi-Key Dictionary Class
    /// </summary>
    /// <typeparam name="V">Value Type</typeparam>
    /// <typeparam name="K">Primary Key Type</typeparam>
    /// <typeparam name="L">Sub Key Type</typeparam>
    public class MultiKeyDictionary<K, L, V>
    {
        internal readonly Dictionary<K, V> baseDictionary = 
                 new Dictionary<K, V>();
        internal readonly Dictionary<L, K> subDictionary = 
                 new Dictionary<L, K>();
        internal readonly Dictionary<K, L> primaryToSubkeyMapping = 
                 new Dictionary<K, L>();

        readonly object lockObject = new object();

        public V this[L subKey]
        {
            get
            {
                V item;
                if (TryGetValue(subKey, out item))
                    return item;

                throw new KeyNotFoundException(
                    "sub key not found: " + subKey.ToString());
            }
        }

        public V this[K primaryKey]
        {
            get
            {
                V item;
                if (TryGetValue(primaryKey, out item))
                return item;

                throw new KeyNotFoundException(
                  "primary key not found: " + primaryKey.ToString());
            }
        }

        public void Associate(L subKey, K primaryKey)
        {
            lock (lockObject)
            {
                if (!baseDictionary.ContainsKey(primaryKey))
                throw new KeyNotFoundException(string.Format(
                  "The base dictionary does not contain the key '{0}'", primaryKey));

                subDictionary[subKey] = primaryKey;
                primaryToSubkeyMapping[primaryKey] = subKey;
            }
        }

        public bool TryGetValue(L subKey, out V val)
        {
            val = default(V);

            lock (lockObject)
            {
                K ep;
                if (subDictionary.TryGetValue(subKey, out ep))
                {
                    if (!TryGetValue(ep, out val))
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            return true;
        }

        public bool TryGetValue(K primaryKey, out V val)
        {
            lock (lockObject)
            {
                if (!baseDictionary.TryGetValue(primaryKey, out val))
                {
                    return false;
                }
            }
            return true;
        }

        public bool ContainsKey(L subKey)
        {
            V val;
            return TryGetValue(subKey, out val);
        }

        public bool ContainsKey(K primaryKey)
        {
            V val;
            return TryGetValue(primaryKey, out val);
        }

        public void Remove(K primaryKey)
        {
            lock (lockObject)
            {
                if (primaryToSubkeyMapping.ContainsKey(primaryKey))
                {
                    if (subDictionary.ContainsKey(primaryToSubkeyMapping[primaryKey]))
                        subDictionary.Remove(primaryToSubkeyMapping[primaryKey]);

                    primaryToSubkeyMapping.Remove(primaryKey);
                }

                baseDictionary.Remove(primaryKey);
            }
        }

        public void Remove(L subKey)
        {
            lock (lockObject)
            {
                baseDictionary.Remove(subDictionary[subKey]);
                primaryToSubkeyMapping.Remove(subDictionary[subKey]);
                subDictionary.Remove(subKey);
            }
        }

        public void Add(K primaryKey, V val)
        {
            lock (lockObject)
            baseDictionary.Add(primaryKey, val);
        }

        public void Add(K primaryKey, L subKey, V val)
        {
            lock (lockObject)
            baseDictionary.Add(primaryKey, val);

            Associate(subKey, primaryKey);
        }

        public V[] CloneValues()
        {
            lock (lockObject)
            {
                V[] values = new V[baseDictionary.Values.Count];
                baseDictionary.Values.CopyTo(values, 0);
                return values;
            }
        }

        public K[] ClonePrimaryKeys()
        {
            lock (lockObject)
            {
                K[] values = new K[baseDictionary.Keys.Count];
                baseDictionary.Keys.CopyTo(values, 0);
                return values;
            }
        }

        public L[] CloneSubKeys()
        {
            lock (lockObject)
            {
                L[] values = new L[subDictionary.Keys.Count];
                subDictionary.Keys.CopyTo(values, 0);
                return values;
            }
        }

        public void Clear()
        {
            lock (lockObject)
            {
                baseDictionary.Clear();
                subDictionary.Clear();
                primaryToSubkeyMapping.Clear();
            }
        }

        public int Count
        {
            get
            {
                lock (lockObject)
                return baseDictionary.Count;
            }
        }

        public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
        {
            lock (lockObject)
            return baseDictionary.GetEnumerator();
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Check out my technical blog here: The Fyslexic Duck. You can find most of what I've put on CodeProject there, plus some additional technical articles.

Comments and Discussions

 
GeneralMy vote of 4 Pin
ArpitNagar21-May-11 6:50
ArpitNagar21-May-11 6:50 
GeneralMy vote of 3 Pin
Kamal Kant Joshi21-May-11 5:30
Kamal Kant Joshi21-May-11 5:30 
GeneralInteresting ! ... and a minor suggestion Pin
BillWoodruff20-May-11 18:56
professionalBillWoodruff20-May-11 18:56 
Hi Aron,

Very interesting code, thanks, reminds me of some experiments I did a long time ago where I was playing with creating dictionaries with "unusual" key objects, including keys that were dictionary objects.

May I suggest: you "flesh out" this article with some discussion of your implementation strategy, perhaps a paragraph on why you are using the "lock" object, and some estimation of cost (memory, performance) using this extended dictionary form compared to some other strategy.

Rate this #4 for now, but definitely see it becoming #5 if expanded, a bit.

regards, Bill Woodruff
"Reason is the natural order of truth; but imagination is the organ of
meaning." C.S. Lewis

GeneralRe: Interesting ! ... and a minor suggestion Pin
Aron Weiler23-Jun-11 20:03
Aron Weiler23-Jun-11 20:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.