C#比较两个字典是否相等

我想在C#中将两个字典与string作为键进行比较,并将值作为int的列表进行比较。 我假设两个字典在它们具有相同的键时是相等的,并且对于每个键而言,它们是具有相同整数的列表(两者不一定是相同的顺序)。

我使用了这个和这个相关问题的答案,但是我的测试套件都没有通过测试函数DoesOrderKeysMatterDoesOrderValuesMatter

我的测试套件:

 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> dict1 = new Dictionary<string, List>(); List list1 = new List(); list1.Add(1); list1.Add(2); dict1.Add("a", list1); List list2 = new List(); 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> dict1 = new Dictionary<string, List>(); List list1 = new List(); list1.Add(1); list1.Add(2); dict1.Add("a", list1); List list2 = new List(); list2.Add(3); list2.Add(4); dict1.Add("b", list2); Dictionary<string, List> dict2 = new Dictionary<string, List>(); // act bool dictsAreEqual = true; dictsAreEqual = AreDictionariesEqual(dict1, dict2); // assert Assert.IsFalse(dictsAreEqual, "Dictionaries are equal"); } [TestMethod] public void DoesOrderKeysMatter() { // arrange Dictionary<string, List> dict1 = new Dictionary<string, List>(); List list1 = new List(); list1.Add(1); list1.Add(2); dict1.Add("a", list1); List list2 = new List(); list2.Add(3); list2.Add(4); dict1.Add("b", list2); Dictionary<string, List> dict2 = new Dictionary<string, List>(); List list3 = new List(); list3.Add(3); list3.Add(4); dict2.Add("b", list3); List list4 = new List(); 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> dict1 = new Dictionary<string, List>(); List list1 = new List(); list1.Add(1); list1.Add(2); dict1.Add("a", list1); List list2 = new List(); list2.Add(3); list2.Add(4); dict1.Add("b", list2); Dictionary<string, List> dict2 = new Dictionary<string, List>(); List list3 = new List(); list3.Add(2); list3.Add(1); dict2.Add("a", list3); List list4 = new List(); 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> dict1, Dictionary<string, List> 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中用红色摇摆线加下划线):

  • `Equals方法中的SetEquals说:“不包含接受Generic.List类型的第一个参数的SetEquals的定义。
  • 在AreDictionariesEqual中, it says DictionaryComparer>是一个类型,但用作变量

    namespace UnitTestProject1 {[TestClass] public class ProvideReportTests {[TestMethod] // …与上面相同

      private bool AreDictionariesEqual(Dictionary<string, List> dict1, Dictionary<string, List> dict2) { DictionaryComparer<string, List>(new ListComparer() dc = new DictionaryComparer<string, List>(new ListComparer(); return dc.Equals(dict1, dict2); } } public class DictionaryComparer : IEqualityComparer<Dictionary> { private IEqualityComparer valueComparer; public DictionaryComparer(IEqualityComparer valueComparer = null) { this.valueComparer = valueComparer ?? EqualityComparer.Default; } public bool Equals(Dictionary x, Dictionary 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 obj) { throw new NotImplementedException(); } } public class ListComparer : IEqualityComparer<List> { private IEqualityComparer valueComparer; public ListComparer(IEqualityComparer valueComparer = null) { this.valueComparer = valueComparer ?? EqualityComparer.Default; } public bool Equals(List x, List y) { return x.SetEquals(y, valueComparer); } public int GetHashCode(List obj) { throw new NotImplementedException(); } } public static bool SetEquals(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) { return new HashSet(second, comparer ?? EqualityComparer.Default) .SetEquals(first); } 

    }

首先,我们需要一个字典的相等比较器。 它需要确保它们具有匹配的键,如果是,则比较每个键的值:

 public class DictionaryComparer : IEqualityComparer> { private IEqualityComparer valueComparer; public DictionaryComparer(IEqualityComparer valueComparer = null) { this.valueComparer = valueComparer ?? EqualityComparer.Default; } public bool Equals(Dictionary x, Dictionary 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 obj) { throw new NotImplementedException(); } } 

但这本身还不够。 我们需要使用另一个自定义比较器来比较字典的值,而不是默认的比较器,因为默认列表比较器不会查看列表的值:

 public class ListComparer : IEqualityComparer> { private IEqualityComparer valueComparer; public ListComparer(IEqualityComparer valueComparer = null) { this.valueComparer = valueComparer ?? EqualityComparer.Default; } public bool Equals(List x, List y) { return x.SetEquals(y, valueComparer); } public int GetHashCode(List obj) { throw new NotImplementedException(); } } 

其中使用以下扩展方法:

 public static bool SetEquals(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) { return new HashSet(second, comparer ?? EqualityComparer.Default) .SetEquals(first); } 

现在我们可以简单地写:

 new DictionaryComparer>(new ListComparer()) .Equals(dict1, dict2); 

我知道这个问题已经有了一个公认的答案,但我想提供一个更简单的替代方案:

 using System.Linq; using System.Collections.Generic; namespace Foo { public static class DictionaryExtensionMethods { public static bool ContentEquals(this Dictionary dictionary, Dictionary otherDictionary) { return (otherDictionary ?? new Dictionary()) .OrderBy(kvp => kvp.Key) .SequenceEqual((dictionary ?? new Dictionary()) .OrderBy(kvp => kvp.Key)); } } } 

我认为AreDictionariesEqual()只需要另一种List比较方法

因此,如果条目顺序无关紧要,您可以尝试这样做:

  static bool ListEquals(List L1, List 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> D1, Dictionary> 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> D1, Dictionary> 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]))); } 

上面接受的答案并不总是返回正确的比较,因为使用HashSet比较2个列表将不会考虑列表中的重复值。 例如,如果OP有:

 var dict1 = new Dictionary>() { { "A", new List() { 1, 2, 1 } } }; var dict2 = new Dictionary>() { { "A", new List() { 2, 2, 1 } } }; 

然后字典比较的结果是它们是相等的,当它们不相同时。 我看到的唯一解决方案是对2列表进行排序并按索引比较值,但我确信有人比我聪明,可以提出更有效的方法。

如果已知两个字典使用IEqualityComparer等效实现,并且希望将该实现视为等效的所有键视为等效,则它们包含相同数量的项,并且一个(任意选择)映射所有元素键中的所有元素。另一个来自另一个的相应值,它们将是等效的,除非或直到其中一个被修改。 对这些条件的测试将比任何不假设两个字典都使用相同的IEqualityComparer方法更快。

如果两个词典不使用相同的IEqualityComparer实现,则通常不应将它们视为等效,无论它们包含IEqualityComparer项目。 例如,带有区分大小写的比较器的Dictionary和带有区分大小写的比较器的Dictionary ,两者都包含键值对(“Fred”,“Quimby”)不等效,因为后者将“FRED”映射到“Quimby”,但前者不会。

只有当字典使用相同的IEqualityComparer实现时,如果一个人对关键字相等的更细粒度的定义感兴趣而不是字典使用的那个,并且密钥的副本没有与每个值一起存储,那么它将是必须建立一个新的字典,以测试原始字典的平等性。 最好延迟此步骤,直到早期测试表明字典似乎匹配为止。 然后构建一个Dictionary ,它将每个键从一个词典映射到它自己,然后在其中查找所有其他词典的键,以确保它们映射到匹配的东西。 如果两个字典都使用不区分大小写的比较器,并且一个包含(“Fred”,“Quimby”)和另一个(“FRED”,“Quimby”),则新的临时字典会将“FRED”映射到“Fred”,并进行比较这两个字符串会显示字典不匹配。

将字典转换为KeyValuePair列表,然后比较为集合:

 CollectionAssert.AreEqual( dict1.OrderBy(kv => kv.Key).ToList(), dict2.OrderBy(kv => kv.Key).ToList() ) 

这是一种使用Linq的方法,可能会牺牲一些效率来整理代码。 来自jfren484 的另一个Linq示例实际上未通过DoesOrderValuesMatter()测试,因为它依赖于List的默认Equals(),它依赖于顺序。

 private bool AreDictionariesEqual(Dictionary> dict1, Dictionary> 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); } 

我喜欢这种方法,因为它在测试失败时提供了更多细节

  public void AssertSameDictionary(Dictionary expected,Dictionary actual) { string d1 = "expected"; string d2 = "actual"; Dictionary.KeyCollection keys1= expected.Keys; Dictionary.KeyCollection keys2= actual.Keys; if (actual.Keys.Count > expected.Keys.Count) { string tmp = d1; d1 = d2; d2 = tmp; Dictionary.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}'"); } } 
 public static IDictionary 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 ToFlattenDictionary(this object source, string parentPropertyKey = null, IDictionary parentPropertyValue = null) { var propsDic = parentPropertyValue ?? new Dictionary(); 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; }