简体   繁体   English

比较两个字典是否相等

[英]Compare two dictionaries for equality

I want to compare in C# two dictionaries with as keys a string and as value a list of int s.我想在 C# 中比较两个字典,其中键是string ,值是int列表。 I assume two dictionaries to be equal when they both have the same keys and for each key as value a list with the same integers (both not necessarily in the same order).我假设两个字典在它们都具有相同的键时是相等的,并且对于每个键作为值,一个具有相同整数的列表(两者不一定以相同的顺序)。

I use both the answers from this and this related question, but both fail my test suite for the test functions DoesOrderKeysMatter and DoesOrderValuesMatter .我使用了这个问题和这个相关问题的答案,但是我的测试套件都没有通过测试函数DoesOrderKeysMatterDoesOrderValuesMatter

My test suite:我的测试套件:

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

What is the correct way to compare these kind of dictionaries?比较这些字典的正确方法是什么? Or is there an error in my (admittedly clumsily written) TestSuite?或者我的(诚然写得很笨拙)TestSuite 中是否有错误?

Update更新

I'm trying to incorporate Servy's answer in my test suite, like below, but I get some errors (underlined with a red wiggly line in Visual Studio):我正在尝试将 Servy 的答案合并到我的测试套件中,如下所示,但我遇到了一些错误(在 Visual Studio 中用红色摆动线下划线):

  • SetEquals in the `Equals method says: "does not contain a definition for SetEquals accepting a first argument of type Generic.List. `Equals 方法中的SetEquals说:“不包含 SetEquals 的定义,它接受 Generic.List 类型的第一个参数。

  • In AreDictionariesEqual it says DictionaryComparer<List> is a type but is used as a variable.`在 AreDictionariesEqual 中it says DictionaryComparer<List> 是一种类型,但用作变量。`

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

So first we need an equality comparer for dictionaries.所以首先我们需要一个字典的相等比较器。 It needs to ensure that they have matching keys and, if they do, compare the values of each 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();
    }
}

but this isn't enough on its own.但这还不够。 We need to compare the values of the dictionary using another custom comparer, not the default comparer as the default list comparer won't look at the values of the list:我们需要使用另一个自定义比较器来比较字典的值,而不是默认比较器,因为默认列表比较器不会查看列表的值:

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();
    }
}

Which uses the following extension method:它使用以下扩展方法:

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

Now we can simply write:现在我们可以简单地写:

new DictionaryComparer<string, List<int>>(new ListComparer<int>())
    .Equals(dict1, dict2);

I know this question already has an accepted answer, but I'd like to offer an even simpler alternative:我知道这个问题已经有一个公认的答案,但我想提供一个更简单的选择:

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

Convert the dictionary to a KeyValuePair list and then compare as collections:将字典转换为KeyValuePair列表,然后作为集合进行比较:

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

I think that AreDictionariesEqual() just needs another method for List comparison我认为AreDictionariesEqual()只需要另一种方法进行列表比较

So if order of entries doesn't matter you can try this:因此,如果条目顺序无关紧要,您可以试试这个:

  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]));

}

And if order of entries matters, try this:如果条目顺序很重要,试试这个:

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])));         
}

The accepted answer above will not always return a correct comparison because using a HashSet to compare 2 lists will not account for duplicate values in the lists.上面接受的答案并不总是返回正确的比较,因为使用 HashSet 比较 2 个列表不会考虑列表中的重复值。 For instance if the OP had:例如,如果 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 } } };

Then the result of the dictionary comparison is they are equal, when they are not.然后字典比较的结果是它们相等,当它们不相等时。 The only solution I see is to sort the 2 list and compare the values by index, but I'm sure someone smarter then me can come up with a more efficient way.我看到的唯一解决方案是对 2 列表进行排序并按索引比较值,但我相信比我更聪明的人可以想出更有效的方法。

If two dictionaries are known to use equivalent implementations of IEqualityComparer , and one wishes to regard as equivalent all keys which that implementation regardss as equivalent, they contain the same number of items, and one (arbitrarily chosen) maps all of the elements keys found in the other to corresponding values from the other, they will be equivalent unless or until one of them is modified.如果已知两个字典使用IEqualityComparer的等效实现,并且希望将该实现视为等效的所有键视为等效,则它们包含相同数量的项目,并且一个(任意选择)映射在中找到的所有元素键另一个与另一个对应的值,除非或直到其中一个被修改,否则它们将是等价的。 Testing for those conditions will be faster than any approach which does not not assume that both dictionaries use the same IEqualityComparer .对这些条件的测试将比任何不假设两个字典都使用相同的IEqualityComparer的方法更快。

If two dictionaries do not use the same implementation of IEqualityComparer , they should generally not be considered equivalent regardless of the items they contain.如果两个字典不使用相同的IEqualityComparer实现,则通常不应将它们视为等效,无论它们包含哪些项目。 For example, a Dictionary<String,String> with a case-sensitive comparer and one with a case-insensitive comparer, both of which contain the key-value pair ("Fred", "Quimby") are not equivalent, since the latter would map "FRED" to "Quimby", but the former would not.例如,带有区分大小写比较器的Dictionary<String,String>和带有不区分大小写比较器的 Dictionary<String,String> 两者都包含键值对 ("Fred", "Quimby") 是不等价的,因为后者会将“FRED”映射到“Quimby”,但前者不会。

Only if the dictionaries use the same implementation of IEqualityComparer , but if one is interested in a finer-grained definition of key-equality than the one used by the dictionaries and a copy of the key is not stored with each value, it will it be necessary to build a new dictionary for the purpose of testing the original dictionaries for equality.仅当字典使用相同的IEqualityComparer实现时,但如果人们对键相等的细粒度定义感兴趣,而不是字典使用的定义,并且键的副本不与每个值一起存储,它将是为了测试原始字典的相等性,有必要建立一个新字典。 It may be best to delay this step until the earlier test has suggested that the dictionaries seem to match.最好延迟此步骤,直到较早的测试表明字典似乎匹配。 Then build a Dictionary<TKey,TKey> which maps each key from one of the dictionaries to itself, and then look up all of the other dictionary's keys in that to make sure that they map to things which match.然后构建一个Dictionary<TKey,TKey> ,它将每个字典中的每个键映射到自身,然后在其中查找所有其他字典的键,以确保它们映射到匹配的内容。 If both dictionaries used case-insensitive comparers, and one contained ("Fred", "Quimby") and the other ("FRED", "Quimby"), the new temporary dictionary would map "FRED" to "Fred", and comparing those two strings would reveal that the dictionaries don't match.如果两个字典都使用不区分大小写的比较器,并且一个包含(“Fred”、“Quimby”)和另一个(“FRED”、“Quimby”),则新的临时字典会将“FRED”映射到“Fred”,并比较这两个字符串会显示字典不匹配。

Here is a way using Linq, probably sacrificing some efficiency for tidy code.这是使用Linq的一种方式,可能会为了整洁的代码而牺牲一些效率。 The other Linq example from jfren484 actually fails the DoesOrderValuesMatter() test, because it depends on the default Equals() for List<int> , which is order-dependent. jfren484中的另一个 Linq 示例实际上未通过 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);
}

Most of the answers are iterating the dictionaries multiple times while it should be simple:大多数答案都是多次迭代字典,而它应该很简单:

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

I like this approach because it gives more details when the test fails我喜欢这种方法,因为它在测试失败时提供了更多细节

    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}'");
        }
    }
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;
    }

Comparing dictionary using string keys is way more complex than what it looks at first glance.使用字符串键比较字典比乍一看要复杂得多。

Dictionary<TKey,TValue> uses an IEqualityComparer<TKey> every time you access an entry in the dictionary to compare your input with the actual entries. Dictionary<TKey,TValue>每次访问字典中的条目时都会使用IEqualityComparer<TKey>来将输入与实际条目进行比较。 The comparer is also used for hash calculations, which serves as some kind of index for faster random access to the entries.比较器也用于哈希计算,它作为某种索引来更快地随机访问条目。 Trying to compare dictionaries with different comparers may have some side effects on key sorting and equality considerations for the key-value pair.尝试使用不同的比较器比较字典可能会对键值对的键排序和相等性考虑产生一些副作用。 The key point here is you need to compare the comparers too when comparing dictionaries.这里的关键点是您在比较字典时也需要比较比较器。

Dictionary<TKey,TValue> also provides collections of keys and values, but they are unsorted. Dictionary<TKey,TValue>还提供键和值的集合,但它们是未排序的。 The keys and values collections are consistent inside the dictionary (the nth key is the nth value's key), but not across instances.字典的键和值集合是一致的(第 n 个键是第 n 个值的键),但实例之间却不一致。 This means we'll have to work with KeyValuePairs<TKey,TValue> and sort them by key on both dictionaries before comparing them.这意味着我们必须使用KeyValuePairs<TKey,TValue>并在比较它们之前按两个字典上的键对它们进行排序。

However, the comparers in the dictionary only check for equality, it's not able to sort the keys.但是,字典中的比较器只检查相等性,它不能对键进行排序 In order to sort pairs, we'll need a new IComparer<TKey> instance, which is another interface than IEqualityComparer<TKey> .为了对对进行排序,我们需要一个新的IComparer<TKey>实例,它是IEqualityComparer<TKey>之外的另一个接口。 But there's a trap here: default implementations of these two interfaces are not consistent.但是这里有个陷阱:这两个接口的默认实现并不一致。 When you create a dictionary using the default contructor, the class will instanciate a GenericEqualityComparer<TKey> if TKey implements IEquatable<TKey> , which require TKey to implement bool Equals(TKey other);当您使用默认构造函数创建字典时,如果 TKey 实现IEquatable<TKey> ,则该类将实例化一个GenericEqualityComparer<TKey> > ,这需要 TKey 实现bool Equals(TKey other); (otherwise, it will fallback to an ObjectEqualityComparer). (否则,它将回退到 ObjectEqualityComparer)。 If you create default Comparer, that will instanciate a GenericComparer<TKey> if TKey implements IComparable<TKey> , which will require TKey to implement int CompareTo(TKey other);如果您创建默认的比较器,如果 TKey 实现IComparable<TKey>将实例化一个GenericComparer<TKey> > ,这将需要 TKey 实现int CompareTo(TKey other); (otherwise it will default to an ObjectComparer). (否则它将默认为 ObjectComparer)。 Not all types implement both interfaces, and those who do sometimes use different implementations.并非所有类型都实现了这两个接口,而且那些实现的类型有时使用不同的实现。 There is a risk that two different keys (according to Equals) are sorted identically (according to CompareTo).存在两个不同的键(根据 Equals)排序相同(根据 CompareTo)的风险。 In that case, there's a risk on the key sorting consitency.在这种情况下,密钥排序一致性存在风险。

Fortunately, string implements both interfaces.幸运的是,字符串实现了这两个接口。 Unfortunately, its implementations are NOT consistent: CompareTo depends on the current culture to sort items, whereas Equals does not !不幸的是,它的实现并不一致:CompareTo 依赖于当前的文化来对项目进行排序,而 Equals 则不然! The solution to this problem is to inject a custom comparer to the dictionary, which provides consistent implementation of both interface.这个问题的解决方案是向字典注入一个自定义比较器,它提供了两个接口的一致实现。 We can use StringComparer for that rather than relying on the default implementation.我们可以使用StringComparer而不是依赖默认实现。 Then we'll simply get the dictionary comparer, cast it, and use it for sorting keys.然后我们将简单地获取字典比较器,对其进行转换,并将其用于对键进行排序。 Also, StringComparer allows comparing the comparers, so we can ensure both dictionaries use the same one.此外,StringComparer 允许比较比较器,因此我们可以确保两个字典使用相同的字典。

First, we need a way to compare the values of the dictionary.首先,我们需要一种方法来比较字典的值。 Since you want to compare lists of int without order, we'll implement an generic equality comparer that sorts items and SequenceEqual them.由于您想比较无顺序的 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());
    }
}

Now, we've got the values covered, but we also need to compare KeyValuePair .现在,我们已经涵盖了值,但我们还需要比较KeyValuePair It is a simple ref struct, so we don't need to check for nulls.它是一个简单的 ref 结构,所以我们不需要检查空值。 We'll simply delegate the comparison to two comparers : one for the key, another for the value.我们将简单地将比较委托给两个比较器:一个用于键,另一个用于值。

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();
    }
}

Now, we can implement the dictionary comparer.现在,我们可以实现字典比较器。 We do null check and compare the dictionaries comparers.我们进行空值检查并比较字典比较器。 Then we consider the dictionary as a simple enumerable of KeyValuePair and SequenceEqual them after sorting them by key.然后我们将字典视为KeyValuePair和 SequenceEqual 的简单可枚举,然后按键对它们进行排序。 For that, we cast the dictionary comparer and delegate the comparison to the KeyValueComparer.为此,我们强制转换字典比较器并将比较委托给 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();
    }
}

Finally, we only need to instanciate comparers and let them do the work.最后,我们只需要实例化比较器并让它们完成工作。

    private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
    {
        return new DictionaryComparer<List<int>>(
            new OrderInsensitiveListComparer<int>())
                .Equals(dict1, dict2);
    }

However, for this to work, we need to use a StringComparer in every dictionary.但是,要使其工作,我们需要在每个字典中使用 StringComparer。

    [TestMethod]
    public void DoesOrderValuesMatter()
    {
        Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>(StringComparer.CurrentCulture);
        // more stuff
    }

The function shown below can be accommodated to perform any generic comparison:下面显示的函数可用于执行任何通用比较:

public bool AreDictionaryEquals(Dictionary<ulong?, string> dictionaryList1, Dictionary<ulong?, string> dictionaryList2)
{

   if (dictionaryList1.Count != dictionaryList2.Count)
      return false;

   IDictionary<ulong?, string> orderedList1 = new Dictionary<ulong?, string>();
   IDictionary<ulong?, string> orderedList2 = new Dictionary<ulong?, string>();

   foreach (var itemDict1 in dictionaryList1.OrderByDescending(key => key.Id))
   {

     orderedList1.Add(itemDict1.Id, itemDict1.PropertyX);

   }

   foreach (var itemDict2 in dictionaryList2.OrderByDescending(key => key.Id))
   {

     orderedList2.Add(itemDict2.Id, itemDict2.PropertyX);

   }

  //check keys and values for equality
  return (orderedList1.Keys.SequenceEqual(orderedList2.Keys) && orderedList1.Keys.All(k => orderedList1[k].SequenceEqual(orderedList2[k])));

}

1- If the length of both dictionaries is not equal we can safely return false. 1- 如果两个字典的长度不相等,我们可以安全地返回 false。

2- Then, we proceed to sort both dictionaries using the value of the keys. 2- 然后,我们继续使用键的值对两个字典进行排序。 The reason for doing this is you could have situations like this:这样做的原因是你可能会遇到这样的情况:

Dictionary A: [1,A], [3,B]字典 A: [1,A], [3,B]

Dictionary B: [3,B], [1,A]字典 B: [3,B], [1,A]

Even though the order is not the same, the content of both can be considered equal.尽管顺序不同,但两者的内容可以认为是相等的。

Finally:最后:

3- We compare both sorted sequences and retrieve the result of this comparison. 3- 我们比较两个排序的序列并检索此比较的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM