使用LINQ合并两个字典

28
我的问题被标记为可能是这个问题的重复:如何在C#中不使用循环将两个字典内容组合起来? 我认为我的问题不同,因为我想以特定的方式合并两个字典:我想要Dictionary1中的所有项目加上Dictionary2中所有不在Dictionary1中的项目(即键不存在于Dictionary1中)。
我有两个像这样的字典:
var d1 = new Dictionary<string,object>();
var d2 = new Dictionary<string,object>();

d1["a"] = 1;
d1["b"] = 2;
d1["c"] = 3;

d2["a"] = 11;
d2["e"] = 12;
d2["c"] = 13;

我想将它们合并成一个新的字典(从技术上讲,它不一定要是字典,可以只是一系列的KeyValuePairs)。合并后的输出应包含d1中的所有KeyValuePairs,以及仅包含在d2中而不在d1中出现的键值对。

概念上:

var d3 = d1.Concat(d2.Except(d1))

但这给了我d1和d2中的所有元素。

看起来应该很明显,但我一定漏掉了什么。


1
这个问题不是那个问题的重复。这个问题询问如何合并两个字典d1和d2,使得结果字典包含d1中的所有项以及d2中不在d1中的所有项。另一个问题是询问如何合并两个字典而没有任何额外的条件,并且答案解释了如何实现。 - wageoghe
6个回答

41

使用 Except 时,默认使用默认的相等比较器,对于 KeyValuePair类型,它会比较键和值。你可以尝试使用以下方法:

var d3 = d1.Concat(d2.Where(kvp => !d1.ContainsKey(kvp.Key)));

14
var d3 = d1.Concat(d2.Where(kvp => ! d1.ContainsKey(kvp.Key)))
           .ToDictionary(x => x.Key, x => x.Value);

这对我来说有效。


7

我不确定这是否是LinQ中的新功能,但这就是.Union()的作用:

var d3 = d1.Union(d2);

当然,使用字典时,您需要提供一个自定义的相等比较器,以仅匹配键:
class KeyValuePairComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>
{
    public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
    {
        return x.Key.Equals(y.Key);
    }
    public int GetHashCode(KeyValuePair<TKey, TValue> x)
    {
        return x.GetHashCode();
    }
}

然后:
var d3 = d1.Union(d2, new KeyValuePairComparer<string, object>());

使用您提供的示例,输出结果为(在C#交互式中测试):
> d1.Union(d2, new KeyValuePairComparer<string, object>())
UnionIterator { { "a", 1 }, { "b", 2 }, { "c", 3 }, { "e", 12 } }

请注意以下区别:

> d2.Union(d1, new KeyValuePairComparer<string, object>())
UnionIterator { { "a", 11 }, { "e", 12 }, { "c", 13 }, { "b", 2 } }

2

那真的很酷!我现在可能不会这样做,但会将其存档以备后用。 - wageoghe
有很多很多的Linq扩展。 想起来的话,我会在这种情况下使用ExceptBy:http://code.google.com/p/morelinq/source/browse/trunk/MoreLinq/ExceptBy.cs - DaveShaw

1

您也可以使用自己的IEqualityComparer。以下是示例:

public class MyComparer : IEqualityComparer<KeyValuePair<string,string>> {
    public bool Equals(KeyValuePair<string, string> x, KeyValuePair<string, string> y) {
        return x.Key.Equals(y.Key);
    }

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

...

Dictionary<string, string> d1 = new Dictionary<string, string>();
d1.Add("A", "B");
d1.Add("C", "D");

Dictionary<string, string> d2 = new Dictionary<string, string>();
d2.Add("E", "F");
d2.Add("A", "D");
d2.Add("G", "H");

MyComparer comparer = new MyComparer();

var d3 = d1.Concat(d2.Except(d1, comparer));
foreach (var a in d3) {
    Console.WriteLine("{0}: {1}", a.Key, a.Value);
}

谢谢!这是一种相当酷的方法,就像 Mark 的方法一样。我看到我可以使用 IEqualityComparer,但我想知道是否有更简单的方法。 - wageoghe
通常对于快速的键比较,LINQ可能是最好的选择,但如果您需要更复杂的东西(并且需要重用它),IEqualityComparer可能更好,特别是如果实现为扩展方法。 - bitxwise

1

另一个解决方案是使用自己的 IEqualityComparer,就像 @bitxwise 和 @DaveShaw 的答案中所示,但不使用 Except(),这会使它变得更加简单:

var d3 = d1.Concat(d2).Distinct(new MyComparer());

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