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

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


当前回答

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

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

}

其他回答

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

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

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

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

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

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

我将分解@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;
}

这在一定程度上取决于如果遇到重复项,你希望发生什么。例如,你可以这样做:

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

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