У меня есть два сложных объекта вроде Object1и Object2. У них есть около 5 уровней дочерних объектов.
Мне нужен самый быстрый способ сказать, совпадают они или нет.
Как это можно было сделать в C # 4.0?
У меня есть два сложных объекта вроде Object1и Object2. У них есть около 5 уровней дочерних объектов.
Мне нужен самый быстрый способ сказать, совпадают они или нет.
Как это можно было сделать в C # 4.0?
Ответы:
Реализовать IEquatable<T>( как правило , в сочетании с переопределение унаследованных Object.Equalsи Object.GetHashCodeметоды) на всех пользовательских типов. В случае составных типов вызовите метод содержащихся типов Equalsвнутри содержащих типы. Для содержащихся коллекций используйте SequenceEqualметод расширения, который внутренне вызывает IEquatable<T>.Equalsили Object.Equalsдля каждого элемента. Этот подход, очевидно, потребует от вас расширения определений ваших типов, но его результаты быстрее, чем любые общие решения, включающие сериализацию.
Изменить : вот надуманный пример с тремя уровнями вложенности.
Для типов значений вы обычно можете просто вызвать их Equalsметод. Даже если поля или свойства никогда не были явно назначены, они все равно будут иметь значение по умолчанию.
Для ссылочных типов вы должны сначала вызвать ReferenceEquals, который проверяет ссылочное равенство - это послужит повышению эффективности, когда вы случайно ссылаетесь на один и тот же объект. Он также будет обрабатывать случаи, когда обе ссылки равны нулю. Если эта проверка не удалась, убедитесь, что поле или свойство вашего экземпляра не равно нулю (во избежание NullReferenceException) и вызовите его Equalsметод. Поскольку наши члены набраны правильно, IEquatable<T>.Equalsметод вызывается напрямую, минуя переопределенныйObject.Equals метод (выполнение которого будет немного медленнее из-за приведения типа).
Когда вы переопределяете Object.Equals, вы также должны переопределить Object.GetHashCode; Я не делал этого ниже для краткости.
public class Person : IEquatable<Person>
{
public int Age { get; set; }
public string FirstName { get; set; }
public Address Address { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Person);
}
public bool Equals(Person other)
{
if (other == null)
return false;
return this.Age.Equals(other.Age) &&
(
object.ReferenceEquals(this.FirstName, other.FirstName) ||
this.FirstName != null &&
this.FirstName.Equals(other.FirstName)
) &&
(
object.ReferenceEquals(this.Address, other.Address) ||
this.Address != null &&
this.Address.Equals(other.Address)
);
}
}
public class Address : IEquatable<Address>
{
public int HouseNo { get; set; }
public string Street { get; set; }
public City City { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Address);
}
public bool Equals(Address other)
{
if (other == null)
return false;
return this.HouseNo.Equals(other.HouseNo) &&
(
object.ReferenceEquals(this.Street, other.Street) ||
this.Street != null &&
this.Street.Equals(other.Street)
) &&
(
object.ReferenceEquals(this.City, other.City) ||
this.City != null &&
this.City.Equals(other.City)
);
}
}
public class City : IEquatable<City>
{
public string Name { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as City);
}
public bool Equals(City other)
{
if (other == null)
return false;
return
object.ReferenceEquals(this.Name, other.Name) ||
this.Name != null &&
this.Name.Equals(other.Name);
}
}
Обновление : этот ответ был написан несколько лет назад. С тех пор я начал отказываться от реализации IEquality<T>изменяемых типов для таких сценариев. Есть два понятия равенства: тождество и эквивалентность . На уровне представления памяти их обычно различают как «эталонное равенство» и «равенство значений» (см. Сравнение равенства ). Однако такое же различие может применяться и на уровне домена. Предположим, что у вашего Personкласса есть PersonIdсвойство, уникальное для конкретного человека в реальном мире. Следует ли считать два объекта с одинаковыми, PersonIdно разными Ageзначениями одинаковыми или разными? В приведенном выше ответе предполагается, что требуется эквивалентность. Однако есть много вариантов использованияIEquality<T> интерфейсом, например коллекции, предполагают, что такие реализации обеспечивают идентификацию . Например, если вы заполняетеHashSet<T>, вы обычно ожидаете, что TryGetValue(T,T)вызов вернет существующие элементы, которые разделяют только идентичность вашего аргумента, не обязательно эквивалентные элементы, содержимое которых полностью одинаково. Это понятие подтверждается примечаниями к GetHashCode:
В общем, для изменяемых ссылочных типов следует переопределить,
GetHashCode()только если:
- Вы можете вычислить хэш-код из неизменяемых полей; или
- Вы можете гарантировать, что хэш-код изменяемого объекта не изменится, пока объект содержится в коллекции, которая полагается на его хэш-код.
partial- и в этом случае, да, вы можете реализовать их Equalsметод через добавленное вручную объявление частичного класса, которое ссылается на поля / свойства из автоматически сгенерированного один.
Enumerable.SequenceEqualметод на массивы: this.Addresses.SequenceEqual(other.Addresses). Это внутренне вызовет ваш Address.Equalsметод для каждой пары соответствующих адресов, учитывая, что Addressкласс реализует IEquatable<Address>интерфейс.
Сериализуйте оба объекта и сравните полученные строки
+1этим просто потому, что никогда не думал о проведении подобного сравнения равенства на основе значений. Это красиво и просто. Было бы неплохо увидеть тесты с этим кодом.
Вы можете использовать метод расширения, рекурсию, чтобы решить эту проблему:
public static bool DeepCompare(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
//Compare two object's class, return false if they are difference
if (obj.GetType() != another.GetType()) return false;
var result = true;
//Get all properties of obj
//And compare each other
foreach (var property in obj.GetType().GetProperties())
{
var objValue = property.GetValue(obj);
var anotherValue = property.GetValue(another);
if (!objValue.Equals(anotherValue)) result = false;
}
return result;
}
public static bool CompareEx(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
if (obj.GetType() != another.GetType()) return false;
//properties: int, double, DateTime, etc, not class
if (!obj.GetType().IsClass) return obj.Equals(another);
var result = true;
foreach (var property in obj.GetType().GetProperties())
{
var objValue = property.GetValue(obj);
var anotherValue = property.GetValue(another);
//Recursion
if (!objValue.DeepCompare(anotherValue)) result = false;
}
return result;
}
или сравните с помощью Json (если объект очень сложный) Вы можете использовать Newtonsoft.Json:
public static bool JsonCompare(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
if (obj.GetType() != another.GetType()) return false;
var objJson = JsonConvert.SerializeObject(obj);
var anotherJson = JsonConvert.SerializeObject(another);
return objJson == anotherJson;
}
DeepCompareвместо простого CompareExрекурсивного вызова ?
resultна return falseсделает его более эффективным.
Если вы не хотите реализовывать IEquatable, вы всегда можете использовать Reflection для сравнения всех свойств: - если они тип значения, просто сравните их - если они ссылочного типа, вызовите функцию рекурсивно, чтобы сравнить ее «внутренние» свойства .
Я думаю не о производительности, а о простоте. Однако это зависит от точного дизайна ваших объектов. Это может усложниться в зависимости от формы ваших объектов (например, если есть циклические зависимости между свойствами). Однако есть несколько решений, которые вы можете использовать, например это:
Другой вариант - сериализовать объект как текст, например, с помощью JSON.NET, и сравнить результат сериализации. (JSON.NET может обрабатывать циклические зависимости между свойствами).
Я не знаю, подразумеваете ли вы под «самым быстрым» самый быстрый способ его реализации или код, который работает быстро. Вы не должны оптимизировать, пока не узнаете, нужно ли вам это. Преждевременная оптимизация - корень всех зол
IEquatable<T>реализация подпадает под преждевременную оптимизацию. Отражение будет значительно медленнее. Реализация по умолчанию Equalsдля настраиваемых типов значений действительно использует отражение; Сами Microsoft рекомендуют переопределить его для повышения производительности: «Переопределите Equalsметод для определенного типа, чтобы повысить производительность метода и более точно представить концепцию равенства для типа».
Сериализуйте оба объекта и сравните полученные строки с помощью @JoelFan
Поэтому для этого создайте статический класс, подобный этому, и используйте расширения для расширения ВСЕХ объектов (чтобы вы могли передать в метод любой тип объекта, коллекции и т. Д.)
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
public static class MySerializer
{
public static string Serialize(this object obj)
{
var serializer = new DataContractJsonSerializer(obj.GetType());
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
return Encoding.Default.GetString(ms.ToArray());
}
}
}
После ссылки на этот статический класс в любом другом файле вы можете сделать это:
Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();
Теперь вы можете просто использовать .Equals для их сравнения. Я использую это для проверки, есть ли объекты в коллекциях. Это действительно хорошо работает.
CultrureInfo. Это будет работать, только если внутренние данные в основном представляют собой строки и целые числа. Иначе была бы катастрофа.
Я предполагаю, что вы имеете в виду не буквально одни и те же объекты
Object1 == Object2
Возможно, вы думаете о сравнении памяти между двумя
memcmp(Object1, Object2, sizeof(Object.GetType())
Но это даже не настоящий код на C # :). Поскольку все ваши данные, вероятно, созданы в куче, память не является непрерывной, и вы не можете просто сравнивать равенство двух объектов агностическим способом. Вам нужно будет сравнивать каждое значение по отдельности индивидуальным способом.
Подумайте о добавлении IEquatable<T>интерфейса в свой класс и определите собственный Equalsметод для вашего типа. Затем в этом методе вручную проверьте каждое значение. Если можете, добавьте еще IEquatable<T>раз вложенные типы и повторите процесс.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Сериализуйте оба объекта, затем вычислите хэш-код и сравните.
Я нашел эту функцию ниже для сравнения объектов.
static bool Compare<T>(T Object1, T object2)
{
//Get the type of the object
Type type = typeof(T);
//return false if any of the object is false
if (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
return false;
//Loop through each properties inside class and get values for the property from both the objects and compare
foreach (System.Reflection.PropertyInfo property in type.GetProperties())
{
if (property.Name != "ExtensionData")
{
string Object1Value = string.Empty;
string Object2Value = string.Empty;
if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
if (type.GetProperty(property.Name).GetValue(object2, null) != null)
Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
if (Object1Value.Trim() != Object2Value.Trim())
{
return false;
}
}
}
return true;
}
Я использую его, и он у меня работает нормально.
ifозначает то, Compare(null, null) == falseчего я не ожидал.
Основываясь на нескольких уже приведенных здесь ответах, я решил в основном поддержать ответ Джоэла Фана . Мне нравятся методы расширения, и они отлично работают для меня, когда ни одно из других решений не использовало бы их для сравнения моих сложных классов.
using System.IO;
using System.Xml.Serialization;
static class ObjectHelpers
{
public static string SerializeObject<T>(this T toSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, toSerialize);
return textWriter.ToString();
}
}
public static bool EqualTo(this object obj, object toCompare)
{
if (obj.SerializeObject() == toCompare.SerializeObject())
return true;
else
return false;
}
public static bool IsBlank<T>(this T obj) where T: new()
{
T blank = new T();
T newObj = ((T)obj);
if (newObj.SerializeObject() == blank.SerializeObject())
return true;
else
return false;
}
}
if (record.IsBlank())
throw new Exception("Record found is blank.");
if (record.EqualTo(new record()))
throw new Exception("Record found is blank.");
Я бы сказал, что:
Object1.Equals(Object2)
будет то, что вы ищете. Это если вы хотите проверить, одинаковы ли объекты, о чем вы, кажется, и спрашиваете.
Если вы хотите проверить, все ли дочерние объекты одинаковы, запустите их в цикле с этим Equals()методом.
public class GetObjectsComparison
{
public object FirstObject, SecondObject;
public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
public FieldInfo SecondObjectFieldInfo;
public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
public bool ErrorFound;
public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
GetObjectsComparison FunctionGet = GetObjectsComparison;
SetObjectsComparison FunctionSet = new SetObjectsComparison();
if (FunctionSet.ErrorFound==false)
foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
{
FunctionSet.SecondObjectFieldInfo =
FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);
FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
if (FirstObjectFieldInfo.FieldType.IsNested)
{
FunctionSet.GetObjectsComparison =
new GetObjectsComparison()
{
FirstObject = FunctionSet.FirstObjectFieldInfoValue
,
SecondObject = FunctionSet.SecondObjectFieldInfoValue
};
if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
{
FunctionSet.ErrorFound = true;
break;
}
}
else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
{
FunctionSet.ErrorFound = true;
break;
}
}
return !FunctionSet.ErrorFound;
}
Один из способов сделать это - переопределить Equals()каждый задействованный тип. Например, ваш объект верхнего уровня переопределит Equals()вызов Equals()метода всех 5 дочерних объектов. Все эти объекты также должны переопределяться Equals(), предполагая, что они являются настраиваемыми объектами, и так далее до тех пор, пока всю иерархию можно будет сравнить, просто выполнив проверку равенства для объектов верхнего уровня.
Используйте IEquatable<T>интерфейс, у которого есть метод Equals.
Благодаря примеру Джонатана. Я расширил его на все случаи (массивы, списки, словари, примитивные типы).
Это сравнение без сериализации и не требует реализации каких-либо интерфейсов для сравниваемых объектов.
/// <summary>Returns description of difference or empty value if equal</summary>
public static string Compare(object obj1, object obj2, string path = "")
{
string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
if (obj1 == null && obj2 != null)
return path1 + "null != not null";
else if (obj2 == null && obj1 != null)
return path1 + "not null != null";
else if (obj1 == null && obj2 == null)
return null;
if (!obj1.GetType().Equals(obj2.GetType()))
return "different types: " + obj1.GetType() + " and " + obj2.GetType();
Type type = obj1.GetType();
if (path == "")
path = type.Name;
if (type.IsPrimitive || typeof(string).Equals(type))
{
if (!obj1.Equals(obj2))
return path1 + "'" + obj1 + "' != '" + obj2 + "'";
return null;
}
if (type.IsArray)
{
Array first = obj1 as Array;
Array second = obj2 as Array;
if (first.Length != second.Length)
return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";
var en = first.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
string res = Compare(en.Current, second.GetValue(i), path);
if (res != null)
return res + " (Index " + i + ")";
i++;
}
}
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
{
System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;
var en = first.GetEnumerator();
var en2 = second.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
if (!en2.MoveNext())
return path + ": enumerable size differs";
string res = Compare(en.Current, en2.Current, path);
if (res != null)
return res + " (Index " + i + ")";
i++;
}
}
else
{
foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
try
{
var val = pi.GetValue(obj1);
var tval = pi.GetValue(obj2);
if (path.EndsWith("." + pi.Name))
return null;
var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
string res = Compare(val, tval, pathNew);
if (res != null)
return res;
}
catch (TargetParameterCountException)
{
//index property
}
}
foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
var val = fi.GetValue(obj1);
var tval = fi.GetValue(obj2);
if (path.EndsWith("." + fi.Name))
return null;
var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
string res = Compare(val, tval, pathNew);
if (res != null)
return res;
}
}
return null;
}
Для удобного копирования кода создан репозиторий
Теперь вы можете использовать json.net. Просто зайдите на Nuget и установите его.
А можно сделать примерно так:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Возможно, вы могли бы создать метод расширения для объекта, если хотите стать более привлекательным. Обратите внимание: сравниваются только общедоступные объекты. А если вы хотите игнорировать публичное свойство при сравнении, вы можете использовать атрибут [JsonIgnore].