比较两个复杂对象的最佳方法
我有两个复杂的对象,如Object1
和Object2
。 他们有大约5个级别的子对象。
我需要最快的方法来说明他们是否相同。
这怎么可以在C#4.0中完成?
在所有自定义types上实现IEquatable<T>
(通常结合inheritance的Object.Equals
和Object.GetHashCode
方法)。 在复合types的情况下,在包含types中调用包含的types的Equals
方法。 对于包含的集合,请使用SequenceEqual
扩展方法,该方法内部调用每个元素上的IEquatable<T>.Equals
或Object.Equals
。 这种方法显然需要扩展types的定义,但其结果比任何涉及序列化的通用解决scheme都快。
编辑 :这是一个有三层嵌套的人为的例子。
对于值types,通常可以调用它们的Equals
方法。 即使字段或属性从未明确分配,它们仍将具有默认值。
对于引用types,您应该首先调用ReferenceEquals
,它会检查引用是否相等 – 当您碰巧引用同一个对象时,这会提高效率。 它也会处理两个引用都为空的情况。 如果检查失败,请确认您的实例的字段或属性不为空(以避免NullReferenceException
)并调用其Equals
方法。 由于我们的成员是正确的types, IEquatable<T>.Equals
方法被直接调用,绕过重写的Object.Equals
方法(由于types转换它的执行会稍微慢一些)。
当你重载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); } }
序列化两个对象并比较结果string
如果你不想实现IEquatable,你总是可以使用Reflection来比较所有的属性:如果它们是值types,只是比较它们。 如果它们是引用types,则recursion调用该函数以比较其“内部”属性。
我不是在考虑表演,而是在简单。 这取决于你的对象的确切devise。 它可能会变得复杂,取决于你的物体形状。 然而,有几个解决scheme可以使用,就像这样:
- 比较.NET对象
我不知道如果以最快的速度,你的意思是最快的方式来实现它或快速运行的代码。 你不应该在知道你是否需要之前进行优化。 不成熟的优化是万恶之源
你可以使用扩展方法recursion来解决这个问题:
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; }
序列化这两个对象并通过@JoelFan比较结果string
所以要做到这一点,像这样创build一个静态类,并使用扩展来扩展所有的对象(所以你可以传递任何types的对象,集合等方法)
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())
但是,这不是真正的代码在C#:)。 因为你所有的数据都可能在堆上创build,所以内存不是连续的,你不能以不可知的方式比较两个对象的相等性。 您将不得不按照自定义的方式逐个比较每个值。
考虑将IEquatable<T>
接口添加到您的类,并为您的types定义一个自定义Equals
方法。 然后,在那个方法中,手动testing每个值。 如果可以,再次在封闭types上添加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; }
我正在使用它,它对我来说工作正常。
一种方法是覆盖所涉及的每种types的Equals()
。 例如,您的顶级对象将会覆盖Equals()
来调用所有5个子对象的Equals()
方法。 这些对象也应该都覆盖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
IEquatable<T>
接口。
我会这样说:
Object1.Equals(Object2)
将是你在找什么。 那就是如果你想看看对象是否相同,这就是你所要求的。
如果你想检查是否所有的子对象都是相同的,用Equals()
方法通过一个循环来运行它们。
非常简单的方法是:
- 创build你的对象的列表;
- 如果这个列表的交集是“假”,那么它们是相等的。 简单:
-
参考: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()); } }