在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让您轻松地处理您想合并项目的方式,如加法,除法,乘法或任何您想要的特定过程。

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

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

其他回答

下面是我使用的一个helper函数:

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

如何添加一个参数重载?

此外,您应该将它们输入为dictionary以获得最大的灵活性。

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

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

我会这样做:

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

简单易行。根据这篇博客文章,它甚至比大多数循环更快,因为它的底层实现通过索引而不是枚举来访问元素(参见这个答案)。

如果存在重复,它当然会抛出异常,因此您必须在合并之前进行检查。