Combined “Check Add or Fetch” from Dictionary

2019-06-28 04:27发布

问题:

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?

回答1:

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;


回答2:

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)
{
    T value;
    return dict.TryGetValue(key, out 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.



回答3:

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();
    }
}