.NET Dictionary: get or create new

C#.NetDictionaryInsert Update

C# Problem Overview


I often find myself creating a Dictionary with a non-trivial value class (e.g. List), and then always writing the same code pattern when filling in data.

For example:

var dict = new Dictionary<string, List<string>>();
string key = "foo";
string aValueForKey = "bar";

That is, I want to insert "bar" into the list that corresponds to key "foo", where key "foo" might not be mapped to anything.

This is where I use the ever-repeating pattern:

List<string> keyValues;
if (!dict.TryGetValue(key, out keyValues))
  dict.Add(key, keyValues = new List<string>());
keyValues.Add(aValueForKey);

Is there a more elegant way of doing this?

Related questions that don't have answers to this question:

C# Solutions


Solution 1 - C#

We have a slightly different take on this, but the effect is similar:

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) 
    where TValue : new()
{
    if (!dict.TryGetValue(key, out TValue val))
    {
        val = new TValue();
        dict.Add(key, val);
    }

    return val;
}

Called:

var dictionary = new Dictionary<string, List<int>>();

List<int> numbers = dictionary.GetOrCreate("key");

It makes use of the generic constraint for public parameterless constructors: where TValue : new().

To help with discovery, unless the extension method is quite specific to a narrow problem, we tend to place extension methods in the namespace of the type they are extending, in this case:

namespace System.Collections.Generic

Most of the time, the person using the type has the using statement defined at the top, so IntelliSense would also find the extension methods for it defined in your code.

Solution 2 - C#

As with so many programming problems, when you find yourself doing something a lot, refactor it into a method:

public static void MyAdd<TKey, TCollection, TValue>(
    this Dictionary<TKey, TCollection> dictionary, TKey key, TValue value)
    where TCollection : ICollection<TValue>, new()
{
    TCollection collection;
    if (!dictionary.TryGetValue(key, out collection))
    {
        collection = new TCollection();
        dictionary.Add(key, collection);
    }
    collection.Add(value);
}

Solution 3 - C#

For further readers, here are some extensions in every flavour I thought fit. You could also do something with an out parameter if you need to check if you have added a value but i think you can use containskey or something already for that.

You can use GetOrAddNew to retrieve an item, or create and add it to the dict. You can use the various overloads of GetOrAdd to add a new value. This could be the default so e.g. NULL or 0 but you can also provide a lambda to construct an object for you, with any kind of constructor arguments you'd like.

var x = new Dictionary<string, int>();
var val = x.GetOrAdd("MyKey", (dict, key) => dict.Count + 2);
var val2 = x.GetOrAdd("MyKey", () => Convert.ToInt32("2"));
var val3 = x.GetOrAdd("MyKey", 1);
    /// <summary>
    /// Extensions for dealing with <see cref="Dictionary{TKey,TValue}"/>
    /// </summary>
    public static class DictionaryExtensions
    {
        public static TValue GetOrAddNew<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) 
            where TValue : new() 
            => dict.GetOrAdd(key, (values, innerKey) => EqualityComparer<TValue>.Default.Equals(default(TValue), defaultValue) ? new TValue() : defaultValue);

        public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
            => dict.GetOrAdd(key, (values, innerKey) => defaultValue);

        public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> valueProvider)
            => dict.GetOrAdd(key, (values, innerKey) => valueProvider());

        public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueProvider)
            => dict.GetOrAdd(key, (values, innerKey) => valueProvider(key));

        public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<IDictionary<TKey, TValue>, TKey, TValue> valueProvider)
        {
            if (dict == null) throw new ArgumentNullException(nameof(dict));
            if (key == null) throw new ArgumentNullException(nameof(key));
            if (valueProvider == null) throw new ArgumentNullException(nameof(valueProvider));

            if (dict.TryGetValue(key, out var foundValue))
                return foundValue;

            dict[key] = valueProvider(dict, key);
            return dict[key];
        }
    }

Solution 4 - C#

If you use .Net Core you can use Dictionary<>.TryAdd().

var dict = new Dictionary<string, string>();
dict.TryAdd("foo", "bar"); // returns bool whether it added or not feel free to ignore.
var myValue = dict["foo"];

Solution 5 - C#

Here is a solution in case the constructor requires a parameter.

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> createNew)
    {
        if (!dict.TryGetValue(key, out var val))
        {
            val = createNew();
            dict.Add(key, val);
        }

        return val;
    }

Simple to use:

MyDict.GetOrCreate(si.Id, createNew: () => new ObjectKnowingItsId(si.Id))

Solution 6 - C#

Ok, different approach:

public static bool TryAddValue<TKey,TValue>(this System.Collections.Generic.IDictionary<TKey,List<TValue>> dictionary, TKey key, TValue value)
    {
        // Null check (useful or not, depending on your null checking approach)
        if (value == null)
            return false;

        List<TValue> tempValue = default(List<TValue>);

        try
        {
            if (!dictionary.TryGetValue(key, out tempValue))
            {
                dictionary.Add(key, tempValue = new List<TValue>());
            }
            else
            {
                // Double null check (useful or not, depending on your null checking approach)
                if (tempValue == null)
                {
                    dictionary[key] = (tempValue = new List<TValue>());
                }
            }

            tempValue.Add(value);
            return true;
        }
        catch
        {
            return false;
        }
    }

In this way you have to "try to add" your value to a generic List of (obviously generalizable to a generic collection), null checking and trying to get existing key/values in your Dictionary. Usage and example:

var x = new Dictionary<string,List<string>>();
x.TryAddValue("test", null); // return false due to null value. Doesn't add the key
x.TryAddValue("test", "ok"); // it works adding the key/value
x.TryAddValue("test", "ok again"); // it works adding the value to the existing list

Hope it helps.

Solution 7 - C#

And what about this?

var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>();
keyValues.Add(aValueForKey);

Solution 8 - C#

I am missing the GetOrAdd for Dictionary, that does exist for ConcurrentDictionary.

ConcurrentDictionary<int,Guid> Conversion = new ConcurrentDictionary<int, Guid>();
List<int> before = new List<int> { 1, 2, 1, 3 };
List<Guid> after = before.Select(x => Conversion.GetOrAdd(x, Guid.NewGuid())).ToList();

This code will generate Guids for each number. Ending up converting both 1's in before to the same Guid.

In your case:

ConcurrentDictionary<int, List<String>> dict;
keyValues = dict.GetOrAdd(key, new List<String>());
keyValues.Add(aValueForKey);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRok StrnišaView Question on Stackoverflow
Solution 1 - C#Adam HouldsworthView Answer on Stackoverflow
Solution 2 - C#ServyView Answer on Stackoverflow
Solution 3 - C#sommmenView Answer on Stackoverflow
Solution 4 - C#Alexander OhView Answer on Stackoverflow
Solution 5 - C#HeikoGView Answer on Stackoverflow
Solution 6 - C#Francesco De LisiView Answer on Stackoverflow
Solution 7 - C#GeorgeView Answer on Stackoverflow
Solution 8 - C#Maik van den HengelView Answer on Stackoverflow