C#比较两本字典是否相等
我想在C#中比较两个字典,其中as键为C#比较两本字典是否相等,c#,unit-testing,dictionary,C#,Unit Testing,Dictionary,我想在C#中比较两个字典,其中as键为字符串,as值为ints列表。我假设两个字典是相等的,当它们都有相同的键,并且对于每个键,都是一个具有相同整数的列表(两者不一定以相同的顺序) 我使用了来自和相关问题的答案,但都未通过测试函数的测试套件DoesOrderKeysMatter和DoesOrderValuesMatter 我的测试套件: using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.C
字符串
,as值为int
s列表。我假设两个字典是相等的,当它们都有相同的键,并且对于每个键,都是一个具有相同整数的列表(两者不一定以相同的顺序)
我使用了来自和相关问题的答案,但都未通过测试函数的测试套件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));
}
}
}
所以首先我们需要一个字典的相等比较器。它需要确保它们有匹配的键,如果有,则比较每个键的值:
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();
}
}
公共类词典比较程序:
质量比较员
{
私人品质比较师;
公共词典比较程序(IEqualityComparer valueComparer=null)
{
this.valueComparer=valueComparer??EqualityComparer.Default;
}
公共布尔等于(字典x、字典y)
{
如果(x.Count!=y.Count)
返回false;
if(x.Keys.Except(y.Keys.Any())
返回false;
if(y.Keys.Except(x.Keys.Any())
返回false;
foreach(x中的var对)
如果(!valueComparer.Equals(pair.Value,y[pair.Key]))
返回false;
返回true;
}
公共int GetHashCode(字典obj)
{
抛出新的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();
}
}
公共类ListComparer:IEqualityComparer
{
私人品质比较师;
公共列表比较程序(IEqualityComparer valueComparer=null)
{
this.valueComparer=valueComparer??EqualityComparer.Default;
}
公共布尔等于(列表x、列表y)
{
返回x.SetEquals(y,值比较器);
}
public int GetHashCode(列表对象)
{
抛出新的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);
}
public static bool SetEquals(此IEnumerable first,IEnumerable second,
IEqualityComparer(比较器)
{
返回新的HashSet(第二个,comparer??EqualityComparer.Default)
.SetEquals(第一);
}
现在我们可以简单地写下:
new DictionaryComparer<string, List<int>>(new ListComparer<int>())
.Equals(dict1, dict2);
newdictionarycomparer(newlistcomparer())
.等于(第1条、第2条);
我认为AreDictionariesEqual()
只需要另一种方法来比较列表
因此,如果条目顺序无关紧要,您可以尝试以下方法:
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])));
}
static bool listquals(列表L1、列表L2)
{
if(L1.Count!=L2.Count)
返回false;
返回L1.Exception(L2).Count()==0;
}
/*
如果可以更改列表内容,您可以尝试
L1.Sort();
L2.Sort();
返回L1.SequenceEqual(L2);
*/
静态布尔DictEquals(字典D1、字典D2)
{
如果(D1.Count!=D2.Count)
返回false;
返回D1.Keys.All(k=>D2.ContainsKey(k)和&listquals(D1[k],D2[k]);
}
如果条目顺序很重要,请尝试以下方法:
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])));
}
静态bool dictequalorderm(字典D1、字典D2)
{
如果(D1.Count!=D2.Count)
返回false;
//检查键是否相等,而不是列表。
返回(D1.Keys.SequenceEqual(D2.Keys)和&D1.Keys.All(k=>D1[k].SequenceEqual(D2[k]);
}
如果已知两个字典使用了等效的IEqualityComparer
实现,并且其中一个希望将该实现视为等效的所有键视为等效,则它们包含相同数量的项,以及一个(任意选择)将另一个中的所有元素键映射到另一个中的相应值,除非或直到其中一个元素键被修改,否则它们将是等效的。测试这些条件比任何不假设两个词典使用相同的IEqualityComparer
的方法都要快
如果两个词典没有使用相同的IEqualityComparer
实现,则通常不应认为它们是等效的,无论它们包含哪些项。例如,带有区分大小写比较器的字典和带有不区分大小写比较器的字典(两者都包含键值对(“Fred”、“Quimby”)是不等价的,因为后者会将“Fred”映射到“Quimby”,但前者不会
仅当字典使用相同的IEqualityComparer
实现时,但如果人们对比字典使用的更细粒度的键相等定义感兴趣,并且键的副本没有与每个值一起存储,为了测试原始字典的平等性,有必要建立一个新的字典。最好推迟这一步,直到先前的测试表明字典似乎匹配为止。然后构建一个字典
,将其中一个字典中的每个键映射到自身,然后在其中查找其他字典的所有键,以确保它们映射到匹配的内容。如果两个字典都使用不区分大小写的比较器,其中一个包含(“Fred”、“Quimby”)而另一个包含(“Fred”、“Quimby”),新的临时字典会将“Fred”映射到“Fred”,比较这两个字符串会发现字典不匹配。我知道这个问题已经有了公认的答案,但我想提供一个更简单的选择:
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));
}
}
}
使用System.Linq;
使用System.Collections.Generic;
名称空间Foo
{
公共静态类DictionaryExtensionMethods
{
公共静态bool ContentEquals(此字典、字典或其他字典)
{
返回(其他字典??新字典())
.OrderBy(kvp=>kvp.Key)
.SequenceEqual((词典??新词典)
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}'");
}
}
CollectionAssert.AreEqual(
dict1.OrderBy(kv => kv.Key).ToList(),
dict2.OrderBy(kv => kv.Key).ToList()
);
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;
}
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;
}
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());
}
}
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();
}
}
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);
}
[TestMethod]
public void DoesOrderValuesMatter()
{
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>(StringComparer.CurrentCulture);
// more stuff
}