Сочетание «Проверить, добавить или получить» из словаря - PullRequest
4 голосов
/ 14 сентября 2010

Я устал от этой словарной идиомы:

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

Было бы неплохо, если бы существовал синтаксис, позволяющий неявно создавать новое значение из конструктора по умолчанию, если его не существует (словарь знает тип значения, в конце концов).Кто-нибудь видел помощника (такого как метод расширения), который делает это?

Ответы [ 3 ]

11 голосов
/ 14 сентября 2010

Реализация:

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

Использование:

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;
3 голосов
/ 05 мая 2013

То же, что и ответ Ани, но в более неразборчивой строчке:)

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

Предоставьте делегата в качестве создателя значения, а не самого значения, чтобы предотвратить ненужное создание объекта.

Словарь, к сожалению, не имеет этой функции из коробки, чтобы сделать все это в одном поиске .

0 голосов
/ 19 декабря 2016

Вы всегда можете свернуть свой собственный словарь.

Решение 1: Унаследовать и использовать «новые» переопределяющие методы, сначала проверяя наличие ключа.Если да, вернуть это значение по ключу или создать делегатом

Func<K, T>

.Тем не менее, это решение сломается при использовании этого словаря через интерфейс

IDictionary<K,T>

Так что для этого вам нужно быть более тщательным с помощью решения 2.

Решение 2: Оболочка словаря, которая использует внутреннийсловарь - остальное такое же, как и в решении 1.

Решение 3: ConcurrentDictionary предлагает GetOrAdd, который также является потокобезопасным.

Решение 4: ConcurrentDictionary Wrapper похож на решение 2.

Вот обертка словаря:

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