62

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:

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Rok Strniša
  • 6,781
  • 6
  • 41
  • 53
  • What if the key exists but the List is null? – Francesco De Lisi Apr 24 '13 at 13:59
  • 3
    @Barabba Generally I'd think adding a null value would be considered inappropriate and you'd *expect* the code to just bomb out and fix the bug of adding a null key, not by trying to handle it here. – Servy Apr 24 '13 at 14:01
  • 1
    @Servy ok, but what if we get a result List from a third-party source? The only thing to deal with it is to handle null values, am I wrong? It happens to me every day :) – Francesco De Lisi Apr 24 '13 at 14:09
  • @Barabba Then you should be null checking the list returned elsewhere before you add it to the dictionary to ensure that you don't add a null value. – Servy Apr 24 '13 at 14:10
  • @Servy sure, null checking is the right way, but what if our Dictionary comes from third party? Ok, this scenario is not the once described here, but I prefer a generic bullet-proof method. – Francesco De Lisi Apr 24 '13 at 14:13
  • @Barabba Then I'd probably just not use a method such as this in the rare edge case of using a particular third party's code with a particular novice-level bug. It's incredibly unlikely to happen, incredibly easy to find when it does (the null pointer exceptions stand out), and incredibly easy to fix (just add in the simple null check). If you really want the method itself to do the null check, by all means add it in when you use the method; you're free to do so. I've explained why I think it would be harmful in my code, but if you know that it doesn't apply to your codebase that's fine. – Servy Apr 24 '13 at 14:16
  • @Servy ok, I agree. I use often TryDoSomething methods with a null check returning false and I don't think is a bad approach. When possible I always do my check before every kind of method/operation/function, but I never feel sure due to noob third-party coding :) Thanks for sharing your point of view. – Francesco De Lisi Apr 24 '13 at 14:33
  • 1
    Related: http://stackoverflow.com/questions/3705950/combined-check-add-or-fetch-from-dictionary?lq=1 – nawfal May 31 '13 at 05:49
  • Related question, with emphasis on performance: [Find-or-insert with only one lookup in C# Dictionary](https://stackoverflow.com/questions/6408916/find-or-insert-with-only-one-lookup-in-c-sharp-dictionary). – Theodor Zoulias Feb 12 '23 at 04:14

11 Answers11

75

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.

malat
  • 12,152
  • 13
  • 89
  • 158
Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
  • 1
    @Darthenius No, the first approach is specific to lists (we have others for `HashSet` and other `Dictionary<>` items). I can't remember the exact reasoning but I think because the `List<>` also contains a generic type, doing it this way plays nicer with type inference. – Adam Houldsworth Apr 24 '13 at 13:37
  • 2
    @Darthenius Well, I just went back to our code and commented out the `List<>` version we had and it no longer has a use, it was functionally the same as the standard one above. So I've removed it. It must have been left behind by accident as I was coding it the first time round. – Adam Houldsworth Apr 24 '13 at 13:44
  • 1
    @Darthenius Well, they weren't exactly functionally equivalent, so I haven't removed them. However, in terms of this question it wasn't relevant. They differed in that they checked for an item being null, not an item not being in the dictionary, so a key with a null item would also create one. – Adam Houldsworth Apr 24 '13 at 13:52
  • thanks Adam but i have never seen this syntax before: "where TValue : new()" - what does it mean? – BenKoshy Feb 16 '17 at 02:51
  • 1
    @BKSpurgeon It is a [generic constraint](https://msdn.microsoft.com/en-us/library/d5x73970.aspx) requiring that the type `TValue` have a public parameterless constructor. – Adam Houldsworth Feb 16 '17 at 07:17
  • How do you make this work with types like `Dictionary`? It doesn't like the `new()` constraint. – void.pointer Mar 18 '21 at 21:21
  • @void.pointer In this instance the question presumes an inner collection. The generic constraint will support _any_ inner type that has a public parameterless constructor. In your instance with `string` you cannot get it to work without re-thinking the method. In any case you need to control construction, or more basically, getting an instance. You could remove the constraint and pass a `Func` or even just a `TValue useWhenNull` or something and replace `val = new TValue()` with your change. – Adam Houldsworth Mar 18 '21 at 22:47
  • I'd like to have both methods ideally but you can't overload by constraint and I can't utilize pattern matching to check constraints. Using C# 9. – void.pointer Mar 19 '21 at 15:15
  • @void.pointer Wouldn't the additional method have a different signature? That should overload ok. Edit: just tried it and the compiler is even smart enough to not show the constrained overload if your type can't satisfy it. – Adam Houldsworth Mar 19 '21 at 20:22
  • I don't see how the signature would be different. Please show your example. – void.pointer Mar 20 '21 at 22:35
  • @void.pointer `public static TValue GetOrCreate(this IDictionary dict, TKey key, Func valueFunc)` or `public static TValue GetOrCreate(this IDictionary dict, TKey key, TValue defaultValue)` crude but at the end of the day you want this method to give you an instance back if one is missing, it needs to make that instance. Alternatively, reflection or expression trees. – Adam Houldsworth Mar 22 '21 at 09:55
  • Ah that's the difference. I didn't want the `defaultValue` param. I simply return `default` for the type. – void.pointer Mar 23 '21 at 13:48
  • 1
    @void.pointer Oh I see, in that case it likely won't "create" so perhaps a different method name such as `GetOrDefault`? – Adam Houldsworth Mar 23 '21 at 14:28
6

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);
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • @Darthenius It is just a different take. It narrows the scope of the method down to adding items to any collection implementing `ICollection`. The main point being to house the code in a separate location for re-use. I don't think he was attempting to improve upon the already provided code. – Adam Houldsworth Apr 24 '13 at 13:59
  • 2
    @Darthenius You can return the collection as well if you want, assuming that's a particularly common thing to do. In my experiences when using the pattern of `Dictionary` I'm not frequently adding multiple values for the same key all at once. If it's something that happens often you could add another overload in which you accept an `IEnumerable` and just add them all within the method. – Servy Apr 24 '13 at 13:59
6

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"];
Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
5

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];
        }
    }
sommmen
  • 6,570
  • 2
  • 30
  • 51
  • 1
    If I correctly understood your intent, there is a bug in `GetOrAddNew`: `defaultValue` should probably be compared to `default(TValue)`. Currently, `my_dict.GetOrAddNew(my_key, null);` adds null value to dictionary instead of calling `new TValue`. – 4LegsDrivenCat May 04 '20 at 15:37
  • [MSDN says](https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=netframework-4.8#System_Object_Equals_System_Object_): `Equals(Object) compares specified object to the current object`. I'm very curious what is `current object` in `EqualityComparer.Default.Equals(default(TValue))`? Why it compiles? – 4LegsDrivenCat May 04 '20 at 15:44
  • @4LegsDrivenCat you're right, i forgot to add defaultValue to the eq. compare. I made a dotnetfidlde to check this; https://dotnetfiddle.net/qEkggh I shall update the answer. As to why it compiles, i'd have to check. – sommmen May 05 '20 at 07:27
  • 1
    @4LegsDrivenCat it compiles fine because this is the code for equals that is called: `public virtual bool Equals(object obj) { return RuntimeHelpers.Equals(this, obj); }` In this case `this` is actually the equality comparer itself (object.equals is called -.-) so its pretty bogus. – sommmen May 05 '20 at 07:39
4

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))
HeikoG
  • 861
  • 10
  • 11
2

And what about this?

var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>();
keyValues.Add(aValueForKey);
George
  • 29
  • 3
  • 3
    a. that's pretty obtuse b. that get's old really quickly if you repeat that all over the place – sehe Jul 07 '18 at 22:26
1

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);
Matt Ke
  • 3,599
  • 12
  • 30
  • 49
1
using System.Runtime.InteropServices;

public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TValue> valueProvider)
  where TKey: notnull
{
  ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
  if (!exists) value = valueProvider.Invoke();

  return value!;
}

public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
  where TKey: notnull
  => GetOrCreate(dictionary, key, () => value);

public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
  where TKey: notnull
  where TValue : new()
  => GetOrCreate(dictionary, key, new TValue());

To avoid duplicate hash lookups.

You can find more info here.

Kaneko Qt
  • 115
  • 2
  • 9
  • @TheodorZoulias this answer is just enhanced version of https://stackoverflow.com/a/61062564/18449435, if you have another solution that seems better to you, please provide it. – Kaneko Qt Jan 02 '23 at 18:33
  • The first overload leaves the dictionary in a corrupted state in case the `valueProvider` fails. The next two are [capturing](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions#capture-of-outer-variables-and-variable-scope-in-lambda-expressions) the `value` in the `() => value` lambda, which results invariably in memory allocation whenever the `GetOrCreate` is called. – Theodor Zoulias Jan 02 '23 at 18:34
  • The solution in [the answer](https://stackoverflow.com/a/61062564/18449435) that you linked cannot cause state corruption, and is not causing memory allocation in case the key already exists. – Theodor Zoulias Jan 02 '23 at 18:37
  • @TheodorZoulias Do you think JIT compilator is prohibited to optimize by eliminating this allocation in any cases? – Кое Кто Aug 30 '23 at 12:41
  • Also this is not thread safe according `CollectionsMarshal.GetValueRefOrAddDefault` docs: `Items should not be added to or removed from the Dictionary while the ref TValue is in use.` – Кое Кто Aug 30 '23 at 12:44
  • @TheodorZoulias Could you explain about "corrupted state"? – Кое Кто Aug 31 '23 at 10:03
  • 1
    @КоеКто I am not a JIT expert, so I don't know what the .NET Jitter could do in theory. I can only tell you what it does in practice right now, by experimentation (`GC.GetTotalAllocatedBytes`). Regarding thread-safety, no mutative operation on a `Dictionary` is thread-safe. The `CollectionsMarshal.GetValueRefOrAddDefault` is not special in this aspect. By corrupted state I mean adding a key-value pair in a `Dictionary` with the default value, like `null`, without the intention of the author. The program might crash or produce incorrect results after such an unintended mutation. – Theodor Zoulias Aug 31 '23 at 10:43
1

Slight twist

Had a special need that was a literal match to the question, but maybe not its intent. For this case, getting the value was expensive (reflection) and only wanted to generate the value once, to populate the dictionary for caching. Building off @adam-houldsworth answer, the value argument was modified into a delegate.

    public static TValue GetOrCreate<TKey, TValue>(
        this IDictionary<TKey, TValue> self,
        TKey key,
        Func<TValue> getValue)
    {
        if (self == null)
        {
            throw new ArgumentNullException(nameof(self));
        }
        else if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }
        else if (!self.ContainsKey(key))
        {
            self[key] = getValue() ;
        }

        return self[key];
    }

Usage

    var assemblyName = callingAssemblies.GetOrCreate
    (
        path, 
        () => Assembly.GetCallingAssembly().GetName().Name
    );
Michael
  • 484
  • 6
  • 8
0

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.

Francesco De Lisi
  • 1,493
  • 8
  • 20
-1

ConcurrentDictionary.GetOrAdd does exactly what is being asked.

Initialization

ConcurrentDictionary<string, List<string>> dict = new();

Usage

var list = dict.GetOrAdd(key, _ => new List<string>());

Notice how we are using a factory method to create a new list as-need. This prevents pre-allocating a list on each call to GetOrAdd.

dana
  • 17,267
  • 6
  • 64
  • 88
  • 1
    I would say if the only reason to use `ConcurrentDictionary` is existence of `GetOrAdd` then it should not be used. – Guru Stron Dec 29 '22 at 22:55