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

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


当前回答

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

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

其他回答

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

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

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

@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的解决方案的修改,所以mergleft现在看起来像这样:

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

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

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

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

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

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

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

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