У меня есть два сложных объекта, например Object1
и Object2
. Они имеют около 5 уровней дочерних объектов.
Мне нужен самый быстрый способ сказать, являются ли они одинаковыми или нет.
Как это можно сделать в С# 4.0?
У меня есть два сложных объекта, например Object1
и Object2
. Они имеют около 5 уровней дочерних объектов.
Мне нужен самый быстрый способ сказать, являются ли они одинаковыми или нет.
Как это можно сделать в С# 4.0?
Внедрите IEquatable<T>
(обычно в сочетании с переопределением унаследованных методов Object.Equals
и Object.GetHashCode
) для всех ваших настраиваемых типов. В случае составных типов вызовите метод типов Equals
пределах содержащихся типов. Для содержащихся коллекций используйте метод расширения SequenceEqual
, который внутренне вызывает IEquatable<T>.Equals
или Object.Equals
для каждого элемента. Этот подход, очевидно, потребует от вас расширения типов определений типов, но его результаты быстрее, чем любые общие решения, включающие сериализацию.
Редактировать: Вот надуманный пример с тремя уровнями вложенности.
Для типов значений вы обычно можете просто вызвать свой метод Equals
. Даже если поля или свойства никогда не были явно назначены, они все равно будут иметь значение по умолчанию.
Для ссылочных типов вы должны сначала вызвать ReferenceEquals
, который проверяет ссылочное равенство - это будет служить повышением эффективности, когда вы будете ссылаться на один и тот же объект. Он также будет обрабатывать случаи, когда обе ссылки равны нулю. Если эта проверка NullReferenceException
неудачно, подтвердите, что поле или свойство вашего экземпляра не равно null (чтобы избежать исключения NullReferenceException
) и вызвать его метод Equals
. Поскольку наши члены правильно набраны, метод IEquatable<T>.Equals
напрямую, минуя переопределенный метод Object.Equals
(выполнение которого будет незначительно медленнее из-за типа cast).
Когда вы переопределяете 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>
интерфейса IEquality<T>
, таких как коллекции, которые предполагают, что такие реализации обеспечивают идентификацию. Например, если вы TryGetValue(T,T)
HashSet<T>
, вы обычно ожидаете, что TryGetValue(T,T)
будет возвращать существующие элементы, которые разделяют только личность вашего аргумента, не обязательно эквивалентные элементы, содержимое которых абсолютно одинаково. Это понятие соблюдается примечаниями в GetHashCode
:
В общем случае для изменяемых типов ссылок вы должны переопределить
GetHashCode()
только в том случае, если:
- Вы можете вычислить хэш-код из полей, которые не изменяются; или же
- Вы можете убедиться, что хеш-код изменяемого объекта не изменяется, пока объект содержится в коллекции, которая полагается на свой хэш-код.
Сериализовать оба объекта и сравнить полученные строки
Вы можете использовать метод расширения, рекурсию для решения этой проблемы:
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 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;
}
Если вы не хотите реализовывать IEquatable, вы всегда можете использовать Reflection для сравнения всех свойств: - если они являются типом значения, просто сравните их -if, они являются ссылочным типом, вызовите функцию рекурсивно, чтобы сравнить ее "внутренний" свойства.
Я не думаю о производительности, а о простоте. Это зависит, однако, от точного дизайна ваших объектов. Это может осложниться в зависимости от формы ваших объектов (например, если есть циклические зависимости между свойствами). Однако есть несколько решений, которые вы можете использовать, например:
Другой вариант - сериализация объекта в виде текста, например, с использованием JSON.NET, и сравнение результатов сериализации. (JSON.NET может обрабатывать циклические зависимости между свойствами).
Я не знаю, если вы имеете в виду самый быстрый способ реализовать его или код, который работает быстро. Вы не должны оптимизировать, прежде чем знать, если вам нужно. Преждевременная оптимизация - корень всего зла
Сериализуйте оба объекта и сравните полученные строки с помощью @JoelFan
Чтобы сделать это, создайте такой статический класс и используйте Extensions для расширения ВСЕХ объектов (чтобы вы могли передать любой тип объекта, коллекции и т.д. в метод)
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 для их сравнения. Я использую это для проверки того, находятся ли объекты в коллекциях. Он работает очень хорошо.
Я предполагаю, что вы не имеете в виду буквально те же объекты
Object1 == Object2
Возможно, вы думаете о сравнении памяти между двумя
memcmp(Object1, Object2, sizeof(Object.GetType())
Но это даже не настоящий код в С#:). Поскольку все ваши данные, вероятно, созданы в куче, память не смежна, и вы не можете просто сравнить равенство двух объектов агностически. Вам придется сравнивать каждое значение по одному за раз, по-своему.
Рассмотрим добавление интерфейса 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;
}
Я использую его, и он отлично работает для меня.
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
.
Основываясь на нескольких ответах, уже приведенных здесь, я решил в основном вернуть ответ JoelFan. Мне нравятся методы расширения, и они отлично подходят для меня, когда ни одно из других решений не использует их для сравнения моих сложных классов.
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.");
Благодаря примеру Джонатана. Я расширил его для всех случаев (массивы, списки, словари, примитивные типы).
Это сравнение без сериализации и не требует реализации каких-либо интерфейсов для сравниваемых объектов.
/// <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;
}
Для удобного копирования кода создан репозиторий
Я бы сказал, что:
Object1.Equals(Object2)
будет тем, что вы ищете. Это, если вы хотите посмотреть, совпадают ли объекты, что вы, кажется, задаете.
Если вы хотите проверить, совпадают ли все дочерние объекты, запустите их через цикл с помощью метода Equals()
.
Сериализуйте оба объекта, затем вычислите хэш-код, затем сравните.
Очень простой способ:
Ссылка: using System.Linq;
class Program
{
public class JohnsonObject
{
public int Id { get; set; }
public string Description { get; set; }
}
static void Main()
{
JohnsonObject[] obj01 = new[] { new JohnsonObject { Id = 1, Description = "01" } };
JohnsonObject[] obj02 = new[] { new JohnsonObject { Id = 1, Description = "01" } };
Console.Write("Are equal? = {0}", !obj01.Intersect(obj02).Any());
}
}