C# 合并两个字典

25

我正在使用C#开发一个针对.NET 3.5的应用程序。其中,我有两个类似的字典,它们包含了特定一组元素的验证标准。这两个字典具有相同的签名。第一个字典具有默认设置,而第二个字典包含了一些用户定义的设置。

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

我想将这两个字典合并成一个,包含两个字典的元素。

我的问题是,两个字典中可能有相同的键值。基本规则是将两个字典合并,并且如果自定义设置中已经存在于默认设置中的任何键,则自定义设置的值将覆盖默认设置的值。我最好的解决方案只是使用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个字典的查询,但大多数涉及将具有重复键的任何元素分组,或仅返回只包含重复键的集合。是否有一个LINQ查询或表达式可以模仿我正在使用的foreach循环的行为?

5个回答

47

有两点需要注意:

  1. LINQ 不适合执行副作用操作。在这种情况下,您尝试修改现有集合而不是执行查询,因此我会避免使用纯 LINQ 的解决方案。
  2. 泛型字典的索引器上的 设置器 已经具有添加键值对(如果不存在该键)或覆盖值(如果存在该键)的效果。

当您设置属性值时,如果键已存在于字典中,则与该键相关联的值将被新分配的值替换。 如果键不存在于字典中,则会将键和值添加到字典中。

因此,您的 foreach 循环本质上相当于:

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

现在已经很简洁了,所以我认为LINQ并不能帮助你太多。


3
谢谢你教我一些我不知道的东西。我对C#相对自学,从未知道你可以用字典做到那个。 - psubsee2003
@psubsee2003:干杯。我相当确定在发现它是多余的之前,我使用了与您的代码相同的模式(偶然间,我正在使用反射器进行探索)。 - Ani
您也可以使用 TryGetValue 方法来检查对象是否已经存在于字典中。 - Matthieu H

12

这是一个基于 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;
        }
    }
}

1
我会把Dictionary改为IDictionary。 - Mr. Pumpkin
如果这是一个字典集合 IEnumerable<Dictionary<TKey, TValue>>,如何进行匹配? - barteloma

4
如果您需要经常这样做,我建议为字典键编写一个相等比较器:
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);

这个对我有效,甚至没有使用KeyEqualityComparer类/参数。谢谢。 - AceMark

1

我认为我最初选择的答案仍然是这种情况下最好的答案,我最近遇到了另一个类似的情况,我有2个 IEnumerable<> 对象,我想将它们转换为字典并以类似的方式合并在一起,所以我想在这里添加那个解决方案以帮助未来的某个人。与将两者都转换为字典并使用所选答案中的方法不同,我找到了一种新的方法。

我实际上在SE-CodeReview上发布了初始解决方案,并得到了进一步完善的建议。这是我使用的最终代码:

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 对象可以被视为重复,而定义哪些对象是重复的成员也应该是 Dictionary<> 的键(在这种情况下是 Name)。
如果无法在 Foo 中重写 Equals(),则另一个选项是使用 Concat()GroupBy() 代替 Union()
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.Concat(secondFoos)
                          .GroupBy(foo => foo.Name)
                          .Select(grp => grp.First())
                          .ToDictionary(k=>k.Name, v=>v);

    return result;
}

1
这个版本创建了一个全新的字典,而不是修改一个已有的字典,并将当前字典与提供的字典合并。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接