Click here to Skip to main content
Click here to Skip to main content

Multi-key generic dictionary class for C#

, 20 May 2011
Rate this:
Please Sign up or sign in to vote.
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.

// 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.

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

namespace Aron.Weiler
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// Multi-Key Dictionary Class
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><typeparam name="V">Value Type</typeparam>
</span>    /// <span class="code-SummaryComment"><typeparam name="K">Primary Key Type</typeparam>
</span>    /// <span class="code-SummaryComment"><typeparam name="L">Sub Key Type</typeparam>
</span>    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>>

License

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

About the Author

Aron Weiler
Technical Lead CareFusion
United States United States
I just looooove software.
 
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 Pinmemberarpitgold21-May-11 6:50 
GeneralMy vote of 3 PinmemberKamal Kant Joshi21-May-11 5:30 
GeneralInteresting ! ... and a minor suggestion PinmemberBillWoodruff20-May-11 18:56 
GeneralRe: Interesting ! ... and a minor suggestion PinmemberAron Weiler23-Jun-11 20:03 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140718.1 | Last Updated 20 May 2011
Article Copyright 2011 by Aron Weiler
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid