Подтвердить что ты не робот

.NET Dictionary: получить или создать новый

Я часто обнаруживаю, что создаю Dictionary с нетривиальным классом значений (например, List), а затем всегда записывать один и тот же шаблон кода при заполнении данных.

Например:

var dict = new Dictionary<string, List<string>>();
string key = "foo";
string aValueForKey = "bar";

То есть, я хочу вставить "bar" в список, соответствующий клавише "foo", где ключ "foo" может не отображаться ни на что.

Здесь я использую постоянно повторяющийся шаблон:

List<string> keyValues;
if (!dict.TryGetValue(key, out keyValues))
  dict.Add(key, keyValues = new List<string>());
keyValues.Add(aValueForKey);

Есть ли более элегантный способ сделать это?

Связанные вопросы, которые не имеют ответов на этот вопрос:

4b9b3361

Ответ 1

Мы немного изменили это, но эффект подобен:

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) 
    where TValue : new()
{
    TValue val;

    if (!dict.TryGetValue(key, out val))
    {
        val = new TValue();
        dict.Add(key, val);
    }

    return val;
}

Вызывается:

var dictionary = new Dictionary<string, List<int>>();

List<int> numbers = dictionary.GetOrCreate("key");

Он использует общее ограничение для открытых конструкторов без параметров: where TValue : new().

Чтобы помочь с обнаружением, если метод расширения не является весьма специфичным для узкой проблемы, мы склонны размещать методы расширения в пространстве имен типа, который они расширяют, в этом случае:

namespace System.Collections.Generic

В большинстве случаев человек, использующий тип, имеет оператор using, определенный сверху, поэтому IntelliSense также найдет методы расширения, определенные в вашем коде.

Ответ 2

Как и во многих проблемах с программированием, когда вы что-то делаете, реорганизуйте его в метод:

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

Ответ 3

Хорошо, другой подход:

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

Таким образом, вам нужно "попытаться добавить" ваше значение в общий список (очевидно, обобщаемый для общей коллекции), нулевую проверку и попытку получить существующий ключ/значения в вашем словаре. Использование и пример:

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

Надеюсь, что это поможет.

Ответ 4

А как насчет этого?

var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>();
keyValues.Add(aValueForKey);