在c#中合并2个或更多字典(Dictionary<TKey, TValue>)的最佳方法是什么? (像LINQ这样的3.0特性就可以了)。

我正在考虑一个方法签名,如下所示:

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

or

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

关于重复键的处理:在发生冲突的情况下,保存到字典中的值并不重要,只要它是一致的。


当前回答

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

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

<T, U>(sourceLeft, sourceRight) <T, U>(sourceLeft, sourceRight, Func<U, U, U> 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()部分)

下面是扩展类(右边有两个版本的扩展,接受一个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让您轻松地处理您想合并项目的方式,如加法,除法,乘法或任何您想要的特定过程。

请注意,我还没有测试扩展的集合版本…它们可能仍然需要一些调整。

此外,扩展不修改原来的字典,你必须分配回来,如果你想。

其他回答

我知道这是一个老问题,但是因为我们现在有LINQ,你可以像这样在一行中完成它

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

or

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

试试

namespace Extensions
{
    public static class DictionaryExtensions
    {
        public static Dictionary<T, Y> MergeWith<T, Y>(this Dictionary<T, Y> dictA,
            Dictionary<T, Y> dictB)
        {
            foreach (var item in dictB)
            {
                if (dictA.ContainsKey(item.Key))
                    dictA[item.Key] = item.Value;
                else
                    dictA.Add(item.Key, item.Value);
            }
            return dictA;
        }
    }
}

当你想合并两个字典时

    var d1 = new Dictionary<string, string>();
    var d2 = new Dictionary<string, string>();
    d1.MergeWith(d2);

下面的方法对我有用。如果存在重复项,则使用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]);
}

我将分解@orip的简单而非垃圾的创建解决方案,以提供除了Merge()之外的一个适当的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;
}

对于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
}

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