C#合并2个词典

时间:2010-10-25 13:50:54

标签: c# dictionary linq-to-objects

我正在开发一个面向.NET 3.5的C#应用​​程序。在其中,我有2个类似的词典,其中包含我的应用程序中特定元素集的验证标准。两个词典都有相同的签名。第一个字典具有默认设置,第二个字典包含一些用户定义的设置。

var default_settings = new Dictionary<string, MyElementSettings>();
var custom_settings = new Dictionary<string, MyElementSettings>();

我想将2个词典合并为一个包含两个词典元素的词典。

我遇到的问题是两个词典都可能具有一些相同的键值。我想要的基本规则是将两个字典组合在一起,如果default_settings中已经存在custom_settings中的任何键,则custom_settings值将覆盖default_settings值。我拥有的最佳解决方案只是一个foreach循环,检查其他字典中是否存在密钥,如果不存在,则添加它。

foreach (var item in custom_settings)
{
    if (default_settings.ContainsKey(item.Key))
        default_settings[item.Key] = item.Value;
    else
        default_settings.Add(item.Key, item.Value);
}

我已经完成了一些基本的LINQ查询,但我仍在努力学习更高级的东西。我已经看到了一些将合并2个字典的查询,但大多数都涉及将任何元素与重复键分组,或者仅返回仅包含重复键的集合/是否存在将模仿foreach循环行为的LINQ查询或表达式我在用?

5 个答案:

答案 0 :(得分:41)

两点:

  1. LINQ不适合执行副作用。在这种情况下,您试图改变现有的集合而不是执行查询,所以我会回避纯粹的LINQ解决方案。
  2. 如果密钥不存在,setter on the generic dictionary's indexer已经具有添加键值对的效果,或者如果密钥值不存在则覆盖该值。
  3.   

    设置属性值时,如果   关键是在Dictionary中,与之关联的值   该密钥被分配的替换   值。如果钥匙不在   字典,关键和   值被添加到字典中。

    所以你的foreach循环基本上等同于:

    foreach (var item in custom_settings)
    {
       default_settings[item.Key] = item.Value;
    }
    

    现在已经非常简洁了,所以我认为LINQ不会那么帮助你。

答案 1 :(得分:8)

这是一个很好的基于Ani答案的扩展方法。

public static class DictionaryExtensionMethods
{
    public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge)
    {
        foreach (var item in merge)
        {
            me[item.Key] = item.Value;
        }
    }
}

答案 2 :(得分:2)

如果你要这么做,那么我建议为字典键编写一个相等比较器:

private class KeyEqualityComparer<T, U> : IEqualityComparer<KeyValuePair<T, U>>
{
    public bool Equals(KeyValuePair<T, U> x, KeyValuePair<T, U> y)
    {
        return x.Key.Equals(y.Key);
    }

    public int GetHashCode(KeyValuePair<T, U> obj)
    {
        return obj.Key.GetHashCode();
    }
}

然后,只要您需要合并字典,就可以执行以下操作

var comparer = new KeyEqualityComparer<string, MyElementSettings>();
dict1 = dict1.Union(dict2,comparer).ToDictionary(a => a.Key, b => b.Value);

答案 3 :(得分:1)

我认为我最初选择的答案仍然是这个特定案例的最佳答案,我发现自己处于另一个类似的情况,不久前我有2个IEnumerable<>个对象,我想转换为字典和以类似的方式合并在一起所以我想我会在这里添加该解决方案以帮助将来的某个人。我没有将两者都转换成字典并使用所选答案中的方法,而是找到了一种新的方法。

我实际上在SE-CodeReview上发布了initial solution,实际上有一个建议可以进一步完善它。这是我使用的最终代码:

public Dictionary<String, Foo> Merge(XElement element1, XElement element2)
{
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML

    var result = firstFoos.Union(secondFoos).ToDictionary(k=>k.Name, v=>v);

    return result;
}

public class Foo
{
    public String Name { get; }

    // other Properties and Methods
    // .
    // .
    // .

    public override Boolean Equals(Object obj)
    {
        if (obj is Foo)
        {
            return this.Name == ((Foo)obj).Name;            
        }

        return false;
    }
}

关键是Foo必须覆盖Equals()来定义哪些Foo对象可以被认为是重复的,定义哪些对象重复的成员也应该是{{1}密钥(在本例中为Dictionary<>

如果您无法覆盖Name中的Equals(),则另一个选项是使用FooConcat()代替GroupBy()

Union()

答案 4 :(得分:0)

此版本创建一个新的完整词典,而不是修改一个词典,并将当前词典与提供的词典合并。

/// <summary>
/// Merges the current dictionary with the supplied dictionary into a new dictionary.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="current"></param>
/// <param name="keyValuePairs"></param>
/// <returns></returns>
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> current, IDictionary<TKey, TValue> keyValuePairs)
{
    var dictionaryMerge = new Dictionary<TKey, TValue>(current);
    foreach (var item in keyValuePairs)
    {
        dictionaryMerge[item.Key] = item.Value;
    }
    return dictionaryMerge;
}