Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Multi-key Generic Dictionary Class for C#

4.38/5 (5 votes)
20 May 2011CPOL1 min read 19.8K  
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)