6

I'm tired of this dictionary idiom:

        Dictionary<Guid,Contact> Contacts;
        //...
        if (!Contacts.ContainsKey(id))
        {
            contact = new Contact();
            Contacts[id] = contact;
        }
        else
        {
            contact = Contacts[id];
        }

It would be nice if there was a syntax that permitted the new value to be created implicitly from a default constructor if it does not exist (the dictionary knows the type of the value, after all). Anyone seen a helper (such as an extension method) that does this?

Ani
  • 111,048
  • 26
  • 262
  • 307
Brent Arias
  • 29,277
  • 40
  • 133
  • 234
  • I voted this question as a duplicate of the [.NET Dictionary: get existing value or create and add new value](https://stackoverflow.com/questions/16192906/net-dictionary-get-existing-value-or-create-and-add-new-value), although this question is older, because the other question is more heavily upvoted and contains more answers. – Theodor Zoulias Feb 12 '23 at 04:11
  • 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:13

3 Answers3

15

Implementation:

public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
                                            TKey key, Func<TValue> valueCreator)
{
    TValue value;
    if (!dictionary.TryGetValue(key, out value))
    {
        value = valueCreator();
        dictionary.Add(key, value);
    }
    return value;
}

public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
                                            TKey key) where TValue : new()
{
   return dictionary.GetOrAdd(key, () => new TValue());
}

Usage:

var contacts = new Dictionary<Guid, Contact>();
Guid id = ...

contacts.GetOrAdd(id).Name = "Abc"; // ok since Contact has public parameterless ctor
contacts.GetOrAdd(id, () => new Contact { Name = "John Doe" }).Age = 40;
Ani
  • 111,048
  • 26
  • 262
  • 307
3

Same as Ani's answer, but in a more unintelligible one-liner :)

/// <param name="valueCreator">The expensive value creator function.</param>
public static T GetOrAdd<S, T>(this IDictionary<S, T> dict, S key, Func<T> valueCreator)
{
    return dict.TryGetValue(key, out var value) ? value : dict[key] = valueCreator();
}

Provide a delegate as value creator than value itself to prevent unnecessary object creation.

Dictionary, unfortunately, doesn't have this feature out of the box to do all this in a single lookup.

nawfal
  • 70,104
  • 56
  • 326
  • 368
  • 1
    Just a note about your mention of using a value creator. That makes more sense when either creating the value is expensive or the common case is NOT to create new instances. If you are just using value types or by profiling you find that the common case is creating new instances, it could be more overhead than necessary, so supporting both direct value assignment and generic delegates is best. There are also the cases when you want to assign an already created value. – Jeremy Sep 11 '15 at 18:31
  • This answer is wrong - precedence is to !: before =, so the valueCreator is always executed. Put parentheses around the assignment. – Warwick Allison Jul 09 '19 at 18:25
  • @WarwickAllison, im not sure if something changed with newer versions of C#, but that code works fine as intended where I tested. valueCreator is not always executed. Proof: https://dotnetfiddle.net/NiWdt7. But yes, for code readability include parentheses. I was going after a fancy one-liner :) – nawfal Jul 10 '19 at 08:00
  • *"Dictionary, unfortunately, doesn't have this feature out of the box to do all this in a single lookup."* -- Starting from .NET 6, [it does](https://stackoverflow.com/questions/6408916/find-or-insert-with-only-one-lookup-in-c-sharp-dictionary/74957779#74957779)! – Theodor Zoulias Feb 12 '23 at 04:17
0

You can always roll your own dictionary.

Solution 1: Inherit and use "new" overriding methods checking first for containing key. If yes return that value by key or create by a

Func<K, T>

delegate. However, this solution will break when using this dictionary via the interface

IDictionary<K,T>

So for that you need to be more thorough via solution 2.

Solution 2: Dictionary Wrapper which uses an internal dictionary - the rest is the same as solution 1.

Solution 3: ConcurrentDictionary offers GetOrAdd which is also thread safe.

Solution 4: ConcurrentDictionary Wrapper similar to solution 2.

Here's a dictionary wrapper:

public class WrappedDictionary<K, T> : IDictionary<K, T>
{
    public IDictionary<K, T> WrappedInstance { get; set; }

    public virtual T this[K key]
    {
        get
        {
            // CUSTOM RESOLUTION CODE GOES HERE
            return this.WrappedInstance[key];
        }
        set
        {
            this.WrappedInstance[key] = value;
        }
    }

    public int Count
    {
        get
        {
            return this.WrappedInstance.Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return this.WrappedInstance.IsReadOnly;
        }
    }

    public ICollection<K> Keys
    {
        get
        {
            return this.WrappedInstance.Keys;
        }
    }

    public ICollection<T> Values
    {
        get
        {
            return this.WrappedInstance.Values;
        }
    }

    public void Add(KeyValuePair<K, T> item)
    {
        this.WrappedInstance.Add(item);
    }

    public void Add(K key, T value)
    {
        this.WrappedInstance.Add(key, value);
    }

    public void Clear()
    {
        this.WrappedInstance.Clear();
    }

    public bool Contains(KeyValuePair<K, T> item)
    {
        return this.WrappedInstance.Contains(item);
    }

    public bool ContainsKey(K key)
    {
        return this.WrappedInstance.ContainsKey(key);
    }

    public void CopyTo(KeyValuePair<K, T>[] array, int arrayIndex)
    {
        this.WrappedInstance.CopyTo(array, arrayIndex);
    }

    public IEnumerator<KeyValuePair<K, T>> GetEnumerator()
    {
        return this.WrappedInstance.GetEnumerator();
    }

    public bool Remove(KeyValuePair<K, T> item)
    {
        return this.WrappedInstance.Remove(item);
    }

    public bool Remove(K key)
    {
        return this.WrappedInstance.Remove(key);
    }

    public bool TryGetValue(K key, out T value)
    {
        // CUSTOM RESOLUTION CODE GOES HERE
        return this.WrappedInstance.TryGetValue(key, out value);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.WrappedInstance.GetEnumerator();
    }
}
Demetris Leptos
  • 1,560
  • 11
  • 12