.NET Dictionary: get or create new
C#.NetDictionaryInsert UpdateC# 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
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);