比较两个字典是不是相等
Posted
技术标签:
【中文标题】比较两个字典是不是相等【英文标题】:Compare two dictionaries for equality比较两个字典是否相等 【发布时间】:2021-09-14 19:13:07 【问题描述】:我想在 C# 中比较两个字典,其中键为 string
,值列表为 int
s。我假设两个字典在它们都具有相同的键时是相等的,并且对于每个键作为值的列表具有相同的整数(两者不一定以相同的顺序)。
我同时使用了来自this 和this 相关问题的答案,但是对于DoesOrderKeysMatter
和DoesOrderValuesMatter
的测试功能,我的测试套件都失败了。
我的测试套件:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
namespace UnitTestProject1
[TestClass]
public class ProvideReportTests
[TestMethod]
public void AreSameDictionariesEqual()
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict1);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
[TestMethod]
public void AreDifferentDictionariesNotEqual()
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
// act
bool dictsAreEqual = true;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsFalse(dictsAreEqual, "Dictionaries are equal");
[TestMethod]
public void DoesOrderKeysMatter()
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(3);
list3.Add(4);
dict2.Add("b", list3);
List<int> list4 = new List<int>();
list4.Add(1);
list4.Add(2);
dict2.Add("a", list4);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
[TestMethod]
public void DoesOrderValuesMatter()
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(2);
list3.Add(1);
dict2.Add("a", list3);
List<int> list4 = new List<int>();
list4.Add(4);
list4.Add(3);
dict2.Add("b", list4);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
return dict1.Keys.Count == dict2.Keys.Count &&
dict1.Keys.All(k => dict2.ContainsKey(k) && object.Equals(dict2[k], dict1[k]));
// also fails:
// return dict1.OrderBy(kvp => kvp.Key).SequenceEqual(dict2.OrderBy(kvp => kvp.Key));
比较这些字典的正确方法是什么?还是我的(诚然写得很笨拙)TestSuite 有错误?
更新
我正在尝试将 Servy 的答案合并到我的测试套件中,如下所示,但我遇到了一些错误(在 Visual Studio 中用红色摆动线下划线):
SetEquals
在 `Equals 方法中说:“不包含接受 Generic.List 类型的第一个参数的 SetEquals 的定义。
在 AreDictionariesEqualit says
DictionaryComparer 中是一个类型,但用作变量。`
namespace UnitTestProject1
[TestClass]
public class ProvideReportTests
[TestMethod]
// ... same as above
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
DictionaryComparer<string, List<int>>(new ListComparer<int>() dc = new DictionaryComparer<string, List<int>>(new ListComparer<int>();
return dc.Equals(dict1, dict2);
public class DictionaryComparer<TKey, TValue> :
IEqualityComparer<Dictionary<TKey, TValue>>
private IEqualityComparer<TValue> valueComparer;
public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
if (x.Count != y.Count)
return false;
if (x.Keys.Except(y.Keys).Any())
return false;
if (y.Keys.Except(x.Keys).Any())
return false;
foreach (var pair in x)
if (!valueComparer.Equals(pair.Value, y[pair.Key]))
return false;
return true;
public int GetHashCode(Dictionary<TKey, TValue> obj)
throw new NotImplementedException();
public class ListComparer<T> : IEqualityComparer<List<T>>
private IEqualityComparer<T> valueComparer;
public ListComparer(IEqualityComparer<T> valueComparer = null)
this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
public bool Equals(List<T> x, List<T> y)
return x.SetEquals(y, valueComparer);
public int GetHashCode(List<T> obj)
throw new NotImplementedException();
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer)
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
【问题讨论】:
object.Equals(dict2[k], dict1[k])
按引用比较列表。不同的列表实例有不同的引用。列表中的项目无关紧要
您的AreDictionariesEqual
可能想使用Enumerable.SequenceEqual
@AakashM 那将取决于订单。它需要与订单无关。
@Servy 啊当然,我想我和CollectionAssert.AreEquivalent
混淆了
【参考方案1】:
所以首先我们需要一个用于字典的相等比较器。它需要确保它们具有匹配的键,如果匹配,则比较每个键的值:
public class DictionaryComparer<TKey, TValue> :
IEqualityComparer<Dictionary<TKey, TValue>>
private IEqualityComparer<TValue> valueComparer;
public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
if (x.Count != y.Count)
return false;
if (x.Keys.Except(y.Keys).Any())
return false;
if (y.Keys.Except(x.Keys).Any())
return false;
foreach (var pair in x)
if (!valueComparer.Equals(pair.Value, y[pair.Key]))
return false;
return true;
public int GetHashCode(Dictionary<TKey, TValue> obj)
throw new NotImplementedException();
但这还不够。我们需要使用另一个自定义比较器来比较字典的值,而不是默认比较器,因为默认列表比较器不会查看列表的值:
public class ListComparer<T> : IEqualityComparer<List<T>>
private IEqualityComparer<T> valueComparer;
public ListComparer(IEqualityComparer<T> valueComparer = null)
this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
public bool Equals(List<T> x, List<T> y)
return x.SetEquals(y, valueComparer);
public int GetHashCode(List<T> obj)
throw new NotImplementedException();
其中使用以下扩展方法:
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second,
IEqualityComparer<T> comparer)
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
现在我们可以简单地写:
new DictionaryComparer<string, List<int>>(new ListComparer<int>())
.Equals(dict1, dict2);
【讨论】:
感谢您的广泛回答!我在将它合并到我的 TestSuite 时遇到了一些麻烦,你能看看我的更新吗?谢谢! @BioGeek 扩展方法需要在静态类中。该类可能不是静态的。您创建的比较器刚刚结束。您将new ListComparer<int>()
卡在了比较器类型的定义中,而这应该是构造函数的参数。【参考方案2】:
我知道这个问题已经有一个公认的答案,但我想提供一个更简单的替代方案:
using System.Linq;
using System.Collections.Generic;
namespace Foo
public static class DictionaryExtensionMethods
public static bool ContentEquals<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, Dictionary<TKey, TValue> otherDictionary)
return (otherDictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key)
.SequenceEqual((dictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key));
【讨论】:
我想知道这是否可以通过首先检查dictionary.Count
是否等于otherDictionary.Count
来进一步改进。可能会加快执行速度。
@KyleFalconer 如果速度将成为一个问题(如果这将在数千次调用或类似的循环中执行),那么是的,首先检查 Count 会很好主意。不过,它会添加更多代码行,并使其更复杂一些,所以如果它不打算在紧密循环中执行,我不会这样做。
不错的答案。您需要添加using System.Linq;
才能编译。
你还需要using System.Collections.Generic;
我认为人们会弄清楚这些。 :)
我非常喜欢这种简单的东西。如果预期用途是单元测试,则使用相等比较器似乎有点过头了。【参考方案3】:
将字典转换为KeyValuePair
列表,然后作为集合进行比较:
CollectionAssert.AreEqual(
dict1.OrderBy(kv => kv.Key).ToList(),
dict2.OrderBy(kv => kv.Key).ToList()
);
【讨论】:
【参考方案4】:我认为AreDictionariesEqual()
只是需要另一种List比较方法
所以如果条目的顺序无关紧要,你可以试试这个:
static bool ListEquals(List<int> L1, List<int> L2)
if (L1.Count != L2.Count)
return false;
return L1.Except(L2).Count() == 0;
/*
if it is ok to change List content you may try
L1.Sort();
L2.Sort();
return L1.SequenceEqual(L2);
*/
static bool DictEquals(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
if (D1.Count != D2.Count)
return false;
return D1.Keys.All(k => D2.ContainsKey(k) && ListEquals(D1[k],D2[k]));
如果条目顺序很重要,试试这个:
static bool DictEqualsOrderM(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
if (D1.Count != D2.Count)
return false;
//check keys for equality, than lists.
return (D1.Keys.SequenceEqual(D2.Keys) && D1.Keys.All(k => D1[k].SequenceEqual(D2[k])));
【讨论】:
【参考方案5】:上面接受的答案并不总是返回正确的比较,因为 使用 HashSet 比较 2 个列表不会考虑列表中的重复值。 例如,如果 OP 有:
var dict1 = new Dictionary<string, List<int>>() "A", new List<int>() 1, 2, 1 ;
var dict2 = new Dictionary<string, List<int>>() "A", new List<int>() 2, 2, 1 ;
然后字典比较的结果是它们相等,当它们不相等时。我看到的唯一解决方案是对 2 列表进行排序并按索引比较值,但我相信比我更聪明的人可以想出更有效的方法。
【讨论】:
【参考方案6】:如果已知两个字典使用IEqualityComparer
的等效实现,并且希望将该实现视为等效的所有键视为等效,则它们包含相同数量的项目,并且一个(任意选择)映射所有在另一个中找到的元素键对应于另一个中的值,除非或直到其中一个被修改,否则它们将是等效的。对这些条件的测试将比任何不假设两个字典都使用相同的IEqualityComparer
的方法更快。
如果两个字典不使用IEqualityComparer
的相同实现,则无论它们包含哪些项目,它们通常不应被视为等效。例如,带有区分大小写比较器的Dictionary<String,String>
和带有不区分大小写比较器的Dictionary<String,String>
,两者都包含键值对(“Fred”、“Quimby”)是不等价的,因为后者将映射“ FRED”到“Quimby”,但前者不会。
仅当字典使用IEqualityComparer
的相同实现时,但如果人们对比字典使用的更细粒度的键相等定义感兴趣并且键的副本不与每个值一起存储,有必要建立一个新的字典来测试原始字典的相等性。最好延迟此步骤,直到较早的测试表明字典似乎匹配。然后构建一个Dictionary<TKey,TKey>
,它将每个字典中的每个键映射到自身,然后在其中查找所有其他字典的键,以确保它们映射到匹配的内容。如果两个字典都使用不区分大小写的比较器,并且一个包含(“Fred”、“Quimby”)和另一个(“FRED”、“Quimby”),则新的临时字典会将“FRED”映射到“Fred”,并比较这两个字符串会显示字典不匹配。
【讨论】:
【参考方案7】:这里是使用 Linq 的一种方式,可能为了整洁的代码牺牲了一些效率。来自jfren484 的The other Linq example 实际上没有通过DoesOrderValuesMatter() 测试,因为它依赖于List<int>
的默认Equals(),这是依赖于顺序的。
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
string dict1string = String.Join(",", dict1.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
string dict2string = String.Join(",", dict2.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
return dict1string.Equals(dict2string);
【讨论】:
【参考方案8】:大多数答案都是多次迭代字典,而它应该很简单:
static bool AreEqual(IDictionary<string, string> thisItems, IDictionary<string, string> otherItems)
if (thisItems.Count != otherItems.Count)
return false;
var thisKeys = thisItems.Keys;
foreach (var key in thisKeys)
if (!(otherItems.TryGetValue(key, out var value) &&
string.Equals(thisItems[key], value, StringComparison.OrdinalIgnoreCase)))
return false;
return true;
【讨论】:
【参考方案9】:我喜欢这种方法,因为它会在测试失败时提供更多详细信息
public void AssertSameDictionary<TKey,TValue>(Dictionary<TKey,TValue> expected,Dictionary<TKey,TValue> actual)
string d1 = "expected";
string d2 = "actual";
Dictionary<TKey,TValue>.KeyCollection keys1= expected.Keys;
Dictionary<TKey,TValue>.KeyCollection keys2= actual.Keys;
if (actual.Keys.Count > expected.Keys.Count)
string tmp = d1;
d1 = d2;
d2 = tmp;
Dictionary<TKey, TValue>.KeyCollection tmpkeys = keys1;
keys1 = keys2;
keys2 = tmpkeys;
foreach(TKey key in keys1)
Assert.IsTrue(keys2.Contains(key), $"key 'key' of d1 dict was not found in d2");
foreach (TKey key in expected.Keys)
//already ensured they both have the same keys
Assert.AreEqual(expected[key], actual[key], $"for key 'key'");
【讨论】:
【参考方案10】:public static IDictionary<string, object> ToDictionary(this object source)
var fields = source.GetType().GetFields(
BindingFlags.GetField |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source) ?? string.Empty
);
var properties = source.GetType().GetProperties(
BindingFlags.GetField |
BindingFlags.GetProperty |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source, null) ?? string.Empty
);
return fields.Concat(properties).ToDictionary(key => key.Key, value => value.Value); ;
public static bool EqualsByValue(this object source, object destination)
var firstDic = source.ToFlattenDictionary();
var secondDic = destination.ToFlattenDictionary();
if (firstDic.Count != secondDic.Count)
return false;
if (firstDic.Keys.Except(secondDic.Keys).Any())
return false;
if (secondDic.Keys.Except(firstDic.Keys).Any())
return false;
return firstDic.All(pair =>
pair.Value.ToString().Equals(secondDic[pair.Key].ToString())
);
public static bool IsAnonymousType(this object instance)
if (instance == null)
return false;
return instance.GetType().Namespace == null;
public static IDictionary<string, object> ToFlattenDictionary(this object source, string parentPropertyKey = null, IDictionary<string, object> parentPropertyValue = null)
var propsDic = parentPropertyValue ?? new Dictionary<string, object>();
foreach (var item in source.ToDictionary())
var key = string.IsNullOrEmpty(parentPropertyKey) ? item.Key : $"parentPropertyKey.item.Key";
if (item.Value.IsAnonymousType())
return item.Value.ToFlattenDictionary(key, propsDic);
else
propsDic.Add(key, item.Value);
return propsDic;
【讨论】:
【参考方案11】:使用字符串键比较字典比乍一看要复杂得多。
Dictionary<TKey,TValue>
每次访问字典中的条目时都会使用IEqualityComparer<TKey>
来将输入与实际条目进行比较。比较器也用于散列计算,它作为某种索引来更快地随机访问条目。尝试使用不同的比较器比较字典可能会对键值对的键排序和相等性考虑产生一些副作用。 这里的重点是在比较字典时也需要比较比较器。
Dictionary<TKey,TValue>
还提供键和值的集合,但它们是未排序的。键和值集合在内部 字典中是一致的(第n 个键是第n 个值的键),但在跨 个实例之间却不是一致的。 这意味着我们必须使用KeyValuePairs<TKey,TValue>
并在比较它们之前在两个字典中按键排序。
然而,字典中的比较器只检查相等性,它不能排序键。为了对对进行排序,我们需要一个新的IComparer<TKey>
实例,它是IEqualityComparer<TKey>
之外的另一个接口。但是这里有个陷阱:这两个接口的默认实现并不一致。当您使用默认构造函数创建字典时,如果 TKey 实现 IEquatable<TKey>
,则该类将实例化 GenericEqualityComparer<TKey>
,这需要 TKey 实现 bool Equals(TKey other);
(否则,它将回退到 ObjectEqualityComparer)。如果您创建默认的比较器,如果 TKey 实现 IComparable<TKey>
,则将实例化 GenericComparer<TKey>
,这将要求 TKey 实现 int CompareTo(TKey other);
(否则它将默认为 ObjectComparer)。并非所有类型都实现了这两个接口,而且那些实现的类型有时使用不同的实现。存在两个不同的键(根据 Equals)排序相同(根据 CompareTo)的风险。 在这种情况下,密钥排序一致性存在风险。
幸运的是,string 实现了这两个接口。不幸的是,它的实现是不一致的:CompareTo 依赖于当前的文化来对项目进行排序,而 Equals 没有! 解决这个问题的方法是向字典中注入一个自定义比较器,它提供了两个接口的一致实现。我们可以使用StringComparer
,而不是依赖默认实现。然后我们将简单地获取字典比较器,对其进行转换,并将其用于对键进行排序。此外,StringComparer 允许比较比较器,因此我们可以确保两个字典使用相同的。
首先,我们需要一种方法来比较字典的值。由于您要比较无顺序的 int 列表,我们将实现一个通用的相等比较器,对项目进行排序并对其进行 SequenceEqual。
internal class OrderInsensitiveListComparer<TValue>
: IEqualityComparer<IEnumerable<TValue>>
private readonly IComparer<TValue> comparer;
public OrderInsensitiveListComparer(IComparer<TValue> comparer = null)
this.comparer = comparer ?? Comparer<TValue>.Default;
public bool Equals([AllowNull] IEnumerable<TValue> x, [AllowNull] IEnumerable<TValue> y)
return x != null
&& y != null
&& Enumerable.SequenceEqual(
x.OrderBy(value => value, comparer),
y.OrderBy(value => value, comparer));
public int GetHashCode([DisallowNull] IEnumerable<TValue> obj)
return obj.Aggregate(17, (hash, item) => hash * 23 ^ item.GetHashCode());
现在,我们已经涵盖了这些值,但我们还需要比较 KeyValuePair
。它是一个简单的 ref 结构,所以我们不需要检查空值。我们将简单地将比较委托给两个比较器:一个用于键,另一个用于值。
internal class KeyValuePairComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>
private readonly IEqualityComparer<TKey> key;
private readonly IEqualityComparer<TValue> value;
public KeyValuePairComparer(
IEqualityComparer<TKey> key = null,
IEqualityComparer<TValue> value = null)
this.key = key ?? EqualityComparer<TKey>.Default;
this.value = value ?? EqualityComparer<TValue>.Default;
public bool Equals([AllowNull] KeyValuePair<TKey, TValue> x, [AllowNull] KeyValuePair<TKey, TValue> y)
// KeyValuePair is a struct, you can't null check
return key.Equals(x.Key, y.Key) && value.Equals(x.Value, y.Value);
public int GetHashCode([DisallowNull] KeyValuePair<TKey, TValue> obj)
return 17 * 23 ^ obj.Key.GetHashCode() * 23 ^ obj.Value.GetHashCode();
现在,我们可以实现字典比较器了。我们进行空值检查并比较字典比较器。然后我们将字典视为KeyValuePair
的简单枚举,并在按键排序后对它们进行SequenceEqual。为此,我们强制转换字典比较器并将比较委托给 KeyValueComparer。
internal class DictionaryComparer<TValue> : IEqualityComparer<Dictionary<string, TValue>>
private readonly IEqualityComparer<TValue> comparer;
public DictionaryComparer(
IEqualityComparer<TValue> comparer = null)
this.comparer = comparer ?? EqualityComparer<TValue>.Default;
public bool Equals([AllowNull] Dictionary<string, TValue> x, [AllowNull] Dictionary<string, TValue> y)
return x != null
&& y != null
&& Equals(x.Comparer, y.Comparer)
&& x.Comparer is StringComparer sorter
&& Enumerable.SequenceEqual(
x.AsEnumerable().OrderBy(pair => pair.Key, sorter),
y.AsEnumerable().OrderBy(pair => pair.Key, sorter),
new KeyValuePairComparer<string, TValue>(x.Comparer, comparer));
public int GetHashCode([DisallowNull] Dictionary<string, TValue> obj)
return new OrderInsensitiveListComparer<KeyValuePair<string, TValue>>()
.GetHashCode(obj.AsEnumerable()) * 23 ^ obj.Comparer.GetHashCode();
最后,我们只需要实例化比较器并让它们完成工作。
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
return new DictionaryComparer<List<int>>(
new OrderInsensitiveListComparer<int>())
.Equals(dict1, dict2);
但是,要使其工作,我们需要在每个字典中使用 StringComparer。
[TestMethod]
public void DoesOrderValuesMatter()
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>(StringComparer.CurrentCulture);
// more stuff
【讨论】:
以上是关于比较两个字典是不是相等的主要内容,如果未能解决你的问题,请参考以下文章