5

I currently have a menu with subitems that is being stored in this dictionary variable:

private Dictionary<string, UserControl> _leftSubMenuItems 
    = new Dictionary<string, UserControl>();

So I add views to the e.g. the "Customer" section like this:

_leftSubMenuItems.Add("customers", container.Resolve<EditCustomer>());
_leftSubMenuItems.Add("customers", container.Resolve<CustomerReports>());

But since I am using a Dictionary, I can only have one key named "customers".

My natural tendency would be to now create a custom struct with properties "Section" and "View", but is there a .NET collection is better suited for this task, something like a "MultiKeyDictionary"?

ANSWER:

Thanks maciejkow, I expanded your suggestion to get exactly what I needed:

using System;
using System.Collections.Generic;

namespace TestMultiValueDictionary
{
    class Program
    {
        static void Main(string[] args)
        {
            MultiValueDictionary<string, object> leftSubMenuItems = new MultiValueDictionary<string, object>();

            leftSubMenuItems.Add("customers", "customers-view1");
            leftSubMenuItems.Add("customers", "customers-view2");
            leftSubMenuItems.Add("customers", "customers-view3");
            leftSubMenuItems.Add("employees", "employees-view1");
            leftSubMenuItems.Add("employees", "employees-view2");

            foreach (var leftSubMenuItem in leftSubMenuItems.GetValues("customers"))
            {
                Console.WriteLine(leftSubMenuItem);
            }

            Console.WriteLine("---");

            foreach (var leftSubMenuItem in leftSubMenuItems.GetAllValues())
            {
                Console.WriteLine(leftSubMenuItem);
            }

            Console.ReadLine();
        }
    }

    public class MultiValueDictionary<TKey, TValue> : Dictionary<TKey, List<TValue>>
    {

        public void Add(TKey key, TValue value)
        {
            if (!ContainsKey(key))
                Add(key, new List<TValue>());
            this[key].Add(value);
        }

        public List<TValue> GetValues(TKey key)
        {
            return this[key];
        }

        public List<TValue> GetAllValues()
        {
            List<TValue> list = new List<TValue>();

            foreach (TKey key in this.Keys)
            {
                List<TValue> values = this.GetValues(key);
                list.AddRange(values);
            }

            return list;
        }
    }

}

Answer 2:

Thanks Blixt for the tip about yield, here is GetAllValues with that change:

public IEnumerable<TValue> GetAllValues()
{
    foreach (TKey key in this.Keys)
    {
        List<TValue> values = this.GetValuesForKey(key);
        foreach (var value in values)
        {
            yield return value;
        }
    }
}

Answer 2 refactored further:

Here is a much more succinct way to do the same thing, thanks Keith:

public IEnumerable<TValue> GetAllValues()
{
    foreach (var keyValPair in this)
        foreach (var val in keyValPair.Value)
            yield return val;
}
3
  • 1
    For your GetAllValues method you could use the yield keyword instead of generating a new list, which uses more memory and is less efficient. Commented Jul 27, 2009 at 9:53
  • That might be all that you need, but its hardly a complete MultiDictionary. What about Remove? Enumerating values? etc. Commented Jul 27, 2009 at 9:54
  • 1
    In fact, the proper solution here is to not reinvent the wheel and use one of the libraries linked in the answers, or look for another one that does more what you need. For such a general purpose, you're sure to find one that is well-tested and optimized. Commented Jul 27, 2009 at 9:56

8 Answers 8

10

If you need variable number of values for one key, why not create Dictionary<string, List<UserControl>> ? Furthermore, you could inherit this class and create your own Add, get same syntax you're using now. This way you can avoid manual adding of empty lists before adding new control.

sth like this:

class MultiValueDictionary<TKey, TValue> : Dictionary<TKey, List<TValue>>
{

   public void Add(TKey key, TValue value)
   {
      if(!ContainsKey(key))
         Add(key, new List<TValue>());
      this[key].Add(value);
   }
}
Sign up to request clarification or add additional context in comments.

1 Comment

There's a debugged one referenced at stackoverflow.com/questions/1187219/… ?
5

Check out NGenerics' HashList. It's a Dictionary which maintains a list of values for each key. Wintellect's PowerCollections library also has a handy MultiDictionary class which does things like automatically clean up when you remove the last value associated with a given key.

1 Comment

+1: A wrapper that allows a more natural way to do what I proposed.
4

How about making the container value type a list:

private Dictionary<string, List<UserControl>> _leftSubMenuItems =
    new Dictionary<string, List<UserControl>>();

if (!_leftSubMenuItems.ContainsKey("customers"))
{
    _leftSubMenuItems["customers"] = new List<UserControl>();
}
_leftSubMenuItems["customers"].Add(container.Resolve<EditCustomer>());
_leftSubMenuItems["customers"].Add(container.Resolve<CustomerReports>());

Comments

3

Just a few tweaks...

public class MultiValueDictionary<TKey, TValue> : 
    Dictionary<TKey, List<TValue>>
{

    public void Add(TKey key, TValue value)
    {
        List<TValue> valList;
        //a single TryGetValue is quicker than Contains then []
        if (this.TryGetValue(key, out valList))
            valList.Add(value);
        else
            this.Add( key, new List<TValue> { value } );
    }

    //this can be simplified using yield 
    public IEnumerable<TValue> GetAllValues()
    {
        //dictionaries are already IEnumerable, you don't need the extra lookup
        foreach (var keyValPair in this)
            foreach(var val in keyValPair.Value);
                yield return val;
    }
}

2 Comments

+1 for yield, although I still believe it's badly spent time implementing your own wrapper when there is almost certainly several others that are complete (i.e. have all functionality one might expect from a MultiValueDictionary) and that have been well-tested by many users.
Can simplify GetAllValues() to this: public IEnumerable<TValue> GetAllValues() { return this.SelectMany(keyValPair => keyValPair.Value); }
3

The .NET framework 3.5 includes a special LINQ Lookup class.

It is similar to a dictionary except that it can handle multiple items with the same key. When you do a search using a given key, instead of receiving a single element, you receive a group of elements that match that key.

I read that it is a hashtable under the covers so it is fast for retrieving.

You use it something like this:

var example1 = (from element in ListWithDuplicates
            select element)
           .ToLookup(A => A.Name);

There are a bunch of caveats:

  • The Lookup class has no public constructor, so you cant just create a Lookup object, it seems to only be available using the .ToLookup syntax.
  • You cannot edit it once it has been created, no Add or Remove etc.
  • Apparently its not serializable
  • Using the grouped data can be a bit tricky

Theres a great article here discussing the Lookup and its implications in more detail.

Comments

1

No, there's no better built-in collection. I think your "natural tendency" is perfectly suited for solving this problem, as those are not really "same keys," but unique keys composed of different parts and Dictionary does the job. You can also nest dictionary (makes sense if you have large number of values for each name):

Dictionary<string, Dictionary<Type, object>> dict = ...;
var value = (T)dict[name][typeof(T)];

This approach will resolve to the element using a single hash table lookup. If you maintain a list of items for each element, you'll have to linearly traverse the list each time you need an element to lookup which defeats the purpose of using a Dictionary in the first place.

Comments

0

I don't know of a "MultiKeyDictionary". I'd recommend using a struct and overriding GetHashCode, Equals and implementing IEquatable<StructName> (which is used by Dictionary<TKey,TValue>).

Comments

0

Are you looking to store multiple entries per key together? Somethign like this ?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.