C# - working with Dictionary

Few notes about Dictionary in .NET

TryGetValue vs ContainsKey + item index

The code below was taken from the .NET Source Browser

Hint: both approaches use the same internal method to find value: FindValue

Let's see the code used to get the value by checking first whether the key exists.

public bool ContainsKey(TKey key) =>
    !Unsafe.IsNullRef(ref FindValue(key));

That was the first usage of the FindValue method. Knowing that the key exists we can now get the value by item index.

public TValue this[TKey key]
{
    get
    {
        ref TValue value = ref FindValue(key);
        if (!Unsafe.IsNullRef(ref value))
        {
            return value;
        }

        ThrowHelper.ThrowKeyNotFoundException(key);
        return default;
    }
    set
    {
        bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting);
        Debug.Assert(modified);
    }
}

Here we have a second usage of the FindValue method inside of the getter.

Now, let's see what the code looks like for TryGetValue method.

public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
    ref TValue valRef = ref FindValue(key);
    if (!Unsafe.IsNullRef(ref valRef))
    {
        value = valRef;
        return true;
    }

    value = default;
    return false;
}

Under the single call of TryGetValue method, FindValue had to be executed only once.

Now, double execution of the FindValue in the case of the first approach with ContainsKey and item index should not have any significant impact on performance, but when we can do less to achieve the same goal then we should go with TryGetValue approach.

Not to mention that using the TryGetValue method increases the readability of the code.

Converting collection into the Dictionary

Let's say we have a list of strings and we need to convert it into the Dictionary (key-value pair collection). We can do this as follows:

var simpleList = new List<string>() { "one", "two", "three", "four" };
var simpleListToDictionary = simpleList.ToDictionary(x => simpleList.IndexOf(x), x => x);

We are using the index of the list element as a key in the new Dictionary. As a result, we are getting a Dictionary with int and string key-value pair:

We can also convert a list of objects into the Dictionary, but only when we can ensure that one of the properties of the object represents a unique value for each object - for example, an identifier retrieved from the database.

Sample entity:

internal class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }
}

Collection initialization:

var users = new List<User>()
{
    new User { Id = 345, FirstName = "Homer", LastName = "Simpson", UserName = "hoSimpson" },
    new User { Id = 3212, FirstName = "Marge", LastName = "Simpson", UserName = "marge" },
    new User { Id = 987, FirstName = "Bart", LastName = "Simpson", UserName = "eatmyshorts" },
};

Conversion to Dictionary:

var usersPresentable = users.ToDictionary(u => u.Id, u => u.UserName);

Result:

Important: do not use entire objects as keys in Dictionary. That's because objects are reference types, so under the key we will store the reference, not the object itself. When an object reference changes the dictionary becomes broken and we won't be able to retrieve data for this particular object anymore. In the case of a Dictionary, keys must be immutable - this applies to all available types of dictionaries in .NET.

Types of Dictionaries in .NET

TypeShort description
Dictionary<TKey, TValue>The simplest type of the Dictionary. It's using the hash table for data retrieval by key, so it's quite fast. It's worth mentioning that the speed of reading the data from this dictionary depends on the quality of the hashing algorithm specified for the TKey value.
OrderedDictionaryThis type of Dictionary guarantees the order of values retrieved from the collection.
SortedDictionary<TKey, TValue>In this case, we have to provide an implementation of comparer to compare the keys - the implementation of IComparer<T> can be provided through the constructor. This dictionary uses binary search tree retrieval, so it may have an impact on performance. The best use case for this type of dictionary is an extensive amount of items that need to be in sorted form.
ReadOnlyDictionary<TKey, TValue>Does not allow items to be inserted to, or removed from the dictionary.
ImmutableDictionary<TKey, TValue>Each time when we take an attempt of making a change in the dictionary like adding or removing an item a copy of the original collection is made and changes are applied to that copy and finally, the new dictionary is returned. We cannot initialize this type of dictionary with a constructor - simply it's not available from the API. To create an ImmutableDictionary we have to use one of the static methods, like CreateRange or AddRange. This type of dictionary is thread-safe.
ConcurrentDictionaryThread safe implementation of the dictionary.