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

Bio*_*eek 19 c# dictionary unit-testing

我想在C#中将两个字典与作为键a 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<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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

比较这类词典的正确方法是什么?或者我的(诚然笨拙写的)TestSuite是否有错误?

更新

我正试图在我的测试套件中加入Servy的答案,如下所示,但是我遇到了一些错误(在Visual Studio中用红色摆动线加下划线):

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

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

        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);
        }
    
    Run Code Online (Sandbox Code Playgroud)

    }

Ser*_*rvy 28

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

其中使用以下扩展方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以简单地写:

new DictionaryComparer<string, List<int>>(new ListComparer<int>())
    .Equals(dict1, dict2);
Run Code Online (Sandbox Code Playgroud)


jfr*_*484 18

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

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为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]));

}
Run Code Online (Sandbox Code Playgroud)

如果条目顺序很重要,请尝试以下方法:

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])));         
}
Run Code Online (Sandbox Code Playgroud)


小智 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 } } };
Run Code Online (Sandbox Code Playgroud)

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


小智 5

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

CollectionAssert.AreEqual(
   dict1.OrderBy(kv => kv.Key).ToList(),
   dict2.OrderBy(kv => kv.Key).ToList()
);
Run Code Online (Sandbox Code Playgroud)


小智 5

大多数答案都是多次迭代字典,而它应该很简单:

    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;
    }
Run Code Online (Sandbox Code Playgroud)