5

My question: is there a canonical way of creating a dictionary of dictionaries and providing an outer/inner key pair? Is there a NugetPackage out there with an implementation?

In my code, I have now a few places where I have a property like this

private readonly IDictionary<string, IDictionary<string, SomeType>> _nestedDict = 
        new Dictionary<string, IDictionary<string, SomeType>>();

and I've created extension methods like this

public static V AddOrUpdate<T, U, V>(
    [NotNull] this IDictionary<T, IDictionary<U, V>> dictionary,
    [NotNull] T outerKey,
    [NotNull] U innerKey,
    [NotNull] Func<V> addValueFactory,
    [NotNull] Func<V, V> updateValueFactory,
    [CanBeNull] IEqualityComparer<U> innerDictionaryComparer = null)
{
    IDictionary<U, V> dict;
    if (dictionary.TryGetValue(outerKey, out dict))
    {
        V currentValue;
        if (dict.TryGetValue(innerKey, out currentValue))
        {
            var updatedValue = updateValueFactory(currentValue);
            if (!object.ReferenceEquals(updatedValue, currentValue))
                dict[innerKey] = updatedValue;
            return updatedValue;
        }
        var addedValue1 = addValueFactory();
        dict[innerKey] = addedValue1;
        return addedValue1;
    }

    var addedValue = addValueFactory();
    if (innerDictionaryComparer != null)
        dictionary[outerKey] = new Dictionary<U, V>(innerDictionaryComparer) {{innerKey, addedValue}};
    else
        dictionary[outerKey] = new Dictionary<U, V> {{innerKey, addedValue}};
    return addedValue;
}

This is powerful, but I'm starting to feel like I should just make a class for nested dictionaries. Here's my thinking:

  1. The addValueFactory and updateValueFactory are sort of clumsy and it feels like it's breaking OO principles.
  2. I am thinking about creating a ConcurrentDictionary of Dictionarys (or perhaps of ConcurrentDictionarys), and this would require some more extension methods. I sort of thought if I'm using it this much that I should just create the darn class and be done with it.

What say you?

  • 1
    How useful is the extension method? I'm all for doing things "right," but I prefer doing things effectively. If it makes sense to create the class, then create the class. If you can't decide, simpler is always better, all other things being equal. – Robert Harvey May 23 '16 at 15:01
  • @RobertHarvey as simple as possible, but no simpler, is wise. I was just wondering if anyone had a class that they knew about, perhaps in a NugetPackage that's easily accessible (and already tested) – Frank Bryce May 23 '16 at 15:06
  • 4
    I would just define my own type MultiKeyDictionary<k, s, v>... Where k = key, s = sub, v = value. Then create an extension method as you have done. – Snoop May 23 '16 at 15:45
  • 6
    Do you really need a dictionary of dictionaries or would Dictionary<Tuple<T,U>, V> work? – Sign May 23 '16 at 16:10
  • 1
    All, I think you have a point and I may be over-engineering this problem at this point. I think a Dictionary (or ConcurrentDictionary) with Tuple keys may be the easiest. I'll get good bang for the buck with this design I think – Frank Bryce May 23 '16 at 17:14
3

There may be a simpler way towards it.

Consider this:

    public class NAryDictionary<TKey, TValue> :
        Dictionary<TKey, TValue>
    {
    }

    public class NAryDictionary<TKey1, TKey2, TValue> :
        Dictionary<TKey1, NAryDictionary<TKey2, TValue>>
    {
    }

    public class NAryDictionary<TKey1, TKey2, TKey3, TValue> :
        Dictionary<TKey1, NAryDictionary<TKey2, TKey3, TValue>>
    {
    }

Then you can write:

    class Program
    {
        static void Main(string[] args)
        {
            var dico3 = new NAryDictionary<bool, int, string, decimal>();

            dico3[false] = new NAryDictionary<int, string, decimal>();
            dico3[false][123] = new NAryDictionary<string, decimal>();
            dico3[false][123]["foo"] = 123456789.012m;

            Console.WriteLine(dico3[false][123]["foo"].ToString("0,0.000"));

            Console.ReadKey();
       }
    }

Now, although it's Visual Studio intellisense-friendly (as you'll notice with automatic code completion proposals after typing the " = " signs in the above assignments), it is arguably a bit cumbersome, still, because of those

... new NAryDictionary<bool, int, string, decimal>()
... new NAryDictionary<int, string, decimal>()

and

... new NAryDictionary<string, decimal>()

Which can be alleviated with,

    public static class NAryDictionaryExtensions
    {
        public static NAryDictionary<TKey2, TValue> New<TKey1, TKey2, TValue>(this NAryDictionary<TKey1, TKey2, TValue> dictionary)
        {
            return new NAryDictionary<TKey2, TValue>();
        }

        public static NAryDictionary<TKey2, TKey3, TValue> New<TKey1, TKey2, TKey3, TValue>(this NAryDictionary<TKey1, TKey2, TKey3, TValue> dictionary)
        {
            return new NAryDictionary<TKey2, TKey3, TValue>();
        }
    }

allowing now to write, more simply,

    class Program
    {
        static void Main(string[] args)
        {
            var dico3 = new NAryDictionary<bool, int, string, decimal>();

            dico3[true] = dico3.New();
            dico3[true][456] = dico3[true].New();
            dico3[true][456]["bar"] = 456789012.345m;

            Console.WriteLine(dico3[true][456]["bar"].ToString("0,0.000"));

            Console.ReadKey();
       }
    }

'Hope this helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.