Заголовок досить базовий, чому я не можу:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Заголовок досить базовий, чому я не можу:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Відповіді:
Коментар до оригінального питання підсумовує це досить добре:
тому що ніхто ніколи не розробляв, не уточнював, не реалізовував, не перевіряв, не задокументував та не постачав цю функцію - @Gabe Moothart
А чому? Ну, ймовірно, тому що поведінку об'єднаних словників не може бути аргументовано таким чином, що відповідає рамковим вказівкам.
AddRange
не існує, оскільки діапазон не має жодного значення для асоціативного контейнера, оскільки діапазон даних дозволяє дублювати записи. Наприклад, якщо IEnumerable<KeyValuePair<K,T>>
ця колекція не захищає від повторних записів.
Поведінка додавання колекції пар ключових значень або навіть об'єднання двох словників прямолінійна. Однак поведінка, як поводитися з кількома повторюваними записами, не є.
Якою має бути поведінка методу, коли він має справу з дублікатом?
Є щонайменше три рішення, про які я можу придумати:
Коли буде викинуто виняток, яким повинен бути стан оригінального словника?
Add
майже завжди реалізується як атомна операція: вона вдається та оновлює стан колекції, або вона виходить з ладу, а стан колекції залишається незмінним. Оскільки це AddRange
може бути невдалим через повторювані помилки, спосіб дотримуватися його поведінкиAdd
буде також зробити його атомарним, кинувши виняток на будь-який дублікат, а стан вихідного словника залишити незмінним.
Як споживач API, було б нудно ітеративно вилучати повторювані елементи, що означає, що AddRange
слід викинути єдиний виняток, який містить усі значення, що повторюються.
Вибір потім зводиться до:
Є аргументи для підтримки обох випадків використання. Для цього ви додаєте IgnoreDuplicates
прапор до підпису?
IgnoreDuplicates
Прапор (якщо встановлено вірно) також забезпечить більшу швидкість вгору, в якості базової реалізації обійде код для дубліката перевірки.
Отже, у вас є прапор, який дозволяє AddRange
підтримувати обидва випадки, але має незадокументований побічний ефект (це те, що дизайнери Framework працювали дуже важко, щоб уникнути).
Підсумок
Оскільки немає чіткої, послідовної та очікуваної поведінки, коли справа стосується дублікатів, простіше не розібратися з ними всі разом і не запропонувати метод для початку.
Якщо вам постійно доводиться об’єднувати словники, ви, звичайно, можете написати власний метод розширення для злиття словників, який буде вести себе так, як це працює для ваших додатків.
AddMultiple
відрізняється від того AddRange
, незалежно від того, що його реалізація буде виграшною: ви кидаєте виняток із масиву всіх повторюваних ключів? Або ви кидаєте виняток на перший повторюваний ключ, який ви зустрічаєте? Яким має бути стан словника, якщо викинутий виняток? Пріштін, чи всі ключі, які вдалися?
Add
- або загорнувши кожного Add
в, try...catch
і таким чином ловити дублікати; або використовувати індексатор і перезаписати перше значення з пізнішим значенням; або попередньо перевірити використання, ContainsKey
перш ніж намагатися Add
, зберігаючи початкове значення. Якби рамки мали AddRange
або AddMultiple
метод, єдиний простий спосіб донести те, що сталося, був би через виняток, а обробка та відновлення, пов'язане з цим, було б не менш складним.
У мене є рішення:
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 class CollectionHelper
{
public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic[x.Key] = x.Value);
}
public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
}
public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
}
public static bool ContainsKeys<TKey, TValue>(this IDictionary<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;
}
}
}
}
Весело.
ToList()
, словник - це IEnumerable<KeyValuePair<TKey,TValue>
. Також метод другого та третього методів буде кинутим, якщо ви додасте існуюче значення ключа. Недобра ідея, ви шукали TryAdd
? Нарешті, другу можна замінити наWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
це не гарне рішення, тому я змінив код. Ви можете використовувати, try { mainDic.AddRange(addDic); } catch { do something }
якщо ви не впевнені в третьому методі. Другий метод працює чудово.
Якщо хтось стикається з цим питанням, як я, - можна досягти "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())
. У цьому випадку він просто бере перший елемент із групи дублікатів, але ви можете реалізувати там більш складну логіку, якщо потрібно.
dict1
не використовується порівняльник рівності за замовчуванням?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Моя здогадка - відсутність належного висновку для користувача щодо того, що сталося. Оскільки ви не можете мати повторювані ключі у словниках, як би ви обробляли об'єднання двох словників, де деякі ключі перетинаються? Звичайно, ви можете сказати: "Мені все одно", але це порушує умову повернення помилок / викидання виключення для повторення ключів.
Add
, окрім того, це може статися не один раз. Це кине те саме, ArgumentException
що Add
робить, напевно?
NamedElementException
??), або викинутий замість, або як внутрішній вираз ArgumentException, який визначає названий елемент, який конфліктує ... кілька варіантів, я б сказав
Ви могли це зробити
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>>
MethodThatReturnAnotherDic
. Походить від ОП. Перегляньте питання і мою відповідь ще раз.
Якщо ви маєте справу з новим словником (і у вас немає існуючих рядків для втрати), ви завжди можете використовувати ToDictionary () з іншого списку об'єктів.
Отже, у вашому випадку ви зробите щось подібне:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Якщо ви знаєте, що у вас не буде дублікатів ключів, ви можете:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Це буде винятком, якщо є дублікат пари ключ / значення.
Я не знаю, чому це не в рамках; має бути. Невідомості немає; просто киньте виняток. У випадку з цим кодом він робить виняток.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
Не соромтеся використовувати такий метод розширення:
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);
Як уже згадували інші, причина, по якій Dictionary<TKey,TVal>.AddRange
це не реалізовано, полягає в тому, що існують різні способи обробляти випадки, коли у вас є дублікати. Це також стосується Collection
або інтерфейсів , таких як IDictionary<TKey,TVal>
, ICollection<T>
і т.д.
Тільки List<T>
реалізує його, і ви зауважите, що IList<T>
інтерфейс не робить з тих же причин: очікувана поведінка при додаванні діапазону значень до колекції може змінюватися в широких межах, залежно від контексту.
Контекст вашого запитання говорить про те, що ви не турбуєтесь про дублікати, і в цьому випадку у вас є проста альтернатива oneliner, використовуючи Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));