The .Net Dictionary Dilemma

Posted on Aug 11, 2012


Nine times out of ten, if you were to ask someone "What is the best way to store a key and a value in .Net?" that someone would answer something along the lines "Well, you'd probably want to use a Dictionary.".

Now, if you were to ask that same question but phrase it slightly differently to "What is the best way to store a key and a value in .Net on the Compact Framework?" you probably wouldn't get "Dictionary" in your answer.

You see, it is a common (misguided, I should add) notion that Dictionaries are, usually for garbage reasons, completely evil, and you would have to be monumentally stupid to even contemplate using them. Even worse, if you even vaguely approached the idea of using enums as your key value, most (misguided) programmers would have you tarred and feathered...and then marked with the red cross, as you have obviously contracted the plague of incompetence.

So, I went ahead and built an input manager revolving around Dictionaries, with enums as my keys.

You see, I don't believe that Dictionaries are the absolute devil-spawn of .Net Collections. Sure, there is a grain of truth in the concept of "Dictionaries create garbage, and Dictionaries create lots of garbage when used with enums", but there is a fairly simple way around that. But first, a quick explanation of why using enums with Dictionaries create garbage.

Dictionary<enum, value> = Garbage

The above statement, while true, doesn't tell the whole story. Using an enum as your key value (that is, the first argument, as Dictionaries look like Dictionary) will more-than-likely cause Boxing to occur. If you are unfamiliar with what boxing is, the general rule of thumb is this:

Boxing is bad. Avoid it.

The reasoning behind this rather broad statement is that Boxing/Unboxing creates garbage. This is exactly why using enums as your key value creates garbage. To avoid this, you have two options:

1. Cast your enum values to ints before inserting them into your Dictionary. Then use integer keys for accessing the values later.

This isn't a great option. It makes your code (in my opinion) ugly, and it will make your life a nightmare later when you have to do this for any Dictionary you use. It will also ruin the type safety that generics have in .Net.

2. Don't use enums as keys, or don't use a Dictionary.

Bit of a letdown, wasn't it? This is the definition of "stuck between a rock and a hard place".

However, there is a magical third option that most everyone glosses over with the "Dictionaries and enums are evil" argument.

The Third Option

Your third option is to utilize an IEqualityComparer.

"Well, how do I do that!?" I hear you screaming at your screen. Don't worry; I'll explain how these work.

First, let's create a normal Dictionary:

// Create our dictionary for storing Buttons (which are the buttons of an Xbox GamePad)
Dictionary<Buttons, bool> GamePadButtons = new Dictionary<Buttons, bool>();

Great! We've got a Dictionary set up to use Buttons as our key, with a bool being used as our value.

Now, if we iterate over this Dictionary, or check one of the values like this:

foreach (Buttons button in GamePadButtons.Keys)
{
    if (GamePadButtons[button] == true)
    {
         //do something
    }
}

Ah...

There's the garbage I was on about earlier. Interestingly enough, this code does not generate garbage on a PC, using .Net 4.0 or later. However, if you are using an earlier version, or targeting the .Net Compact Framework (which is what the Xbox uses), you will still get garbage.

And lots of it.

So, then, let's fix it. This will require that you make a new class (one of the aforementioned IEqualityComparers). Personally, I would make a new .cs file and store all of my IEqualityComparers in that single .cs file. If we follow the sage advice of Nick Gravelyn, we should end up with something like this:

public class GamePadButtonsEqualityComparer : IEqualityComparer
{
    private static GamePadButtonsEqualityComparer defaultInstance;
    public static GamePadButtonsEqualityComparer Default
    {
        get
        {
            if (defaultInstance == null)
                defaultInstance = new GamePadButtonsEqualityComparer();
            return defaultInstance;
        }
    }
    public bool Equals(Buttons x, Buttons y)
    {
        return x == y;
    }
    public int GetHashCode(Buttons obj)
    {
        return obj.GetHashCode();
    }
}

We've now created an IEqualityComparer for the Buttons class! Now, all it'll take to erase the garbage created by our Dictionary is to change this one line of code:

// Our old line
Dictionary<Buttons, bool> GamePadButtons = new Dictionary<Buttons, bool>();

// Our new line
Dictionary<Buttons, bool> GamePadButtons = new Dictionary<Buttons, bool>(GamePadButtonsEqualityComparer.Default);

We've fixed it! That single line of code has solved all of our Dictionary-induced headach--

Wait. That hasn't done anything at all! Where have we gone wrong?

It turns out that we need to change one more line of code to completely erradicate the garbage. This time, the line of code is located in our IEqualityComparer:

// Our old line
return obj.GetHashCode();

// Our new line
return (int)obj;

There we go. It turns out that GetHashCode does generate some garbage here, so instead of using that method (which returns an int), we just manually cast the object to an int ourselves.

Problem solved! All of the garbage generated by our Dictionary+enum pair has been resolved.

Now for the bad news. You'll have to make an IEqualityComparer for every enum you want to use. (For example, if you wanted to use the Keys enum for Keyboard keys, you'll need to make an IEqualityComparer for that.) However, the code is exactly the same as the one we've made here, only you'll use Keys instead of Buttons. So, yes, this would qualify as boilerplate code, but it's much easier (and less ugly) than having to cast to ints everywhere!

Conclusion

Dictionaries are great for storing keys and values. By having eliminated one of the drawbacks (if you can call it that) of using them, we've now made it easier to correctly disprove the argument "Dictionaries with enums are completely and utterly evil, and only the incompetent use them".

Granted, it's not the most user-friendly way of creating Dictionaries -- having to create an IEqualityComparer for every unique enum you want to use is not fun -- but it's much better (in my opinion) than casting to ints, or worse, not using Dictionaries at all. (and maybe using a double-dimension array, or two separate arrays/lists, etc. instead)



Loading Conversation