Сравнение двух словарей для равных данных в С#?

У меня есть два словаря, содержащих строковый ключ, а затем объект. Объект содержит пять полей. Есть ли элегантный способ обеспечить, чтобы оба словаря сначала содержали одни и те же ключи, а затем, если это правильно, содержат те же пять полей для каждого объекта?

Будут ли два словаря иметь один и тот же встроенный хэш-код или что-то еще?

EDIT, похоже, не работает для следующего кода:

Dictionary<string, MyClass> test1 = new Dictionary<string, MyClass>();
Dictionary<string, MyClass> test2 = new Dictionary<string, MyClass>();

MyClass i = new MyClass("", "", 1, 1, 1, 1);
MyClass j = new MyClass("", "", 1, 1, 1, 1);

test1.Add("1", i);
test2.Add("1", j);

bool equal = test1.OrderBy(r => r.Key).SequenceEqual(test2.OrderBy(r => r.Key));

class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a= aa;
        b= bb;
        c= cc;
        d= dd;
        e= ee;
        f= ff;
    }

это возвращает false?

Ответы

Ответ 1

Сначала вы должны переопределить метод Equals и GetHashCode в своем классе, иначе сравнение будет выполняться вместо ссылок на фактические значения. (Код для переопределения Equals и GetHashCode указан в конце), после чего вы можете использовать:

var result = (dic1 == dic2) || //Reference comparison (if both points to same object)
             (dic1.Count == dic2.Count && !dic1.Except(dic2).Any());

Поскольку порядок, в котором возвращаются элементы в словаре, undefined, вы не можете полагаться на Dictionary.SequenceEqual (без OrderBy).

Вы можете попробовать:

Dictionary<string, object> dic1 = new Dictionary<string, object>();
Dictionary<string, object> dic2 = new Dictionary<string, object>();
dic1.Add("Key1", new { Name = "abc", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });

dic2.Add("Key1",new { Name = "abc",Number=  "123", Address= "def", Loc="xyz"});
dic2.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });


bool result = dic1.SequenceEqual(dic2); //Do not use that

В большинстве случаев приведенное выше вернет true, но на самом деле нельзя полагаться на это из-за неупорядоченной природы Dictionary.

Так как SequenceEqual также будет сравнивать порядок, поэтому полагаться только на SequenceEqual может быть неправильно. Вы должны использовать OrderBy для заказа обоих словарей, а затем используйте SequenceEqual как:

bool result2 = dic1.OrderBy(r=>r.Key).SequenceEqual(dic2.OrderBy(r=>r.Key));

Но это потребует нескольких итераций, один раз для упорядочения, а другой для сравнения каждого элемента с помощью SequenceEqual.

Код для переопределения Equals и GetHashCode

private class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a = aa;
        b = bb;
        c = cc;
        d = dd;
        e = ee;
        f = ff;
    }

    protected bool Equals(MyClass other)
    {
        return string.Equals(a, other.a) && string.Equals(b, other.b) && c == other.c && e == other.e && d == other.d && f == other.f;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyClass)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = (a != null ? a.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ (b != null ? b.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ c.GetHashCode();
            hashCode = (hashCode * 397) ^ e.GetHashCode();
            hashCode = (hashCode * 397) ^ d.GetHashCode();
            hashCode = (hashCode * 397) ^ f.GetHashCode();
            return hashCode;
        }
    }
}

Вы также можете увидеть: Правильный способ переопределить Equals() и GetHashCode()

Ответ 2

Вы можете использовать

bool dictionariesEqual = 
    dic1.Keys.Count == dic2.Keys.Count &&
    dic1.Keys.All(k => dic2.ContainsKey(k) && object.Equals(dic2[k], dic1[k]));

Ответ 3

В этом случае вы можете просто использовать метод SequenceEquals(), например следующий:

   Dictionary<string, object> d1 = new Dictionary<string, object>();
   d1.Add("first", new { Name = "TestName", Age = 12, ID = 001 }); 

   Dictionary<string, object> d2 = new Dictionary<string, object>();
   d2.Add("first", new { Name = "TestName", Age = 12, ID = 001 });

   Console.WriteLine(d1.SequenceEqual(d2)); //outputs True                

Примечание. Для простоты я использовал неявные классы для заполнения словарей. Код будет работать одинаково с любыми объектами. Хэш-коды обоих словарей не равны, что можно легко проверить, выполнив следующие действия:

   Console.WriteLine(d1.GetHashCode() + " " + d2.GetHashCode()); //outputs different hashcodes

Ответ 4

Встроенная функция Equals Dictionary<T> проверяет только ссылочное равенство, см. этот вопрос в SO. Хаскоды не надежно говорят вам, если два объекта равны; всегда есть шанс столкновения хэшей. Никогда не используйте хэш-коды в качестве теста на равенство!

Я бы сделал это вручную: сравните количество записей обоих словарей, перебирайте пары ключ-значение одного словаря и проверьте, существует ли ключ в другом, и сравнивайте соответствующие объекты с обоими словарями. Изменить: см. Ответ Rawling:)

Ответ 5

Здесь было несколько ответов, которые, я думаю, были довольно близки, но было несколько дополнительных моментов, которые, как я думал, должны быть добавлены, поэтому я добавляю их в качестве еще одного возможного ответа.

Во-первых, я бы не использовал метод SequenceEquals. Это метод расширения для Enumerable и неявно требует, чтобы две коллекции находились в одном порядке. Словари не предназначены для упорядоченных коллекций, поэтому использование SequenceEquals означает, что вам нужно будет без необходимости перебирать оба словаря для создания отсортированных/упорядоченных коллекций промежуточных элементов, которые вам также не нужны, а затем перебирать эти коллекции для сравнения их для равенства, Это кажется действительно неэффективным и злоупотреблением LINQ, и все это во имя попыток быть кратким и написать однострочное решение. Если идея "элегантности" OP краткая, я предполагаю, что это будет делать трюк, но это кажется расточительным.

С другой стороны, если идея "элегантности" OP эффективна, вам, вероятно, потребуется написать немного больше кода. Во-первых, вы должны либо переопределить метод Equals для вашего класса, либо реализовать IEquatable в вашем классе (см. здесь). Это позволит вам сравнить значения в словаре. Затем вы, вероятно, захотите сделать что-то вроде реализации интерфейса, такого как IEqualityComparer для вашего словаря.

Тогда сравнение двух словарей будет выглядеть примерно так. Это просто быстрый и грязный пример "задней части салфетки", поэтому он не является примером наилучшего способа сделать это, но он призван проиллюстрировать способ повторения только столько раз по словарю, сколько необходимо, и выйти из него, как только найдено неравенство.

Сначала требуется код:

public class Foo
{
    //members here...
    public override bool Equals(object obj)
    {
        //implementation here
    }
    //You should probably also override GetHashCode to be thorough,
    //but that an implementation detail...
}

//This method could stand on its own or you could change it to make it 
//part of the implementation of one of the comparison interfaces...
bool DictionariesEqual(Dictionary<string, Foo> x, Dictionary<string, Foo> y)
{
    //If we're comparing the same object, it obviously equal to itself.
    if(x == y)
    {
        return true;
    }
    //Make sure that we don't have null objects because those are
    //definitely not equal.
    if (x == null || y == null)
    {
        return false;
    }

    //Stop processing if at any point the dictionaries aren't equal.
    bool result = false;

    //Make sure that the dictionaries have the same count.
    result = x.Count == y.Count;

    //If we passed that check, keep going.
    if(result)
    {
        foreach(KeyValuePair<string, Foo> xKvp in x)
        {
            //If we don't have a key from one in the other, even though
            //the counts are the same, the dictionaries aren't equal so
            //we can fail out.
            Foo yValue;
            if(!y.TryGetValue(xKvp.Key, out yValue))
            {
                result = false;
                break;
            }
            else
            {
                //Use the override of the Equals method for your object
                //to see if the value from y is equal to the value from
                //x.
                result = xKvp.Value.Equals(yValue);
                if(!result)
                {
                    //If they're not equal we can just quit out.
                    break;
                }
            }
        }
    }
    return result;
}

Затем мы будем использовать его следующим образом:

Dictionary<string, Foo> dict1 = new Dictionary<string, Foo>();
Dictionary<string, Foo> dict2 = new Dictionary<string, Foo>();
//Fill the dictionaries here...

//Compare the dictionaries
bool areDictsEqual = DictionariesEqual(dict1, dict2);

Итак, это не самый краткий код, но он также не повторяет больше, чем необходимо. На мой взгляд, это более элегантно.