Сравнение двух словарей для равных данных в C#?
У меня есть два словаря, содержащие строковый ключ, а затем объект. Объект содержит пять полей. Есть ли элегантный способ убедиться, что оба словаря сначала содержат одни и те же ключи, а затем, если это правильно, содержат одни и те же пять полей на объект?
будут ли два словаря иметь одинаковый встроенный хэш-код или что-то еще?
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?
5 ответов
сначала вы должны переопределить Equals
и GetHashCode
метод в вашем классе, в противном случае сравнение будет выполняться по ссылкам вместо фактических значений. (код для переопределения Equals
и GetHashCode
приводится в конце), после этого вы можете использовать:
var result = (dic1 == dic2) || //Reference comparison (if both points to same object)
(dic1.Count == dic2.Count && !dic1.Except(dic2).Any());
С порядок, в котором возвращаются элементы в словаре, не определен, вы не можете положиться на словарь.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 ()
можно использовать
bool dictionariesEqual =
dic1.Keys.Count == dic2.Keys.Count &&
dic1.Keys.All(k => dic2.ContainsKey(k) && object.Equals(dic2[k], dic1[k]));
встроенный на Dictionary<T>
проверяет только равенство ссылок, см. этот вопрос на SO. Хэш-коды не позволяют достоверно определить, равны ли два объекта; всегда есть вероятность столкновения хэшей. Никогда не используйте хэш-коды в качестве теста равенства!
Я бы сделал это вручную: сравните количество записей обоих словарей, повторите пары ключ-значение одного словаря и проверьте, существует ли ключ в другом и сравните соответствующие объекты из обоих словарей. Edit: см. ответ Роулинга:)
здесь было несколько ответов, которые, я думаю, были довольно близки, но было несколько дополнительных моментов, которые, как я думал, должны быть добавлены, поэтому я добавляю их как еще один возможный ответ.
во-первых, я бы избегал использования метода 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's 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's 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);
Итак, это не самый краткий код, но он также не повторяется больше, чем необходимо. На мой взгляд, это более элегантно.
в этом случае вы можете просто использовать метод 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