Сравните содержимое двух объектов для равенства

У меня есть два комплекса (т. Е. Объекты со строками, int, double, List и другие домашние типы данных) объекты того же типа. Я хотел бы сравнить их содержание и убедиться, что они идентичны. Примечание. Объект не реализует .Equals (у меня нет контроля над этим) и не реализует IComparable.

Существует ли общий способ (reflection?) Для сравнения содержимого двух объектов?

Спасибо!

Существует ли общий способ сравнения содержимого двух объектов?

Ну да, но в целом это называется интерфейсом IComparable.

Если вы могли бы спуститься из classа и создать ребенка, который реализовал IComparable, это может быть идеальным.

Я создал class для глубокого сравнения .NET-объектов. Увидеть:

https://github.com/GregFinzer/Compare-Net-Objects

Мое рабочее решение.!

 private bool Compare(object obj1, object obj2) { if (obj1 == null || obj2 == null) { return false; } if (!obj1.GetType().Equals(obj2.GetType())) { return false; } Type type = obj1.GetType(); if (type.IsPrimitive || typeof(string).Equals(type)) { return obj1.Equals(obj2); } if (type.IsArray) { Array first = obj1 as Array; Array second = obj2 as Array; var en = first.GetEnumerator(); int i = 0; while (en.MoveNext()) { if (!Compare(en.Current, second.GetValue(i))) return false; i++; } } else { foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)) { var val = pi.GetValue(obj1); var tval = pi.GetValue(obj2); if (!Compare(val, tval)) return false; } foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)) { var val = fi.GetValue(obj1); var tval = fi.GetValue(obj2); if (!Compare(val, tval)) return false; } } return true; } 

Надеюсь, поможет.!

Отражение будет именно таким, но проблема заключается в содержащихся в ней типах – например, вы не можете просто использовать Equals или EqualityComparer , так как суб-данные также не будут удобно сопоставимы, если это List т. Д. ,

Как часто вам нужно это делать? Не могли бы вы их сериализовать и сравнить сериализованное значение? Это может быть самый надежный вариант.

GetHashcode работает для меня.

Я переопределяю GetHashcode () в каждом classе со всеми общедоступными свойствами X-OR-ed, например

 override GetHashCode() { return A.GetHashCode() ^ B.GetHashCode ^ C.SafeString().Get.. } 

Я повторяю это через все classы, снова X-OR значения. IsModified сравнивает ранее HashValue с текущим. Два разных объекта действительно могут вернуть один и тот же HashValue с шансом от 1 до 4 миллиардов, но для многих целей это достаточно хорошо для меня.

Но у меня есть еще лучшая идея, используя MemoryStream

здесь есть расширение:

 public static bool IsBinaryEqualTo(this object obj, object obj1) { using (MemoryStream memStream = new MemoryStream()) { if (obj == null || obj1 == null) { if (obj == null && obj1 == null) return true; else return false; } BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); binaryFormatter.Serialize(memStream, obj); byte[] b1 = memStream.ToArray(); memStream.SetLength(0); binaryFormatter.Serialize(memStream, obj1); byte[] b2 = memStream.ToArray(); if (b1.Length != b2.Length) return false; for (int i = 0; i < b1.Length; i++) { if (b1[i] != b2[i]) return false; } return true; } } 

Я только что написал свою версию. Эта функция использует общий и рефлексивный характер. Он работает рекурсивным образом, пока все вещи в объекте уже не сравниваются или не обнаруживают тот, который не равен.

  public static bool CompareObjects(T expectInput, T actualInput) { // If T is primitive type. if (typeof(T).IsPrimitive) { if (expectInput.Equals(actualInput)) { return true; } return false; } if (expectInput is IEquatable) { if (expectInput.Equals(actualInput)) { return true; } return false; } if (expectInput is IComparable) { if (((IComparable)expectInput).CompareTo(actualInput) == 0) { return true; } return false; } // If T is implement IEnumerable. if (expectInput is IEnumerable) { var expectEnumerator = ((IEnumerable)expectInput).GetEnumerator(); var actualEnumerator = ((IEnumerable)actualInput).GetEnumerator(); var canGetExpectMember = expectEnumerator.MoveNext(); var canGetActualMember = actualEnumerator.MoveNext(); while (canGetExpectMember && canGetActualMember && true) { var currentType = expectEnumerator.Current.GetType(); object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(currentType).Invoke(null, new object[] { expectEnumerator.Current, actualEnumerator.Current }); if ((bool)isEqual == false) { return false; } canGetExpectMember = expectEnumerator.MoveNext(); canGetActualMember = actualEnumerator.MoveNext(); } if (canGetExpectMember != canGetActualMember) { return false; } return true; } // If T is class. var properties = typeof(T).GetProperties(); foreach (var property in properties) { var expectValue = typeof(T).GetProperty(property.Name).GetValue(expectInput); var actualValue = typeof(T).GetProperty(property.Name).GetValue(actualInput); if (expectValue == null || actualValue == null) { if (expectValue == null && actualValue == null) { continue; } return false; } object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(property.PropertyType).Invoke(null, new object[] { expectValue, actualValue }); if ((bool)isEqual == false) { return false; } } return true; } 

Сериализуйте объекты в строку XML, и вы можете завершить сравнение строк между двумя объектами, которые сериализованы …

 private string Serialize(T value) { if (value == null) { return string.Empty; } try { XmlSerializer xmlserializer = new XmlSerializer(typeof(T)); StringWriter stringWriter = new StringWriter(); XmlWriter writer = XmlWriter.Create(stringWriter); xmlserializer.Serialize(writer, value); string serializeXml = stringWriter.ToString(); writer.Close(); return serializeXml; } catch (Exception ex) { return string.Empty; } } } 

Для моего последнего проекта я сделал приятный глубокий компилятор с некоторыми функциями.

  public class ObjektHelper { ///  /// Compairs two Objects and gives back true if they are equal ///  ///  /// Object 1 /// Object 2 /// If the list is not mepty, only the field within equal names are compaired. /// If you want not compair some fields enter their name in this list. ///  public static bool DeepCompare(T obj1, T obj2, string[] consideredFieldNames, params string[] notConsideredFieldNames) { var errorList = new List(); if (notConsideredFieldNames == null) notConsideredFieldNames = new[] {""}; DeepCompare(obj1, obj2, errorList, consideredFieldNames, notConsideredFieldNames, false); return errorList.Count < = 0; } ///  /// Compairs two Objects and gives an error list back. ///  ///  ///  ///  /// The error list gives back the names of the fields that are not equal. /// If the list is not mepty, only the field within equal names are compaired. /// If you want not compair some fields enter their name in this list. /// If the value is false, the method end at the first error. public static void DeepCompare(T obj1, T obj2, List errorList, string[] consideredFieldNames, string[] notConsideredFieldNames, bool endWithErrors) { if (errorList == null) throw new Exception("errorListliste ist NULL"); if (Equals(obj1, default(T)) && Equals(obj2, default(T))) return; if (Equals(obj1, default(T)) || Equals(obj2, default(T))) { errorList.Add("One of the object are null!"); return; } if (!endWithErrors && errorList != null && errorList.Count > 0) throw new Exception("ErrorListliste is not empty"); var type1 = obj1.GetType(); var type2 = obj2.GetType(); var propertyInfos1 = type1.GetProperties(); var propertyInfos2 = type2.GetProperties(); // To use the access via index, the list have to be ordered! var propertyInfoOrdered1 = propertyInfos1.OrderBy(p => p.Name).ToArray(); var propertyInfoOrdered2 = propertyInfos2.OrderBy(p => p.Name).ToArray(); if (type1 != type2) errorList.AddRange(new List {type1, type2}); else { for (var i = 0; i < propertyInfos1.Length; i++) { var t1 = propertyInfoOrdered1[i].PropertyType; var t2 = propertyInfoOrdered2[i].PropertyType; if (t1 != t2) { errorList.AddRange(new List {type1, type2}); continue; } var name1 = propertyInfoOrdered1[i].Name; var name2 = propertyInfoOrdered2[i].Name; // Use the next 4 lines to find a bug //if (name1 == "Enter name of field with the bug") // Console.WriteLine(name1); //if (name2 == "Enter name of field1 with the bug" || name2 == "Enter name of field2 with the bug") // Console.WriteLine(name2); if (consideredFieldNames != null && !consideredFieldNames.Contains(name1)) continue; if (notConsideredFieldNames != null && notConsideredFieldNames.Contains(name1)) continue; var value1 = propertyInfoOrdered1[i].GetValue(obj1, null); var value2 = propertyInfoOrdered2[i].GetValue(obj2, null); // check Attributes var guiName1 = (propertyInfoOrdered1[i].GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(GuiNameofModelAttribute)) as GuiNameofModelAttribute)?.GuiName; var guiName2 = (propertyInfoOrdered2[i].GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(GuiNameofModelAttribute)) as GuiNameofModelAttribute)?.GuiName; // create errorListrange var temperrorListRange = new List(); if (guiName1 != null && guiName2 != null) temperrorListRange.AddRange(new List { guiName1, guiName2 }); else temperrorListRange.AddRange(new List { propertyInfoOrdered1[i], propertyInfoOrdered2[i] }); // both fields are null = OK if ((value1 == null && value2 == null) || (value1 is Guid && value2 is Guid)) continue; // one of the fields is null = errorList if (value1 == null || value2 == null) errorList.AddRange(temperrorListRange); // Value types, Enum and String compair else if (t1.BaseType == typeof (ValueType) || t1.BaseType == typeof (Enum) || t1 == typeof (string)) { if (!value1.Equals(value2)) errorList.AddRange(temperrorListRange); } // List, array, generic lists, collection and bindinglist compair else if (t1 == typeof (Array) || (t1.IsGenericType && (t1.GetGenericTypeDefinition() == typeof (List<>) || t1.GetGenericTypeDefinition() == typeof (IList<>) || t1.GetGenericTypeDefinition() == typeof (Collection<>) || t1.GetGenericTypeDefinition() == typeof (ICollection<>) || t1.GetGenericTypeDefinition() == typeof (ObservableCollection<>) || t1.GetGenericTypeDefinition() == typeof (BindingList<>) || t1.GetGenericTypeDefinition() == typeof (BindingList<>) ))) DeepListCompare(value1 as IEnumerable, value2 as IEnumerable, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors); // Clas compair else if (t1.IsClass || t1.IsAnsiClass) DeepCompare(value1, value2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors); else throw new NotImplementedException(); if (!endWithErrors && errorList.Count > 0) break; } } } // End DeepCompare ///  /// Compairs two lists and gives back true if they are equal. ///  ///  /// Generic list 1 /// Generic List 2 ///  public static bool DeepListCompare(T tlist1, T tlist2) { var errorList = new List(); DeepCompare(tlist1, tlist2, errorList, null, null, false); return errorList.Count < = 0; } ///  /// Compairs two lists and gives backthe error list. ///  ///  /// Generic list 1 /// Generic list 2 /// The error list gives back the names of the fields that are not equal. /// If the list is not mepty, only the field within equal names are compaired. /// If you want not compair some fields enter their name in this list. /// If the value is false, the method end at the first error. public static void DeepListCompare(T tlist1, T tlist2, List errorList, string[] consideredFieldNames, string[] notConsideredFieldNames, bool endWithErrors) where T : IEnumerable { if (errorList == null) throw new Exception("errorListliste ist NULL"); if (!endWithErrors && errorList.Count > 0) throw new Exception("errorListliste ist nicht Leer"); if (Equals(tlist1, null) || Equals(tlist2, null)) { errorList.AddRange(new List {tlist1, tlist2}); return; } var type1 = tlist1.GetType(); var type2 = tlist2.GetType(); var propertyInfos1 = type1.GetProperties(); var propertyInfos2 = type2.GetProperties(); // To use the access via index, the list have to be ordered! var propertyInfoOrdered1 = propertyInfos1.OrderBy(p => p.Name).ToArray(); var propertyInfoOrdered2 = propertyInfos2.OrderBy(p => p.Name).ToArray(); for (var i = 0; i < propertyInfos1.Length; i++) { var t1 = propertyInfoOrdered1[i].PropertyType; var t2 = propertyInfoOrdered2[i].PropertyType; if (t1 != t2) errorList.AddRange(new List {t1, t2}); else { // Kick out index if (propertyInfoOrdered1[i].GetIndexParameters().Length != 0) { continue; } // Get value var value1 = propertyInfoOrdered1[i].GetValue(tlist1, null) as IEnumerable; var value2 = propertyInfoOrdered2[i].GetValue(tlist2, null) as IEnumerable; if (value1 == null || value2 == null) continue; // Only run through real lists. if (t1 == typeof (Array) || t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof (List<>) || t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof (Collection<>)) { // cast var objectList1 = value1.ToList(); var objectList2 = value2.ToList(); if (objectList1.Count == 0 && objectList1.Count == 0) { //errorList.AddRange(new List { objectList1, objectList1 }); continue; } foreach (var item1 in objectList1) { foreach (var item2 in objectList2) { var temperrorListCount = errorList.Count; DeepCompare(item1, item2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors); if (temperrorListCount != errorList.Count) continue; objectList2.Remove(item2); break; } if (!endWithErrors && errorList.Count > 0) break; } } else DeepCompare(value1, value2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors); } if (!endWithErrors && errorList.Count > 0) break; } } // End DeepListCompare } // end class ObjectHelper [AttributeUsage(AttributeTargets.All)] public class GuiNameofModelAttribute : Attribute { public readonly string GuiName; public GuiNameofModelAttribute(string guiName) { GuiName = guiName; } } 


Вы можете просто написать метод утилиты в другом classе для сравнения. Однако предполагается, что свойства рассматриваемого classа являются общедоступными. Они?

Спасибо за подход MemoryStream, Марк. Я думал, что была ошибка, когда я увидел «это» в аргументах, но, на удивление, компилятор действительно позволяет вам так поступать, да? Я сделал небольшое изменение и вместо этого решил переопределить Equals (). Престижность также для использования сравнения длины и массива, а не SequenceEquals (). Требуется дополнительная минута для написания, но, согласно http://www.dotnetperls.com/sequenceequal , производительность намного лучше.

 public override bool Equals(object obj) { // If comparison object is null or is a different type, no further comparisons are necessary... if (obj == null || GetType() != obj.GetType()) { return false; } // Compare objects using byte arrays... using (MemoryStream memStream = new MemoryStream()) { BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); // Get byte array of "this" object... binaryFormatter.Serialize(memStream, this); byte[] b1 = memStream.ToArray(); // Get byte array of object to be compared... memStream.SetLength(0); binaryFormatter.Serialize(memStream, obj); byte[] b2 = memStream.ToArray(); // Compare array sizes. If equal, no further comparisons are necessary... if (b1.Length != b2.Length) return false; // If sizes are equal, compare each byte while inequality is not found... for (int i = 0; i < b1.Length; i++) { if (b1[i] != b2[i]) return false; } } return true; } 

Самый быстрый и простой способ, который я нашел, – сериализовать оба объекта с помощью MessagePack, а затем сравнить массивы байтов.

 public static bool DeepEquals(object o1, object o2) { var b1 = MessagePackSerializer.Serialize(o1, ContractlessStandardResolver.Instance); var b2 = MessagePackSerializer.Serialize(o2, ContractlessStandardResolver.Instance); return b1.SequenceEqual(b2); } 

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

Вам нужен метод сравнения чего-то другого; в C ++ вы могли бы просто написать глобальную функцию, но я не думаю, что c # допускает это, как и Java.

Что бы я сделал, это написать class, который реализует iComparable, и имеет ctor, который принимает объект вашего желаемого classа и включает в себя вашу функцию Equals. Установите его так, чтобы все, что он держит, ссылается на исходный объект, чтобы сохранить mallocations.
Тогда вы можете написать

Foo (A) .Equals (новый Foo (B))

Вы могли бы вместо этого наследовать от предоставленного classа, но это означало бы необходимость создавать и отслеживать эти вещи.