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

Почему у словаря нет AddRange?

Заголовок достаточно прост, почему я не могу:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
4b9b3361

Ответ 1

Комментарий к исходному вопросу суммирует это довольно хорошо:

потому что никто не проектировал, не определял, не реализовывал, не тестировал, не документировал и не отправлял эту функцию. - @Gabe Moothart

В чем причина? Вероятно, потому, что поведение слияния словарей не может быть обосновано так, как это соответствует правилам Framework.

AddRange не существует, потому что диапазон не имеет никакого значения для ассоциативного контейнера, поскольку диапазон данных позволяет дублировать записи. Например, если у вас есть IEnumerable<KeyValuePair<K,T>>, эта коллекция не защищает дубликаты записей.

Поведение добавления пары пар ключ-значение или даже слияние двух словарей прямолинейно. Однако поведение, связанное с несколькими повторяющимися записями, не является.

Каким должно быть поведение метода, когда он имеет дело с дубликатом?

Есть как минимум три решения, о которых я могу думать:

  • выдает исключение для первой записи, которая является дубликатом
  • выдает исключение, содержащее все повторяющиеся записи
  • Игнорировать дубликаты

Когда генерируется исключение, каково должно быть состояние исходного словаря?

Add почти всегда реализуется как атомная операция: он успешно завершает и обновляет состояние коллекции, или он терпит неудачу, и состояние коллекции остается неизменным. Поскольку AddRange может выйти из строя из-за повторяющихся ошибок, способ сохранить его поведение в соответствии с Add будет также сделать его атомарным, выбросив исключение в любой дубликат и оставив состояние исходного словаря неизменным.

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

Выбор затем сводится к:

  • Выбросьте исключение со всеми дубликатами, оставив исходный словарь самостоятельно.
  • Игнорировать дубликаты и продолжить.

Существуют аргументы для поддержки обоих вариантов использования. Для этого добавьте флаг IgnoreDuplicates в подпись?

Флаг IgnoreDuplicates (если установлен в true) также обеспечит значительную скорость, так как базовая реализация будет обходить код для повторной проверки.

Итак, теперь у вас есть флаг, который позволяет AddRange поддерживать оба случая, но имеет недокументированный побочный эффект (это то, что разработчики Framework работали очень трудно, чтобы этого избежать).

Резюме

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

Если вы постоянно сталкиваетесь с необходимостью объединить словари, вы можете, конечно, написать свой собственный метод расширения, чтобы объединить словари, которые будут вести себя так, как это работает для вашего приложения.

Ответ 2

Я нашел другое решение:

Dictionary<string, string> mainDic = new Dictionary<string, string>() { 
    { "Key1", "Value1" },
    { "Key2", "Value2.1" },
};
Dictionary<string, string> additionalDic= new Dictionary<string, string>() { 
    { "Key2", "Value2.2" },
    { "Key3", "Value3" },
};
mainDic.AddRangeOverride(additionalDic); // Overrides all existing keys
// or
mainDic.AddRangeNewOnly(additionalDic); // Adds new keys only
// or
mainDic.AddRange(additionalDic); // Throws an error if keys already exist
// or
if (!mainDic.ContainsKeys(additionalDic.Keys)) // Checks if keys don't exist
{
    mainDic.AddRange(additionalDic);
}

...

namespace MyProject.Helper
{
    public static void AddRangeOverride<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => dic[x.Key] = x.Value);
    }

    public static void AddRangeNewOnly<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
    }

    public static void AddRange<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
    }

    public static bool ContainsKeys<TKey, TValue>(this Dictionary<TKey, TValue> dic, IEnumerable<TKey> keys)
    {
        bool result = false;
        keys.ForEachOrBreak((x) => { result = dic.ContainsKey(x); return result; });
        return result;
    }

    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
            action(item);
    }

    public static void ForEachOrBreak<T>(this IEnumerable<T> source, Func<T, bool> func)
    {
        foreach (var item in source)
        {
            bool result = func(item);
            if (result) break;
        }
    }

}

Веселись.

Ответ 3

Моя догадка заключается в отсутствии надлежащего вывода для пользователя о том, что произошло. Как вы не можете повторять ключи в словарях, как бы вы справились с объединением двух словарей, где некоторые ключи пересекаются? Конечно, вы могли бы сказать: "Мне все равно", но это нарушает соглашение о возврате false/throw исключение для повторения ключей.

Ответ 4

В случае, если кто-то сталкивается с таким вопросом, как я, - можно достичь "AddRange" с помощью методов расширения IEnumerable:

var combined =
    dict1.Union(dict2)
        .GroupBy(kvp => kvp.Key)
        .Select(grp => grp.First())
        .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Основной трюк при объединении словарей имеет дело с дублирующими ключами. В приведенном выше коде находится часть .Select(grp => grp.First()). В этом случае он просто берет первый элемент из группы дубликатов, но при необходимости вы можете использовать более сложную логику.

Ответ 5

Вы можете сделать это

Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);

public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
    dic.Add(.., ..);
}

или используйте Список для добавления и/или используя шаблон выше.

List<KeyValuePair<string, string>>

Ответ 6

Если вы имеете дело с новым Словарем (и у вас нет существующих строк для проигрывания), вы всегда можете использовать ToDictionary() из другого списка объектов.

Итак, в вашем случае вы сделали бы что-то вроде этого:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);

Ответ 7

Если вы знаете, что у вас не будет дубликатов ключей, вы можете сделать:

dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Он выдает исключение, если существует двойная пара ключей/значений.

Я не знаю, почему это не в рамках; должно быть. Нет никакой неопределенности; просто выбросьте исключение. В случае с этим кодом он генерирует исключение.

Ответ 8

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

public static Dictionary<T, U> AddRange<T, U>(this Dictionary<T, U> destination, Dictionary<T, U> source)
{
  if (destination == null) destination = new Dictionary<T, U>();
  foreach (var e in source)
    destination.Add(e.Key, e.Value);
  return destination;
}

Ответ 9

Вот альтернативное решение с использованием c # 7 ValueTuples (литералы кортежей)

public static class DictionaryExtensions
{
    public static Dictionary<TKey, TValue> AddRange<TKey, TValue>(this Dictionary<TKey, TValue> source,  IEnumerable<ValueTuple<TKey, TValue>> kvps)
    {
        foreach (var kvp in kvps)
            source.Add(kvp.Item1, kvp.Item2);

        return source;
    }

    public static void AddTo<TKey, TValue>(this IEnumerable<ValueTuple<TKey, TValue>> source, Dictionary<TKey, TValue> target)
    {
        target.AddRange(source);
    }
}

Используется как

segments
    .Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
    .Where(zip => zip.Item1 != null)
    .AddTo(queryParams);