Click here to Skip to main content
15,885,216 members
Articles / Programming Languages / C#

Understanding Dictionaries in .NET

Rate me:
Please Sign up or sign in to vote.
4.87/5 (33 votes)
25 Oct 2013CPOL15 min read 58.2K   45   15
This is a basic article explaining the Dictionary class in .NET.

The Very Basic 

If we look at MSDN, the definition of the Dictionary<TKey, TValue> class is this:

"Represents a collection of keys and values."

But in my opinion such definition is far from useful. If I needed to define a dictionary in a single sentence, I would at least try to define it like this:

"A dictionary is an indexed collection that allows values to be found by user-defined keys."

Such definition, at least to me, let it clear the most important trait of a dictionary: It is indexed, which also suggests that it is fast. And it also says that the keys may be user-defined, suggesting they are not required to be part of the "value".

Yet, I think that trying to define such class in a single sentence is not enough, so I will try to point the biggest points:

  • It is somewhat a double collection, being a collection of keys and a collection of values and, at the end, a collection of key/value pairs;
  • It binds keys to values, even if they are unrelated, so it is useful to create a relationship between objects;
  • It is indexed by keys, which allows very fast searches using the keys, be it to know if a key is present, to find the appropriate value or to remove an entry (to remove a key/value pair).

Why is the Dictionary class important?

The best way I can explain the importance of a dictionary to someone that doesn't know the class yet (be a new developer or an experienced developer that was focused on another domain) is: Imagine that you have a collection with many items (I mean, many, many items, like thousands, millions or even more items). I will use as example a collection of products. Imagine that you want a method to return a product by Name. What do you do?

If we have a normal list or array in memory, we may end-up doing something like this:

C#
public Product GetProductByName(string name)
{
  foreach(Product product in list)
    if (product.Name == name)
      return product;

  return null;
}

Or

C#
public Product GetProductByName(string name)
{
  for(int i=0; i<array.Length; i++)
  {
    Product product = array[i];

    if (product.Name == name)
      return product;
  }

  return null;
}

Did you see the problem with this approach?

If not, let me explain: What will happen if you search for a name that's not present in the list or array?

You will iterate the entire collection, only to discover that the item is not there. The bigger the list, the slower the search is. Now imagine that you are doing this during a large import and you don't want to stop if the item is not found, you will only log an error and continue. Items near the end are slow to find, invalid names are the slowest ones. The only products that are returned really fast are those on the beginning of the collection.

You may think it is not a problem, as you will be iterating in-memory collections, yet it will become slower when there are more items (and with millions of items it will be a problem). So, why not use a solution that's already built to be fast?

In such a situation, you will benefit from using a Dictionary, as it is indexed and, in many situations, checking if an item exists is an O(1) operation (that is, independently if there's only 1 item or 10 millions of items in the dictionary, the search for an item by key [like the Name] consumes time as if there was a single item).

Another presentation

I don't know if you are already interested in the Dictionary class or not. But I know that when I used Delphi, I was usually forced to use the TList class. Then I started to use C++ Builder at work and, thanks to templates (as in C++ we have templates, not generics, but the main idea is the same) I created the TypedList class, which had methods almost identical to the TList class, but rightly typed, and everyone loved.

That's, in fact, the same reason many people love the List<T> type in .NET. It is a typed list, that avoids boxing, avoids invalid item types to be added, makes errors more apparent (at compile time instead of at run-time, at the callers place instead of making exceptions to happen in a future use etc). And the dictionary has all of those benefits. And it is usually faster for searches!

The problem is: Many people don't know about the existence of dictionaries and, if they do, they think it is simply a "funny" list that puts keys and values together without seeing its indexing capability.

So, if you want a very fast solution to find your items, consider using dictionaries. If you have a common problem (like finding items by name), consider using a Dictionary<string, YourItemType> to store such collection.

Using a Dictionary - The Basic Methods

Probably the most important members of the Dictionary class are:

  • Add: Adds a key/value pair. The key must be unique and can't be null but there aren't any constraints for the value;
  • The indexer: You can use the indexer (the dictionary[someKey] construct) to get or to set a value of the dictionary. While setting a value, if another value with the same key already exists it is replaced (instead of throwing an exception like happens with the Add method) but when used for reads, if the key does not exist an exception is thrown so, in most cases, it is preferable to use the TryGetValue method to get a value for a key;
  • TryGetValue: The TryGetValue is the best way to check if an item exists and also get its value. It's only problem is the use of an out parameter which makes its use a little messy. So, if you want to get the value for a key, when you are not sure if there's a value for such key in the dictionary, you should do something like this:

    C#
    Product product;
    if (productsByNameDictionary.TryGetValue(productName, out product))
    {
      // Here the product was got.
    }
    else
    {
      // There's no product with such name.
      // In fact, the product value will have default(Product), which is null for reference types.
    }

    To me, the messy part of the TryGetValue is the fact that I usually don't add null values, so a return of null will be enough to tell me there's no such key in the dictionary instead of having to declare a local variable just before doing the call. So, for my situation I created an extension method like this:

    C#
    public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
    {
      TValue result;
      dictionary.TryGetValue(key, out result);
      return result;
    }

    And, as the TryGetValue will fill the result with default(TValue) if the item is not found (that is, null for reference types) and I never add null values, I can use it like this:

    C#
    Product product = productsDictionaryByName.GetValueOrDefault(productName);
    
    if (product != null)
    {
      // here a product was found.
    }
    else
    {
      // if it is appropriate to do something if an item was not found, you can do it here.
    }
    
  • ContainsKey: This method is useful if you want to check if a key was already added to the dictionary or not when you don't want to get the value. For example, you may be checking if such key was already added before calling Add() and you also don't want to replace the value if there's one already, so using the indexer is not appropriate. There are some people that use the ContainsKey for reading purposes, like this:

    C#
    if (productsByNameDictionary.ContainsKey(productName))
      DoSomething(productsByNameDictionary[productName]);

    But it is a bad practice to do this. Use the TryGetValue (or at least the GetValueOrDefault extension method) in place of such ContainsKey and indexer read. You will replace a double search by a single search when the item is there.

Using a Dictionary - The Planning

Maybe the biggest constraint in using the Dictionary class is knowing the search criteria at creation time. I just used the example of Products. It is easy to load a list of Products in memory and create a list with it. Later, if you want to search products by Name, Price or Id (considering that they usually have such an Id in the database) is easy. But how do you create a dictionary?

Well, if we try to transform a Product list into a dictionary, we may end-up with many of them. The products, for sure, are a collection. But each search criteria, if we consider as valid for indexing, must have its own Key type. So, to search for products by Name we need a Dictionary<string, Product>, to search for products by Id we need a Dictionary<long, Product> (considering Id to be a long). But, if you have a method that returns a collection and you want to return an indexed collection, which return type will you use? Will it be a dictionary indexed by name, by Id or by what?

I am not going to say it is an easy decision. In fact, using a dictionary is usually a matter of how many inserts and searches are expected. If you have many different inserts, and an amount of searches equivalent to the number of inserts, then a list is usually OK. But in many cases you add some items (be it 10, be it 10 millions) and then you search for them many, many, many times.

Using a collection of products as example (again), a method that returns such collection indexed by Id and by Name will, in fact, need to return another object, with should contain the appropriate dictionaries as members and should only publish methods to find the items by the appropriate fields (like a FindByName, FindById etc).

Using dictionaries to bind unrelated objects

I was using the example of a class Product which you probably expect to have an Id and a Name, so building a dictionary to search products by Id or by Name is only useful to speed-up searches, yet you will be capable of finding such Id or Name if you already have the Product instance.

But what about "adding properties" to existing objects? I am not talking about changing the source code of those objects to add new properties as I am considering those objects to be created elsewhere. The purpose is really to add new information to already created instances.

For example, imagine that you want to add help descriptions to all the controls that are inside a Window. Those controls are the standard one and you can't add new properties. Also you don't want to use the Tag property as it isn't typed or because it may be already used by something else. So, you can create a Dictionary<Control, string> and you can add the help string for each control, like doing:

C#
helpStrings.Add(buttonOK, "This button closes the window and applies all the modifications");

And to get the help string, instead of doing something like:

C#
string helpString = buttonOK.HelpString

As such property doesn't really exist, you can do:

C#
string helpString = helpStrings.GetValueOrDefault(buttonOK);

Simple, isn't it?

  • Note: In this example, the Dictionary must be inside the Window/Form to work properly. If you use a static dictionary you will keep all controls in memory, even after they are disposed. If you want to "add properties" to objects without keeping them alive longer than needed, check the ConditionalWeakTable (which is a kind of dictionary that allows the keys to be collected).

A warning: LINQ and Dictionaries don't combine

I recently wrote an article about Easiness duality. Maybe there I didn't write a good situation to explain the problem, but I think that LINQ has such problem, at least when combined with Dictionaries.

LINQ is a generic purpose solution to deal with collections. It really helps in many situations by allowing us (the developers) to write less code to achieve the same result and, in many situations, with better speed or memory consuption than by writing "basic" solutions. For example, when filtering collections we can simply enumerate the results instead of creating new lists that represent the filtered result, so we avoid unnecessary memory allocations and if we want to stop at the first item that match the criteria we don't spend time populating an entire list.

But LINQ is terrible when combined with dictionaries. The truth is that LINQ usually iterates over the entire collection to do its job. It may create its own dictionaries (or similar structures) when doing joins, but it will not take advantage of an already existing dictionary (it simply can't, as a Dictionary<long, Product> doesn't tell if it is indexed by Id or by some other long property. So, if you only want to search for a single item in a dictionary (like verifying if a key exists) and you use the Keys.Where(someCondition).Single() you will iterate over all keys.

That's not a LINQ bug. That's not a Dictionary bug either. The Dictionary allows you to access it as a collection of key/value pairs or to access the keys or values as independent collections because you may have such a need. You may really want to iterate over all items, be it to debug, to serialize or the like.

Then LINQ is there to add capabilities to collections, so it ends up adding methods to the dictionary itself and to its Keys and Values properties. But if you use LINQ with a dictionary you will be losing the most important trait of a dictionary: Its indexation. So, except that you have a very specific need and that you know what you are doing, don't use LINQ over dictionaries.

Another warning: The TKey must have a consistent Equality

The Dictionary class allows you to define the Comparer to be used to compare keys for equality during its constructor but, by default, well, it uses the EqualityComparer<TKey>.Default, which will use the GetHashCode and Equals methods. This may be a little advanced as in most cases, like any key type that doesn't implement such methods or by using primitive types as key it will simply work.

But it is important to be aware of this, as if you use a Key type that has a mutable hashcode (for example, a HashCode based on the actual property values) or if you want to extend a specific instance of a type that overrides the Equals method you can have strange bugs. So, if you think your dictionary is "bugged", check the type used as Key.

Kinds of Dictionaries

The name Dictionary is related to the class itself and to the kind of solution it gives (that is, searching values that are indexed by keys).

There are many classes that provide IDictionary implementations and even dictionary like implementations that don't share an interface, like SortedList, ConcurrentDictionary, ImmutableDictionary, ConditionalWeakTable etc.

You can check every implementation if you want, but I consider the main points to be:

  • The Dictionary itself doesn't guarantee the items to be in any particular order (well, it originally keep the items in the order they are added, but if you remove an item from the middle, the next added item will take its place... so, the general idea is that it doesn't keep any order) and, even if it support multiple readers concurrently, it is not thread-safe, so it is up to you to do the synchronization between many threads if you need to;
  • The ConcurrentDictionary is the thread-safe equivalent to the Dictionary class and it has many multi-action methods (like AddOrUpdate, TryRemove, GetOrAdd etc). Personally I don't like how its GetOrAdd works, as two values may be created in parallel and only one will be used, so in most cases I prefer a normal dictionary with a lock. If you want to understand this particular case better, see my article: Dictionary + Locking versus ConcurrentDictionary;
  • The SortedList keeps all the entries ordered by their Keys. It is slower than the normal Dictionary class for searches and it can be much slower on inserts, as it may require to reposition all the items to accomodate the new one. Personally, I only tried to use it when I believed it was fast. Later, I stick with the normal Dictionary;
  • The ImmutableDictionary, well, there's an entire concept related to Immutable objects. They are thread-safe by default. There's the guarantee that if you store an immutable dictionary, no one else will change the contents of your dictionary (independently if you are the one that created the dictionary and gave it to some other method or the one that received such dictionary), but its usage is very different (each Add, Remove etc will return a new dictionary instead of changing the actual object). Also there are many speed and memory consuption differences, so I think it is better to check by yourself;
  • Finally, the ConditionalWeakTable is the best one if you want to "add" properties to objects at run-time, as it will only keep values alive as long as the keys (the original objects) are still alive. Considering garbage collections may happen at any moment, this class is also thread-safe. And considering its purpose is to add properties to instances, it doesn't use a equality comparer for the keys. Only instances are considered.

Some "places" that already use dictionaries

If you program ASP.NET applications you have probably used dictionaries even if they don't implement the IDictionary interface. The ViewState, Session, Application and even Cookies are dictionary like classes. I don't know their internal implementation, but that may be enough to understand how useful they are.

But if you don't program ASP.NET applications, don't worry, there are plenty of places that use dictionaries. In fact, I can't imagine how to write caching solutions without a dictionary. Caches are a great example of data that's "found by a key", be it a name or an Id. So, a dictionary will help by doing a fast search.

But if you don't care about these, what about Dependency Properties? In special, attached properties are properties that you can "add" to other objects... how do you think they are implemented internally? I am not saying you will see a real dictionary there, but the concept (that in fact comes from "hash tables") is there.

Finally, I will say that any good IoC (Inversion of Control) container will use a dictionary to decide which "constructor" to use to create instances of a requested type. So, it's time to explore these amazing classes.

License

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


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionBest article Pin
Pankaj-kumar-chaudhary23-Aug-16 20:48
Pankaj-kumar-chaudhary23-Aug-16 20:48 
AnswerRe: Best article Pin
Paulo Zemek24-Aug-16 6:07
mvaPaulo Zemek24-Aug-16 6:07 
GeneralMy vote of 3 Pin
agplaza1-Nov-13 4:28
agplaza1-Nov-13 4:28 
QuestionGood one, but I wouldn't be that sharp on certain assertions Pin
agplaza1-Nov-13 4:25
agplaza1-Nov-13 4:25 
AnswerRe: Good one, but I wouldn't be that sharp on certain assertions Pin
Paulo Zemek1-Nov-13 6:00
mvaPaulo Zemek1-Nov-13 6:00 
QuestionGood, and two suggestions Pin
Jani Giannoudis25-Oct-13 13:45
Jani Giannoudis25-Oct-13 13:45 
AnswerRe: Good, and two suggestions Pin
Paulo Zemek25-Oct-13 14:43
mvaPaulo Zemek25-Oct-13 14:43 
AnswerRe: Good, and two suggestions Pin
Jani Giannoudis26-Oct-13 4:07
Jani Giannoudis26-Oct-13 4:07 
GeneralRe: Good, and two suggestions Pin
Paulo Zemek26-Oct-13 6:49
mvaPaulo Zemek26-Oct-13 6:49 
Well... I will try to explain a little more on the overall capability of the Dictionary class... but I must take care as I don't want to make people believe it should be used like a list.
QuestionYes... 10!! Pin
Paulo Lima25-Oct-13 9:59
Paulo Lima25-Oct-13 9:59 
AnswerRe: Yes... 10!! Pin
Paulo Zemek25-Oct-13 11:30
mvaPaulo Zemek25-Oct-13 11:30 
Generalmy vote 5 Pin
Southmountain25-Oct-13 6:32
Southmountain25-Oct-13 6:32 
GeneralRe: my vote 5 Pin
Paulo Zemek25-Oct-13 7:07
mvaPaulo Zemek25-Oct-13 7:07 
GeneralMy vote of 5 Pin
fredatcodeproject25-Oct-13 2:54
professionalfredatcodeproject25-Oct-13 2:54 
GeneralRe: My vote of 5 Pin
Paulo Zemek25-Oct-13 4:39
mvaPaulo Zemek25-Oct-13 4:39 

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

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