Dr. Strange HoH: How I Learned To Love Perl and C#

Its no secret that I came to C# after doing lots of Perl. I treasure my Perl experience. Many claim that Perl is unreadable, but I argue that the language is as good as the programmers using it. I’ve seen pages of equally unreadable PHP, VB, and yes, even C#. I’ve used Perl libraries as references for how things work. Many Perl libraries are examples of great highly readable code. Language doesn’t matter.

Perl’s documentation is a testament to open source languages. I learned Perl with perldoc installed an the perl man page. It taught me the Perl way of data structures and as I got more advanced I ran into Arrays of Arrays(AoA), Arrays of Hashes(AoH), Hashes of Arrays(HoA) and Hash of Hashes(HoH). These are all defined in the perllol perldoc.

Some of the most confusing Perl that I ever did read or write was confusing because instead of building explicitly named types, convention was relied on too much over configuration and everything was just a HoHoHoHoHoHoH. I think it was a joke about Santa Clause.

All that said, there are times when you really want a HoH or a HoHoH or a HoHoHoH. That is Dictionary<string,Dictionary<string,string>> or Dictionary<string,Dictionary<string,Dictionary<string,string>>> or Dictionary<string,Dictionary<string,Dictionary<string,Dictionary<string,string>>>> for you non perl types.

A little over a year ago, Eilon Lipton wrote a post on using anonymous types as Dictionaries. I rather liked it, but That was just a H (Dictionary<string,string>).  I recently had the need for a HoHoA.

I’m not advocating using this code or ever writing code like this. In fact, I discourage it. If you can find a way to do the same thing but with compile time checks, please do that instead. The problem with using anonymous types as dictionary literals is that you won’t know until runtime that you have made a mistake. WRITE YOUR UNIT TESTS!

public static Dictionary<string, string> ToDictionary(this object source)
{
    var dict = new Dictionary<string, string>();
    var props = source.GetType().GetProperties();
    Array.ForEach(props, p => dict.Add(p.Name, (string)p.GetValue(source, null)));
    return dict;
}
public static Dictionary<string, IList<string>> ToDictionaryOfStrings(this object source)
{
    var dict = new Dictionary<string, IList<string>>();
    var props = source.GetType().GetProperties();
    Array.ForEach(props, p => dict.Add(p.Name, (IList<string>)p.GetValue(source, null)));
    return dict;
}
public static Dictionary<string, Dictionary<string,string>> ToDictionaryOfDictionaries(this object source)
{
    var dict = new Dictionary<string, Dictionary<string, string>>();
    var props = source.GetType().GetProperties();
    Array.ForEach(props, p => dict.Add(p.Name, (Dictionary<string,string>)p.GetValue(source,null).ToDictionary()));
    return dict;
}
public static Dictionary<string, Dictionary<string, IList<string>>> ToDictionaryOfDictionariesOfStrings(this object source)
{
    var dict = new Dictionary<string, Dictionary<string, IList<string>>>();
    var props = source.GetType().GetProperties();
    Array.ForEach(props, p => dict.Add(p.Name, (Dictionary<string, IList<string>>)p.GetValue(source, null).ToDictionaryOfStrings()));
    return dict;
}

Yes, you have to call the right method, it isn’t magic. Yes, these extension methods pollute all objects with their clutter. They do work when you need them though!

var things = 
new
{
    Humans = new { Johny = new[] { "Guitar", "Docks" }, Gina = new[] { "Diner", "Pay" } },
    Dogs = new { Sparky = new[] { "beer", "baseball" }, Toto = new[] { "witch", "kansas" } },
    Marsians = new { Marvin = new[] { "death ray", "moon laser" }, Quato = new[] { "speech", "power" } }
}.ToDictionaryOfDictionariesOfStrings();

Assert.IsTrue(things.ContainsKey("Humans"));
Assert.IsTrue(things.ContainsKey("Dogs"));
Assert.IsTrue(things.ContainsKey("Marsians"));
var humans = things["Humans"];
Assert.IsTrue(humans.ContainsKey("Johny"));
Assert.IsTrue(humans.ContainsKey("Gina"));
var gina = humans["Gina"];
Assert.IsTrue(gina.Contains("Diner"));
Assert.IsTrue(gina.Contains("Pay"));
var dogs = things["Dogs"];
Assert.IsTrue(dogs.ContainsKey("Sparky"));
Assert.IsTrue(dogs.ContainsKey("Toto"));
var sparky = dogs["Sparky"];
Assert.IsTrue(sparky.Contains("beer"));
Assert.IsTrue(sparky.Contains("baseball"));

Good times.