Відповіді:
Реалізуйте 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;
}
}
Серіалізуйте обидва об'єкти, потім обчисліть Hash Code, а потім порівняйте.
Я знайшов цю функцію нижче для порівняння об'єктів.
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(), якщо припустити, що вони є власними об'єктами, і так далі, поки всю ієрархію не вдасться порівняти, виконавши перевірку рівності об'єктів верхнього рівня.
Завдяки прикладу Джонатана. Я розширив його на всі випадки (масиви, списки, словники, примітивні типи).
Це порівняння без серіалізації та не вимагає реалізації будь-яких інтерфейсів для порівняних об'єктів.
/// <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].