Заголовок достаточно прост, почему я не могу:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Заголовок достаточно прост, почему я не могу:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Комментарий к исходному вопросу суммирует это довольно хорошо:
потому что никто не проектировал, не определял, не реализовывал, не тестировал, не документировал и не отправлял эту функцию. - @Gabe Moothart
В чем причина? Вероятно, потому, что поведение слияния словарей не может быть обосновано так, как это соответствует правилам Framework.
AddRange
не существует, потому что диапазон не имеет никакого значения для ассоциативного контейнера, поскольку диапазон данных позволяет дублировать записи. Например, если у вас есть IEnumerable<KeyValuePair<K,T>>
, эта коллекция не защищает дубликаты записей.
Поведение добавления пары пар ключ-значение или даже слияние двух словарей прямолинейно. Однако поведение, связанное с несколькими повторяющимися записями, не является.
Каким должно быть поведение метода, когда он имеет дело с дубликатом?
Есть как минимум три решения, о которых я могу думать:
Когда генерируется исключение, каково должно быть состояние исходного словаря?
Add
почти всегда реализуется как атомная операция: он успешно завершает и обновляет состояние коллекции, или он терпит неудачу, и состояние коллекции остается неизменным. Поскольку AddRange
может выйти из строя из-за повторяющихся ошибок, способ сохранить его поведение в соответствии с Add
будет также сделать его атомарным, выбросив исключение в любой дубликат и оставив состояние исходного словаря неизменным.
В качестве потребителя API было бы утомительно иметь итеративный метод удаления повторяющихся элементов, что подразумевает, что AddRange
должен выдать единственное исключение, содержащее все повторяющиеся значения.
Выбор затем сводится к:
Существуют аргументы для поддержки обоих вариантов использования. Для этого добавьте флаг IgnoreDuplicates
в подпись?
Флаг IgnoreDuplicates
(если установлен в true) также обеспечит значительную скорость, так как базовая реализация будет обходить код для повторной проверки.
Итак, теперь у вас есть флаг, который позволяет AddRange
поддерживать оба случая, но имеет недокументированный побочный эффект (это то, что разработчики Framework работали очень трудно, чтобы этого избежать).
Резюме
Поскольку нет четкого, последовательного и ожидаемого поведения, когда дело касается дубликатов, проще не общаться с ними все вместе и не предоставлять метод для начала.
Если вы постоянно сталкиваетесь с необходимостью объединить словари, вы можете, конечно, написать свой собственный метод расширения, чтобы объединить словари, которые будут вести себя так, как это работает для вашего приложения.
Я нашел другое решение:
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;
}
}
}
Веселись.
Моя догадка заключается в отсутствии надлежащего вывода для пользователя о том, что произошло. Как вы не можете повторять ключи в словарях, как бы вы справились с объединением двух словарей, где некоторые ключи пересекаются? Конечно, вы могли бы сказать: "Мне все равно", но это нарушает соглашение о возврате false/throw исключение для повторения ключей.
В случае, если кто-то сталкивается с таким вопросом, как я, - можно достичь "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())
. В этом случае он просто берет первый элемент из группы дубликатов, но при необходимости вы можете использовать более сложную логику.
Вы можете сделать это
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>>
Если вы имеете дело с новым Словарем (и у вас нет существующих строк для проигрывания), вы всегда можете использовать ToDictionary() из другого списка объектов.
Итак, в вашем случае вы сделали бы что-то вроде этого:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Если вы знаете, что у вас не будет дубликатов ключей, вы можете сделать:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Он выдает исключение, если существует двойная пара ключей/значений.
Я не знаю, почему это не в рамках; должно быть. Нет никакой неопределенности; просто выбросьте исключение. В случае с этим кодом он генерирует исключение.
Не стесняйтесь использовать метод расширения следующим образом:
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;
}
Вот альтернативное решение с использованием 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);