【问题标题】:Merging dictionaries in C#在 C# 中合并字典
【发布时间】:2010-09-22 14:08:32
【问题描述】:

在 C# 中合并 2 个或多个字典 (Dictionary<T1,T2>) 的最佳方法是什么? (LINQ 之类的 3.0 功能很好)。

我正在考虑一个方法签名:

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 那里得到了一个很酷的解决方案,但我正在考虑处理重复键的东西。在发生冲突的情况下,将哪个值保存到字典中并不重要,只要它是一致的即可。

【问题讨论】:

  • 不相关,但对于希望只合并两个字典而不进行重复键检查的人来说,这很好用:dicA.Concat(dicB).ToDictionary(kvp =&gt; kvp.Key, kvp =&gt; kvp.Value)
  • @Benjol 你可以在答案部分添加这个
  • Clojure 在 C# 中的合并:dict1.Concat(dict2).GroupBy(p =&gt; p.Key).ToDictionary(g =&gt; g.Key, g =&gt; g.Last().Value)
  • @Benjol:通过优先选择第一个字典中带有dictA.Concat(dictB.Where(kvp =&gt; !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=&gt; kvp.Key, kvp =&gt; kvp.Value) 的条目来消除重复项。

标签: c# dictionary merge


【解决方案1】:

这是我的解决方案:它的行为类似于 python 中的 dict.update() 方法。

public static class DictionaryExtensions
{
    public static void Update<K,V>(this IDictionary<K, V> me, IDictionary<K, V> other)
    {
        foreach (var x in other)
        {
            me[x.Key] = x.Value;
        }
    }
}

【讨论】:

    【解决方案2】:

    根据这篇文章中的所有答案,这是我能想到的最通用的解决方案。

    我创建了 IDictionary.Merge() 扩展的 2 个版本:

    • 合并(sourceLeft, sourceRight)
    • Merge(sourceLeft, sourceRight, Func mergeExpression)

    第二个是第一个的修改版本,它允许您指定一个 lambda 表达式来处理这样的重复:

    Dictionary<string, object> customAttributes = 
      HtmlHelper
        .AnonymousObjectToHtmlAttributes(htmlAttributes)
        .ToDictionary(
          ca => ca.Key, 
          ca => ca.Value
        );
    
    Dictionary<string, object> fixedAttributes = 
      new RouteValueDictionary(
        new { 
          @class = "form-control"
        }).ToDictionary(
          fa => fa.Key, 
          fa => fa.Value
        );
    
    //appending the html class attributes
    IDictionary<string, object> editorAttributes = fixedAttributes.Merge(customAttributes, (leftValue, rightValue) => leftValue + " " + rightValue);
    

    (您可以关注ToDictionary()Merge() 部分)

    这是扩展类(有 2 个版本的扩展,在右侧采用 IDictionary 的集合):

      public static class IDictionaryExtension
      {
        public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IDictionary<T, U> sourceRight)
        {
          IDictionary<T, U> result = new Dictionary<T,U>();
    
          sourceLeft
            .Concat(sourceRight)
            .ToList()
            .ForEach(kvp => 
              result[kvp.Key] = kvp.Value
            );
    
          return result;
        }
    
        public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IDictionary<T, U> sourceRight, Func<U, U, U> mergeExpression)
        {
          IDictionary<T, U> result = new Dictionary<T,U>();
    
          //Merge expression example
          //(leftValue, rightValue) => leftValue + " " + rightValue;
    
          sourceLeft
            .Concat(sourceRight)
            .ToList()
            .ForEach(kvp => 
              result[kvp.Key] =
                (!result.ContainsKey(kvp.Key))
                  ? kvp.Value
                  : mergeExpression(result[kvp.Key], kvp.Value)
            );
    
          return result;
        }
    
    
        public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IEnumerable<IDictionary<T, U>> sourcesRight)
        {
          IDictionary<T, U> result = new Dictionary<T, U>();
          
          new[] { sourceLeft }
            .Concat(sourcesRight)
            .ToList()
            .ForEach(dic =>
              result = result.Merge(dic)
            );
    
          return result;
        }
    
        public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IEnumerable<IDictionary<T, U>> sourcesRight, Func<U, U, U> mergeExpression)
        {
          IDictionary<T, U> result = new Dictionary<T, U>();
    
          new[] { sourceLeft }
            .Concat(sourcesRight)
            .ToList()
            .ForEach(dic =>
              result = result.Merge(dic, mergeExpression)
            );
    
          return result;
        }
      }
    

    mergeExpression 让您轻松处理想要合并项目的方式,例如加法、除法、乘法或任何您想要的特定过程。

    请注意,我尚未测试扩展的集合版本...它们可能仍需要一些调整。

    此外,扩展程序不会修改原始字典,如果需要,您必须将其重新分配。

    【讨论】:

      【解决方案3】:

      在没有 LINQ 的情况下再次简化,如果存在则布尔默认为非破坏性合并,如果为 true 则完全覆盖而不是使用枚举。它仍然适合我自己的需要,而无需任何更花哨的代码:

      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)
          {
              foreach (KeyValuePair _ in source)
                  if (overwrite || !target.ContainsKey(_.Key))
                      target[_.Key] = _.Value;
          }
      }
      

      【讨论】:

      • 似乎没必要用.ToList().ForEach代替foreach。为什么要分配列表?
      • 你说的很对!我只是喜欢近一个班轮,但对 KeyValuePair 的 foreach 循环将是最好的。我可能会更新它!
      【解决方案4】:

      这部分取决于遇到重复项时您希望发生的情况。例如,您可以这样做:

      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,并意识到字典支持对其键/值对进行迭代。

      【讨论】:

      • 要实际合并值而不是仅从第一个字典中获取它们,您可以在 Jon Skeet 的编辑中将 group => group.First() 替换为 group => group.SelectMany(value => value)回答。
      • 现在我想知道 GroupBy 会比 ToLookup 更适合?我认为 ILookup 只是在 IGrouping 之上添加了一个索引器、大小属性和 contains 方法 - 所以它必须快一点?
      • @toong:说实话,两者都可以。使用GroupBy 确实可以更高效一些——但我怀疑它是否会很重要。
      • 一个小细节:与大多数其他答案一起,这不涉及(某些)输入字典使用非默认比较器的情况:例如不区分大小写的字符串键。一个完全通用的解决方案应该允许调用者指定目标字典的比较器。或复制到现有字典中,例如 Jonas Stensved 的答案。
      • @Joe:通过为ToDictionary 方法调用提供比较器就很简单了。不过,对于更常见的情况,我更愿意让答案更简单,我希望任何需要自定义比较器的人都能找到相关的重载。
      【解决方案5】:

      除了Merge() 之外,我会拆分@orip 的简单且非垃圾创建解决方案以提供就地AddAll() 来处理将一个字典添加到另一个字典的简单情况。

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

      【讨论】:

        【解决方案6】:

        我参加聚会很晚了,可能错过了一些东西,但是如果没有重复的键,或者正如 OP 所说,“如果发生冲突,将哪个值保存到字典中并不重要只要它是一致的,”这个有什么问题(将D2合并到D1)?

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

        这似乎很简单,也许太简单了,我想知道我是否遗漏了什么。这就是我在一些我知道没有重复键的代码中使用的。不过,我仍在测试中,所以我现在很想知道我是否忽略了某些东西,而不是稍后再发现。

        【讨论】:

        • imo 最干净和最易读的解决方案之一,也避免了许多其他人使用的 ToList()。
        • 这个答案的唯一错误是 OP 要求一个解决方案“合并 2 个 或更多 字典的最佳方法”,但这个解决方案处理合并两个字典。
        【解决方案7】:

        如果有多个键(“righter”键替换“lefter”键),这不会爆炸,可以合并多个字典(如果需要)并保留类型(限制它需要一个有意义的默认公共构造函数):

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

        【讨论】:

        • 干得好。这正是我所需要的。很好地使用泛型。同意语法有点尴尬,但你无能为力。
        • 我喜欢这个解决方案,但有一个警告:如果 this T me 字典是使用 new Dictionary&lt;string, T&gt;(StringComparer.InvariantCultureIgnoreCase) 或类似的东西声明的,则生成的字典将不会保留相同的行为。
        • 如果你把me变成Dictionary&lt;K,V&gt;,那么你就可以做var newMap = new Dictionary&lt;TKey, TValue&gt;(me, me.Comparer);,而且你也避免了额外的T类型参数。
        • 有人有如何使用这个野兽的例子吗?
        • @Tim:我为下面的野兽添加了一个示例,我添加了 ANeves 的解决方案,结果是一个更加驯化的野兽:)
        【解决方案8】:

        请注意,如果您使用名为“添加”的扩展方法,您可以使用集合初始化器来组合尽可能多的字典,如下所示:

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

        【讨论】:

          【解决方案9】:
          fromDic.ToList().ForEach(x =>
                  {
                      if (toDic.ContainsKey(x.Key))
                          toDic.Remove(x.Key);
                      toDic.Add(x);
                  });
          

          【讨论】:

            【解决方案10】:

            来自@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;
                }
            

            【讨论】:

              【解决方案11】:

              选项 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());
              

              【讨论】:

                【解决方案12】:
                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]; 应该涵盖这两种情况。
                • 我同意@GuillermoPrandi。虽然,安全总比后悔好
                【解决方案13】:
                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; });
                }
                

                您可以跳过/忽略(默认)或覆盖重复项:Bob 是您的叔叔,前提是您不会对 Linq 性能过于挑剔,而是喜欢像我一样简洁可维护的代码:在这种情况下,您可以删除默认的 MergeKind。 SkipDuplicates 强制调用者进行选择,并使开发人员知道结果将是什么!

                【讨论】:

                • 下面的精炼答案没有不必要的二进制枚举,当一个布尔值会做......
                • 没有高于或低于,答案将根据他们的投票等进行排名。
                【解决方案14】:

                害怕看到复杂的答案,因为是 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
                }
                

                对于更复杂的场景,请参阅其他答案。
                希望对您有所帮助。

                【讨论】:

                  【解决方案15】:

                  或:

                  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”获胜。

                  【讨论】:

                    【解决方案16】:

                    我会这样做:

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

                    简单易行。根据this blog post,它甚至比大多数循环更快,因为它的底层实现通过索引而不是枚举器(see this answer)访问元素。

                    如果有重复当然会抛出异常,所以你必须在合并之前检查。

                    【讨论】:

                    • 如果重复很重要,请使用dictionaryFrom.ToList().ForEach(x =&gt; dictionaryTo[x.Key] = x.Value)。这样,dictionaryFrom 中的值将覆盖可能存在的键的值。
                    • 这不太可能比循环更快 - 你忘记了 ToList() 实际上最终会复制字典。不过编码速度更快! ;-)
                    • 这样更快。因为 ToList() 实际上并没有复制字典,它只是复制其中元素的引用。基本上列表对象本身在内存中会有一个新地址,对象是一样的!!
                    • 博客帖子消失了,但是是的 Wayback 机器:web.archive.org/web/20150311191313/http://diditwith.net/2006/10/…
                    • 更好 Jon Skeet's answer ? 乔恩·斯基特
                    【解决方案17】:

                    我知道这是一个老问题,但既然我们现在有了 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,而您的第二个示例只是在添加来自mergee 的每个项目后不断丢弃扩展的 IEnumerable> .
                    【解决方案18】:

                    以下内容对我有用。如果有重复,就会使用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]);
                    }
                    

                    【讨论】:

                    • 我必须删除 'where TValue : class' 才能使用 Guid 作为值
                    • @om471987 是的,Guid 是一个结构而不是一个类,所以这是有道理的。我认为最初我没有添加该子句时遇到了一些问题。不记得为什么了。
                    【解决方案19】:

                    @Tim:应该是注释,但是 cmets 不允许进行代码编辑。

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

                    【讨论】:

                    • 接近我自己写的。这,ofc,只适用于Dictionary 类型。可以为其他字典类型(ConcurrentDictionaryReadOnlyDictionary 等)添加重载我不认为创建新列表,并且 concat 个人是必要的。我只是先迭代了 params 数组,然后迭代了每个字典的每个 KVP。我看不出有什么退步。
                    • 你可以用 IDictionary 代替 Dictionary
                    • 即使您在ConcurrentDictionary 上使用了扩展方法,您也将始终获得Dictionary 对象。这可能导致难以追踪错误。
                    • newMap 使用 me 初始化,因此您不需要 Concat —— 您将两次添加 me 的值。
                    【解决方案20】:

                    这是我使用的辅助函数:

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

                    【讨论】:

                    • 这里列出了很多很棒的解决方案,但我最喜欢这个简单的解决方案。只是我个人的喜好。
                    • if(first == null) 那么这个逻辑是没有用的,因为first 不是ref 并且没有返回。并且不能是ref,因为它被声明为接口实例;您需要删除错误检查或将其声明为类实例或只返回一个新字典(没有扩展方法)。
                    • @Grault:空值检查是为了防止无效输入失败。没有什么可以阻止您调用Merge&lt;string,string&gt;(null, null);Dict&lt;int,int&gt; b = null; b.Merge(null); 引用类型参数可以为空,即使它们不是ref。见note on the "Passing an argument by reference" docs
                    【解决方案21】:

                    考虑到performance of dictionary key lookups and deletes,因为它们是哈希运算,并且考虑到问题的措辞是最好的方式,我认为下面是一种完全有效的方法,而其他方法有点过分-复杂,恕我直言。

                        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&lt;T&gt;... 主要是因为如果您使用 IEnumerable&lt;T&gt;,则您已经向同一集合的多个枚举敞开了大门,如果您这样做可能会非常昂贵从延迟的 LINQ 语句中获取您的字典集合。

                    【讨论】:

                    • 为什么使用 Remove() 然后 Add() 而不是使用Item[] property?此外,Dictionary doesn't appear to have 一个采用 KeyValuePair 的 Add 函数:“CS7036 没有给出与 'Dictionary.Add(T1, T2) 的所需形式参数 'value' 相对应的参数” AddOrUpdate来自哪里?
                    【解决方案22】:

                    使用EqualityComparer 进行合并,该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);
                    

                    【讨论】:

                      【解决方案23】:

                      使用扩展方法进行合并。当有重复键时它不会抛出异常,而是将这些键替换为第二个字典中的键。

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

                      【讨论】:

                        【解决方案24】:

                        派对现在几乎已经死了,但这里有一个“改进”版本的 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;
                        }
                        

                        【讨论】:

                          【解决方案25】:

                          根据上面的答案,但添加一个 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));
                          }
                          

                          【讨论】:

                            【解决方案26】:

                            试试下面的

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

                            【讨论】:

                              【解决方案27】:
                              Dictionary<String, String> allTables = new Dictionary<String, String>();
                              allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
                              

                              【讨论】:

                              • 只是想知道 - Union 不就行了吗? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages())) 在生产代码中使用它,如果有任何缺点,请告诉我! :)
                              • @Michal:Union 将返回一个IEnumerable&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;,其中相等性由键和值的相等性定义(存在允许替代方案的重载)。这对于 foreach 循环来说很好,这就是所有必要的,但在某些情况下您实际上需要字典。
                              【解决方案28】:

                              添加一个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 类变得更好。也许这个问题应该成为一个wiki,或者一个签入到git的类......。
                              【解决方案29】:

                              简单的解决方案是:

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

                              【讨论】:

                              • 谢谢。得到我想要的任何东西。
                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 2021-11-28
                              • 2017-08-03
                              • 2013-10-04
                              • 1970-01-01
                              • 2014-10-14
                              • 2011-11-16
                              相关资源
                              最近更新 更多