Слияние словарей в C#

Как лучше всего объединить 2 или более словарей (Dictionary<T1,T2>) в C#? (Возможности 3.0, такие как LINQ, подходят).

Я думаю о сигнатуре метода в следующих строках:

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);

или же

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);

Обновлено: Получил классное решение от JaredPar и Jon Skeet, но я думал о чем-то, что обрабатывает повторяющиеся ключи. В случае коллизии не имеет значения, какое значение сохраняется в dict, если оно согласовано.

Не связано, но для тех, кто хочет объединить только два словаря без проверки дублирующихся ключей, это прекрасно работает: dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)

Benjol 14.12.2011 11:31

@Benjol, вы могли бы добавить это в разделе ответов

M.Kumaran 02.09.2013 17:54

Слияние Clojure на C#: dict1.Concat(dict2).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value)

Bruce 01.06.2015 07:00

@Benjol: устраните дубликаты, отдав предпочтение записям из первого словаря с dictA.Concat(dictB.Where(kvp => !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=> kvp.Key, kvp => kvp.Value).

Suncat2000 13.04.2020 16:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
542
4
314 552
26
Перейти к ответу Данный вопрос помечен как решенный

Ответы 26

Тривиальное решение было бы таким:

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
    Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var dict in dictionaries)
        foreach (var x in dict)
            result[x.Key] = x.Value;
    return result;
}

Спасибо. Получил все, что хочу.

Gaurav Koradiya 06.07.2020 14:48

Попробуйте следующее

static Dictionary<TKey, TValue>
    Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> enumerable)
{
    return enumerable.SelectMany(x => x).ToDictionary(x => x.Key, y => y.Value);
}
Ответ принят как подходящий

Это частично зависит от того, что вы хотите, если вы столкнетесь с дубликатами. Например, вы можете:

var result = dictionaries.SelectMany(dict => dict)
                         .ToDictionary(pair => pair.Key, pair => pair.Value);

Это вызовет исключение, если вы получите какие-либо повторяющиеся ключи.

Обновлено: если вы используете ToLookup, вы получите поиск, который может иметь несколько значений для каждого ключа. Затем вы мог конвертируете это в словарь:

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

Это немного некрасиво и неэффективно, но это самый быстрый способ сделать это с точки зрения кода. (По общему признанию, я не тестировал это.)

Вы, конечно, можете написать свой собственный метод расширения ToDictionary2 (с лучшим названием, но сейчас у меня нет времени думать о нем) - это не так уж сложно, просто перезаписав (или проигнорируя) повторяющиеся ключи. Важным моментом (на мой взгляд) является использование SelectMany и понимание того, что словарь поддерживает итерацию по парам ключ / значение.

Чтобы фактически объединить значения вместо того, чтобы просто брать их из первого словаря, вы можете заменить group => group.First () на group => group.SelectMany (value => value) в отредактированном ответе Джона Скита.

andreialecu 13.04.2011 19:53

Теперь мне интересно, что GroupBy подходит лучше, чем ToLookup? Я думаю, что ILookup просто добавляет индексатор, свойство размера и содержит метод поверх IGrouping - так что это должно быть немного быстрее?

toong 30.05.2013 19:25

@toong: Честно говоря, все должно быть в порядке. Использование GroupBy действительно могло бы быть немного более эффективным, но я сомневаюсь, что это будет иметь значение в любом случае.

Jon Skeet 30.05.2013 21:06

Незначительная деталь: наряду с большинством других ответов это не касается ситуации, когда (некоторые из) входных словарей используют компаратор не по умолчанию: например, ключ строки без учета регистра. Полностью общее решение должно позволить вызывающей стороне указать средство сравнения для целевого словаря. Или скопируйте в существующий словарь, как ответ Йонаса Стенсведа.

Joe 04.12.2013 11:21

@Joe: Это достаточно просто, предоставив средство сравнения для вызова метода ToDictionary. Я бы предпочел, чтобы ответ был проще для более распространенного случая, и я надеюсь, что любой, кому нужен пользовательский компаратор, сможет найти соответствующую перегрузку.

Jon Skeet 04.12.2013 11:23

@Serge: Предлагаю вам задать новый вопрос с точными требованиями.

Jon Skeet 06.11.2018 19:32

Серж - решение Джона работает для набора словарей, содержащихся в переменных .. словарях. Итак, для двух словарей у вас будет список из двух словарей. SelectMany объединит их в список пар ключ / значение, которые затем используются для создания нового словаря.

Gerard ONeill 19.01.2019 04:43

Просто обратите внимание, что если вы вместо этого используете .Last, он будет «обновлять» исходные значения вместо блокировки и никогда не изменяться.

Greg 05.03.2021 20:14

Как насчет добавления перегрузки params?

Кроме того, вы должны ввести их как IDictionary для максимальной гибкости.

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(IEnumerable<IDictionary<TKey, TValue>> dictionaries)
{
    // ...
}

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(params IDictionary<TKey, TValue>[] dictionaries)
{
    return Merge((IEnumerable<TKey, TValue>) dictionaries);
}

Он как бы дополняет другие ответы здесь, отмечая, что вы можете сделать класс DictionaryExtensions еще лучше. Возможно, этот вопрос должен стать вики-страницей или классом, зарегистрированным в git ....

Chris Moschini 13.12.2012 11:15

Вот вспомогательная функция, которую я использую:

using System.Collections.Generic;
namespace HelperMethods
{
    public static class MergeDictionaries
    {
        public static void Merge<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
        {
            if (second == null || first == null) return;
            foreach (var item in second) 
                if (!first.ContainsKey(item.Key)) 
                    first.Add(item.Key, item.Value);
        }
    }
}

Здесь перечислено множество отличных решений, но мне больше всего нравится простота этого. Просто мое личное предпочтение.

jason 19.10.2012 19:24
if (first == null), тогда эта логика бесполезна, потому что first не является ref и не возвращается. И это не может быть ref, потому что он объявлен как экземпляр интерфейса; вам нужно либо удалить проверку ошибок, либо объявить ее как экземпляр класса, либо просто вернуть новый словарь (без метода расширения).
Grault 15.03.2013 10:18

@Jesdisciple - удалил проблему согласно вашему предложению

Andrew Harry 12.02.2015 01:56
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

Просто интересно - не будет ли просто Союз просто работать? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages())) Если вы используете его в производственном коде, дайте мне знать, если есть какие-либо недостатки! :)

Mars Robertson 02.08.2013 11:21

@Michal: Union вернет IEnumerable<KeyValuePair<TKey, TValue>>, где равенство определяется равенством ключа и значения (есть перегрузка, позволяющая использовать альтернативы). Это нормально для цикла foreach, где это все, что необходимо, но есть случаи, когда вам действительно нужен словарь.

Timothy 07.10.2016 21:15

Это не сработает, если есть несколько ключей («правые» клавиши заменяют «лефтерные» ключи), может объединить несколько словарей (при желании) и сохранить тип (с ограничением, что для этого требуется значимый общедоступный конструктор по умолчанию):

public static class DictionaryExtensions
{
    // Works in C#3/VS2008:
    // Returns a new dictionary of this ... others merged leftward.
    // Keeps the type of 'this', which must be default-instantiable.
    // Example: 
    //   result = map.MergeLeft(other1, other2, ...)
    public static T MergeLeft<T,K,V>(this T me, params IDictionary<K,V>[] others)
        where T : IDictionary<K,V>, new()
    {
        T newMap = new T();
        foreach (IDictionary<K,V> src in
            (new List<IDictionary<K,V>> { me }).Concat(others)) {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K,V> p in src) {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

}

Отличная работа. Это было именно то, что мне нужно. Хорошее использование дженериков. Согласны, что синтаксис немного неудобен, но вы ничего не можете с этим поделать.

Peter M 23.04.2010 21:09

Мне понравилось это решение, но есть предостережение: если словарь this T me был объявлен с использованием new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase) или чего-то подобного, результирующий словарь не будет сохранять такое же поведение.

João Portela 09.01.2014 18:51

Если вы сделаете meDictionary<K,V>, вы можете сделать var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);, и вы также избежите дополнительного параметра типа T.

ANeves thinks SE is evil 30.01.2014 15:25

Есть у кого-нибудь пример, как пользоваться этим зверьком?

Tim 27.01.2015 21:54

@Tim: Я добавил пример для зверя ниже, я добавил решение от ANeves, и в результате получился более прирученный зверь :)

keni 22.03.2015 07:25

Я бы сделал это так:

dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));

Просто и легко. Согласно это сообщение в блоге, это даже быстрее, чем большинство циклов, поскольку его базовая реализация обращается к элементам по индексу, а не по счетчику (см. этот ответ).

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

Если дубликаты имеют значение, используйте dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value). Таким образом, значения из dictionaryFrom переопределяют значение для возможно существующего ключа.

okrumnow 21.02.2012 13:46

Вряд ли это будет быстрее цикла - вы забываете, что ToList () фактически копирует словарь. Но быстрее кодировать! ;-)

Søren Boisen 20.03.2014 20:06

Это быстрее .. Поскольку ToList () фактически не копирует словарь, он просто копирует ссылки на элементы внутри них. В основном сам объект списка будет иметь новый адрес в памяти, объекты такие же !!

GuruC 18.03.2015 04:19

Сообщение в блоге исчезло, но ура Wayback machine: web.archive.org/web/20150311191313/http://diditwith.net/2006‌ / 10 /…

CAD bloke 18.03.2016 11:59

Как насчет выделения памяти? Ссылка не работает, поэтому непонятно, как она может быть лучше, чем foreach, которому не нужно выделять память для нового списка.

Valentin Kuzub 28.11.2016 16:30

Хм, я полагаю, лучше, чем ответ Джона Скита то.

Sнаđошƒаӽ 12.12.2016 09:43
лучшеJon Skeet's answer? Джон Скит
Kiquenet 26.02.2018 15:15

На основе ответов выше, но с добавлением параметра Func, чтобы вызывающий абонент мог обрабатывать дубликаты:

public static Dictionary<TKey, TValue> Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> dicts, 
                                                           Func<IGrouping<TKey, TValue>, TValue> resolveDuplicates)
{
    if (resolveDuplicates == null)
        resolveDuplicates = new Func<IGrouping<TKey, TValue>, TValue>(group => group.First());

    return dicts.SelectMany<Dictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(dict => dict)
                .ToLookup(pair => pair.Key, pair => pair.Value)
                .ToDictionary(group => group.Key, group => resolveDuplicates(group));
}

Партия практически мертва, но вот «улучшенная» версия user166390, которая попала в мою библиотеку расширений. Помимо некоторых деталей, я добавил делегата для вычисления объединенного значения.

/// <summary>
/// Merges a dictionary against an array of other dictionaries.
/// </summary>
/// <typeparam name = "TResult">The type of the resulting dictionary.</typeparam>
/// <typeparam name = "TKey">The type of the key in the resulting dictionary.</typeparam>
/// <typeparam name = "TValue">The type of the value in the resulting dictionary.</typeparam>
/// <param name = "source">The source dictionary.</param>
/// <param name = "mergeBehavior">A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)</param>
/// <param name = "mergers">Dictionaries to merge against.</param>
/// <returns>The merged dictionary.</returns>
public static TResult MergeLeft<TResult, TKey, TValue>(
    this TResult source,
    Func<TKey, TValue, TValue, TValue> mergeBehavior,
    params IDictionary<TKey, TValue>[] mergers)
    where TResult : IDictionary<TKey, TValue>, new()
{
    var result = new TResult();
    var sources = new List<IDictionary<TKey, TValue>> { source }
        .Concat(mergers);

    foreach (var kv in sources.SelectMany(src => src))
    {
        TValue previousValue;
        result.TryGetValue(kv.Key, out previousValue);
        result[kv.Key] = mergeBehavior(kv.Key, kv.Value, previousValue);
    }

    return result;
}

Слияние с использованием метода расширения. Он не генерирует исключение, когда есть повторяющиеся ключи, но заменяет эти ключи ключами из второго словаря.

internal static class DictionaryExtensions
{
    public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
    {
        if (first == null) throw new ArgumentNullException("first");
        if (second == null) throw new ArgumentNullException("second");

        var merged = new Dictionary<T1, T2>();
        first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
        second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);

        return merged;
    }
}

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

Dictionary<string, string> merged = first.Merge(second);

Слияние с использованием EqualityComparer, который сопоставляет элементы для сравнения с другим значением / типом. Здесь мы сопоставим KeyValuePair (тип элемента при перечислении словаря) с Key.

public class MappedEqualityComparer<T,U> : EqualityComparer<T>
{
    Func<T,U> _map;

    public MappedEqualityComparer(Func<T,U> map)
    {
        _map = map;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityComparer<U>.Default.Equals(_map(x), _map(y));
    }

    public override int GetHashCode(T obj)
    {
        return _map(obj).GetHashCode();
    }
}

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

// if dictA and dictB are of type Dictionary<int,string>
var dict = dictA.Concat(dictB)
                .Distinct(new MappedEqualityComparer<KeyValuePair<int,string>,int>(item => item.Key))
                .ToDictionary(item => item.Key, item=> item.Value);

Я очень опаздываю на вечеринку и, возможно, что-то упускаю, но если либо нет повторяющихся ключей, либо, как говорит OP, «В случае коллизии не имеет значения, какое значение сохраняется в dict, если оно последовательный, "что не так с этим (слияние D2 с D1)?"

foreach (KeyValuePair<string,int> item in D2)
            {
                 D1[item.Key] = item.Value;
            }

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

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

404 01.03.2018 13:46

Следующее работает для меня. Если есть дубликаты, будет использоваться значение dictA.

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> dictA, IDictionary<TKey, TValue> dictB)
    where TValue : class
{
    return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);
}

@EsbenSkovPedersen Я не думаю, что сталкивался с этим, когда отвечал на это (новый разработчик C#)

Ethan Reesor 05.05.2015 16:43

Мне пришлось удалить 'where TValue: class', чтобы использовать Guid в качестве значения

om471987 26.01.2016 02:47

@ om471987 Да, Guid - это структура, а не класс, так что в этом есть смысл. Думаю, изначально у меня возникла проблема, когда я не добавил этот пункт. Больше не помню почему.

Ethan Reesor 26.01.2016 10:16

Учитывая производительность поиска и удаления ключей словаря, поскольку они являются хэш-операциями, и учитывая, что формулировка вопроса была Лучший, я думаю, что ниже это совершенно правильный подход, а другие немного сложнее, IMHO.

    public static void MergeOverwrite<T1, T2>(this IDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null) return;

        foreach (var e in newElements)
        {
            dictionary.Remove(e.Key); //or if you don't want to overwrite do (if !.Contains()
            dictionary.Add(e);
        }
    }

ИЛИ, если вы работаете в многопоточном приложении и ваш словарь в любом случае должен быть потокобезопасным, вы должны сделать это:

    public static void MergeOverwrite<T1, T2>(this ConcurrentDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null || newElements.Count == 0) return;

        foreach (var ne in newElements)
        {
            dictionary.AddOrUpdate(ne.Key, ne.Value, (key, value) => value);
        }
    }

Затем вы можете обернуть это, чтобы он обрабатывал перечисление словарей. В любом случае, вы смотрите примерно на ~ O (3n) (все условия идеальны), поскольку .Add() сделает дополнительный, ненужный, но практически бесплатный Contains() за кулисами. Я не думаю, что станет намного лучше.

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

    public static IDictionary<T1, T2> MergeAllOverwrite<T1, T2>(IList<IDictionary<T1, T2>> allDictionaries)
    {
        var initSize = allDictionaries.Sum(d => d.Count);
        var resultDictionary = new Dictionary<T1, T2>(initSize);
        allDictionaries.ForEach(resultDictionary.MergeOverwrite);
        return resultDictionary;
    }

Обратите внимание, что я использовал IList<T> для этого метода ... в основном потому, что если вы берете IEnumerable<T>, вы открываете себя для нескольких перечислений одного и того же набора, что может быть очень дорогостоящим, если вы получили свою коллекцию словарей из отложенный оператор LINQ.

@Tim: это должен быть комментарий, но комментарии не позволяют редактировать код.

Dictionary<string, string> t1 = new Dictionary<string, string>();
t1.Add("a", "aaa");
Dictionary<string, string> t2 = new Dictionary<string, string>();
t2.Add("b", "bee");
Dictionary<string, string> t3 = new Dictionary<string, string>();
t3.Add("c", "cee");
t3.Add("d", "dee");
t3.Add("b", "bee");
Dictionary<string, string> merged = t1.MergeLeft(t2, t2, t3);

Примечание: я применил модификацию @ANeves к решению @Andrew Orsich, поэтому теперь MergeLeft выглядит так:

public static Dictionary<K, V> MergeLeft<K, V>(this Dictionary<K, V> me, params IDictionary<K, V>[] others)
    {
        var newMap = new Dictionary<K, V>(me, me.Comparer);
        foreach (IDictionary<K, V> src in
            (new List<IDictionary<K, V>> { me }).Concat(others))
        {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K, V> p in src)
            {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

Близко к тому, что я написал сам. Это, конечно, будет работать только для типов Dictionary. Могут быть добавлены перегрузки для других типов словарей (ConcurrentDictionary, ReadOnlyDictionary и т. д.). Я не думаю, что создание нового списка и concat необходимо лично. Я просто сначала итерировал массив params, а затем повторил каждый KVP для каждого словаря. Я не вижу отступления.

crush 22.07.2015 02:34

Вы можете использовать IDictionary вместо Dictionary

Mariusz Jamro 18.04.2016 09:41

Вы всегда будете получать объект Dictionary, даже если вы использовали метод расширения на ConcurrentDictionary. Это может привести к затруднению отслеживания ошибок.

Marcel 14.05.2016 08:59

Я знаю, что это старый вопрос, но, поскольку теперь у нас есть LINQ, вы можете сделать это в одной строке, например

Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));

или же

mergee.ToList().ForEach(kvp => merged.Append(kvp));

Извините за отрицательный голос @Cruces, но ваш первый пример дублирует ответ stackoverflow.com/a/6695211/704808, а ваш второй пример просто отбрасывает расширенный IEnumerable <KeyValuePair <string, string >> после добавления каждого элемента из mergee.

weir 06.07.2018 22:30

или же :

public static IDictionary<TKey, TValue> Merge<TKey, TValue>( IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        return x
            .Except(x.Join(y, z => z.Key, z => z.Key, (a, b) => a))
            .Concat(y)
            .ToDictionary(z => z.Key, z => z.Value);
    }

результатом является объединение, в котором для повторяющихся записей выигрывает "y".

Боялся видеть сложные ответы, будучи новичком в C#.

Вот несколько простых ответов. Объединение словарей d1, d2 и т. д. И обработка любых перекрывающихся ключей («b» в примерах ниже):

Пример 1

{
    // 2 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };

    var result1 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=22, c=30    That is, took the "b" value of the last dictionary
}

Пример 2

{
    // 3 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };
    var d3 = new Dictionary<string, int>() { { "d", 40 }, { "b", 23 } };

    var result1 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30, d=40    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=23, c=30, d=40    That is, took the "b" value of the last dictionary
}

Для более сложных сценариев см. Другие ответы. Надеюсь, это помогло.

using System.Collections.Generic;
using System.Linq;

public static class DictionaryExtensions
{
    public enum MergeKind { SkipDuplicates, OverwriteDuplicates }
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, MergeKind kind = MergeKind.SkipDuplicates) =>
        source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });
}

Вы можете либо пропустить / игнорировать (по умолчанию), либо перезаписать дубликаты: и ваш дядя Боб, при условии, что вы не слишком привередливы в производительности Linq, а вместо этого предпочитаете сжатый поддерживаемый код, как я: в этом случае вы можете удалить MergeKind.SkipDuplicates по умолчанию, чтобы обеспечить соблюдение выбор для вызывающего абонента, и разработчик узнает, какими будут результаты!

Уточненный ответ ниже без ненужного двоичного перечисления, когда подойдет bool ...

mattjs 14.08.2019 10:56

Нет ничего выше или ниже, ответы оцениваются на основе их голосов и т. д.

user692942 30.07.2020 18:18
public static IDictionary<K, V> AddRange<K, V>(this IDictionary<K, V> one, IDictionary<K, V> two)
        {
            foreach (var kvp in two)
            {
                if (one.ContainsKey(kvp.Key))
                    one[kvp.Key] = two[kvp.Key];
                else
                    one.Add(kvp.Key, kvp.Value);
            }
            return one;
        }

Я не думаю, что вам нужен if; просто one[kvp.Key] = two[kvp.Key]; должен охватывать оба случая.

Guillermo Prandi 15.09.2020 19:34

Я согласен с @GuillermoPrandi. Хотя лучше перестраховаться, чем сожалеть

mattylantz 17.09.2020 21:31

Опция 1 : Это зависит от того, что вы хотите сделать, если вы уверены, что у вас нет повторяющегося ключа в обоих словарях. чем вы могли бы сделать:

var result = dictionary1.Union(dictionary2).ToDictionary(k => k.Key, v => v.Value)

Примечание : Это вызовет ошибку, если вы получите дубликаты ключей в словарях.

Вариант 2: Если у вас может быть дублированный ключ, вам придется обрабатывать дубликат ключа с помощью предложения where.

var result = dictionary1.Union(dictionary2.Where(k => !dictionary1.ContainsKey(k.Key))).ToDictionary(k => k.Key, v => v.Value)

Примечание : Не будет дублироваться ключ. если будет какой-либо повторяющийся ключ, то он получит ключ Dictionary1.

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

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

Версия из ответа @ user166390 с добавленным параметром IEqualityComparer, позволяющим проводить сравнение ключей без учета регистра.

    public static T MergeLeft<T, K, V>(this T me, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        return me.MergeLeft(me.Comparer, others);
    }

    public static T MergeLeft<T, K, V>(this T me, IEqualityComparer<K> comparer, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        T newMap = Activator.CreateInstance(typeof(T), new object[] { comparer }) as T;

        foreach (Dictionary<K, V> src in 
            (new List<Dictionary<K, V>> { me }).Concat(others))
        {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K, V> p in src)
            {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }
fromDic.ToList().ForEach(x =>
        {
            if (toDic.ContainsKey(x.Key))
                toDic.Remove(x.Key);
            toDic.Add(x);
        });

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

using System.Collections.Generic;
using System.Linq;

public static partial class Extensions
{
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, bool overwrite = false)
    {
        source.ToList().ForEach(_ => {
            if ((!target.ContainsKey(_.Key)) || overwrite)
                target[_.Key] = _.Value;
        });
    }
}

Обратите внимание, что если вы используете метод расширения под названием 'Add', вы можете использовать инициализаторы коллекции, чтобы объединить столько словарей, сколько необходимо, например:

public static void Add<K, V>(this Dictionary<K, V> d, Dictionary<K, V> other) {
  foreach (var kvp in other)
  {
    if (!d.ContainsKey(kvp.Key))
    {
      d.Add(kvp.Key, kvp.Value);
    }
  }
}


var s0 = new Dictionary<string, string> {
  { "A", "X"}
};
var s1 = new Dictionary<string, string> {
  { "A", "X" },
  { "B", "Y" }
};
// Combine as many dictionaries and key pairs as needed
var a = new Dictionary<string, string> {
  s0, s1, s0, s1, s1, { "C", "Z" }
};

Другие вопросы по теме