比较两个复杂对象的最佳方法
Posted
技术标签:
【中文标题】比较两个复杂对象的最佳方法【英文标题】:Best way to compare two complex objects 【发布时间】:2012-05-14 08:00:55 【问题描述】:我有两个复杂的对象,例如 Object1
和 Object2
。 它们有大约 5 个级别的子对象。
我需要最快的方法来判断它们是否相同。
如何在 C# 4.0 中做到这一点?
【问题讨论】:
见:github.com/GregFinzer/Compare-Net-Objects 【参考方案1】:在所有自定义类型上实现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>
。有两个相等的概念:identity 和 equivalence。在内存表示级别,这些通常被区分为“引用相等”和“值相等”(请参阅Equality Comparisons)。但是,同样的区别也适用于域级别。假设您的 Person
类有一个 PersonId
属性,每个不同的现实世界的人都是唯一的。具有相同PersonId
但不同Age
值的两个对象应该被视为相等还是不同?上面的答案假设一个是在等价之后。但是,IEquality<T>
接口的许多用法(例如集合)假定此类实现提供了 identity。例如,如果您正在填充HashSet<T>
,您通常会期望TryGetValue(T,T)
调用返回仅共享您的参数标识的现有元素,不一定是内容完全相同的等效元素。这个概念由GetHashCode
上的注释强制执行:
一般来说,对于可变引用类型,您应该覆盖
您可以从不可变的字段计算哈希码;或 您可以确保当可变对象包含在依赖于其哈希码的集合中时,该对象的哈希码不会改变。GetHashCode()
,仅在以下情况下:
【讨论】:
我通过 RIA 服务获取此对象...我可以将 IEquatablepartial
- 在这种情况下,是的,您可以通过手动添加的引用字段的部分类声明来实现它们的 Equals
方法/自动生成的属性。
如果“Address address”实际上是“Address[] adresses”,会如何实现呢?
您可以在数组上调用 LINQ Enumerable.SequenceEqual
方法:this.Addresses.SequenceEqual(other.Addresses)
。假设Address
类实现了IEquatable<Address>
接口,这将在内部为每对对应地址调用您的Address.Equals
方法。
开发人员可能检查的另一类比较是“WorksLike”。对我来说,这意味着即使两个实例可能有一些不相等的属性值,程序也会通过处理这两个实例产生相同的结果。【参考方案2】:
序列化两个对象并比较结果字符串
【讨论】:
我不明白为什么会有。序列化通常是一个优化的过程,在任何情况下您都需要访问每个属性的值。 成本很高。您正在生成数据流、附加字符串,然后测试字符串相等性。数量级,仅此而已。更不用说默认情况下序列化将使用反射。 数据流没什么大不了的,我不明白你为什么需要附加字符串......测试字符串相等性是最优化的操作之一......你可能有一个反思点......但总的来说,序列化不会比其他方法差“几个数量级”。如果您怀疑存在性能问题,您应该进行基准测试......我没有遇到过这种方法的性能问题 我是+1
这仅仅是因为我从未想过以这种方式进行基于值的相等比较。很好很简单。使用此代码查看一些基准测试会很好。
这不是一个好的解决方案,因为两种序列化都可能以类似的方式出错。例如,源对象的某些属性可能尚未序列化,并且在反序列化时将在目标对象中设置为 null。在这种情况下,比较字符串的测试会通过,但两个对象实际上并不相同!【参考方案3】:
你可以使用扩展方法,递归来解决这个问题:
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;
【讨论】:
第一个解决方案很棒!我喜欢你不必对对象本身进行 json 序列化或实现添加任何代码。当您只是比较单元测试时适用。如果 objValue 和 anotherValue 都等于 null,我是否建议添加一个简单的比较?这将避免在尝试执行 null.Equals() 时抛出 NullReferenceException // ReSharper 禁用一次 RedundantJumpStatement if (objValue == anotherValue) continue; //空引用保护 else if(!objValue.Equals(anotherValue)) Fail(expected, actual); 有什么理由使用DeepCompare
而不是简单地递归调用CompareEx
?
这可能会不必要地比较整个结构。将result
替换为return false
会提高效率。
这对我正在进行的项目非常有帮助。我必须将 if (!obj.GetType().IsClass) return obj.Equals(another);
添加到 DeepCompare 函数中才能使其正常工作。【参考方案4】:
如果你不想实现 IEquatable,你总是可以使用 Reflection 来比较所有的属性: - 如果它们是值类型,只需比较它们 - 如果它们是引用类型,则递归调用函数以比较其“内部”属性。
我考虑的不是性能,而是简单性。但是,这取决于对象的确切设计。根据您的对象形状,它可能会变得复杂(例如,如果属性之间存在循环依赖关系)。但是,您可以使用多种解决方案,例如:
Compare .NET objects另一种选择是将对象序列化为文本,例如使用 JSON.NET,并比较序列化结果。 (JSON.NET 可以处理属性之间的循环依赖)。
我不知道您所说的最快是指实现它的最快方式还是运行速度快的代码。在知道是否需要之前,您不应该进行优化。 Premature optimization is the root of all evil
【讨论】:
我几乎不认为IEquatable<T>
实现可以算作过早优化。反射将大大减慢。 Equals
自定义值类型的默认实现确实使用反射; Microsoft 自己建议重写它以提高性能:“为特定类型重写 Equals
方法以提高方法的性能并更接近地表示该类型的相等概念。”
这取决于他要运行equals方法多少次:1、10、100、100、一百万?那会有很大的不同。如果他可以使用通用解决方案而不执行任何操作,他将节省一些宝贵的时间。如果它太慢,那么是时候实现 IEquatable(甚至可能尝试制作可缓存或智能的 GetHashCode)至于反射的速度,我必须同意它更慢......或者更慢,这取决于你如何做它(即是否重用 PropertyInfos 类型等)。
@Worthy7 确实如此。请看项目的内容。测试是通过示例记录的好方法。但是,更好的是,如果您查找它,您会找到一个 .chm 帮助文件。所以这个项目比大多数项目有更好的文档。
对不起,你是对的,我完全错过了“wiki”标签。我习惯了每个人都在自述文件中写东西。【参考方案5】:
序列化两个对象并通过@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
中定义的转换的影响。这仅在内部数据主要是字符串和整数时才有效。否则将是一场灾难。
如果一个新导演告诉你放弃 C# 并用 Python 代替它怎么办。作为开发人员,我们需要了解假设问题必须在某个地方停止。解决问题,继续下一步。如果您有时间,请回到它...
Python 在语法和用法上更像 MATLAB。必须有充分的理由从静态类型安全语言转向像 python 这样的混杂脚本。【参考方案6】:
您现在可以使用 json.net。只需继续 Nuget 并安装它。
你可以这样做:
public bool Equals(SamplesItem sampleToCompare)
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
如果你想变得更漂亮,你也许可以为 object 创建一个扩展方法。请注意,这仅比较公共属性。如果您想在进行比较时忽略公共属性,您可以使用 [JsonIgnore]
属性。
【讨论】:
如果你的对象中有列表并且那些有列表,那么试图遍历这两个对象将是一场噩梦。如果您将两者都序列化然后进行比较,则不必处理这种情况。 如果您的复杂对象中有字典,我不相信 .net 序列化程序可以序列化它。 Json 序列化器可以。【参考方案7】:序列化两个对象,然后计算哈希码,然后比较。
【讨论】:
【参考方案8】:我假设您指的不是字面上相同的对象
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;
【讨论】:
【参考方案9】:如果您需要不可变的类。我的意思是,一旦创建,就不能修改任何属性。在这种情况下,C# 9 有一个称为记录的特性。
您可以轻松地按值和类型比较记录是否相等。
public record Person
public string LastName get;
public string FirstName get;
public Person(string first, string last) => (FirstName, LastName) = (first, last);
var person1 = new Person("Bill", "Wagner");
var person2 = new Person("Bill", "Wagner");
Console.WriteLine(person1 == person2); // true
【讨论】:
Answer 似乎做了所问的事情,尽管问题包含“如何在 C# 4.0 中做到这一点?”所以 c# 9 的特性是没有选择的。 @RandRandom 是的,这就是我在回答中提到该版本的原因。因此,如果有人有相同的查询并且他们将来使用相同或更高版本,这个答案将对他们有所帮助。【参考方案10】:我会说:
Object1.Equals(Object2)
将是您正在寻找的。那就是如果您要查看对象是否相同,这就是您要问的问题。
如果要检查所有子对象是否相同,请使用Equals()
方法循环运行它们。
【讨论】:
当且仅当它们提供 Equals 的非引用相等重载。 每个类都必须实现自己的比较方式。如果作者的类没有重写 Equals() 方法,他们将使用 System.Object() 类的基本方法,这将导致逻辑错误。【参考方案11】:我在下面找到了这个用于比较对象的函数。
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
这不是我所期望的。
type.GetProperty(property.Name).GetValue...
应该只是 property.GetValue...
。还缺少type.GetFields()
和适当的绑定。还可以使用Convert.ToString( property.GetValue(object1) )
简化空值检查。【参考方案12】:
根据这里已经给出的一些答案,我决定主要支持JoelFan's answer。我喜欢扩展方法,当没有其他解决方案可以使用它们来比较我的复杂类时,这些方法对我来说非常有用。
扩展方法
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.");
【讨论】:
【参考方案13】:感谢乔纳森的例子。我为所有情况(数组、列表、字典、原始类型)扩展了它。
这是没有序列化的比较,不需要为比较对象实现任何接口。
/// <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;
为了方便复制repository创建的代码
【讨论】:
【参考方案14】: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;
【讨论】:
使用递归原理。【参考方案15】:一种方法是在涉及的每种类型上覆盖Equals()
。例如,您的***对象将覆盖 Equals()
以调用所有 5 个子对象的 Equals()
方法。这些对象也应该全部覆盖Equals()
,假设它们是自定义对象,依此类推,直到可以通过对***对象执行相等检查来比较整个层次结构。
【讨论】:
【参考方案16】:使用IEquatable<T>
接口,该接口具有Equals
方法。
【讨论】:
【参考方案17】:通用扩展方法
public static class GenericExtensions
public static bool DeepCompare<T>(this T objA, T objB)
if (typeof(T).IsValueType)
return objA.Equals(objB);
if (ReferenceEquals(objA, objB))
return true;
if ((objA == null) || (objB == null))
return false;
if (typeof(T) is IEnumerable)
var enumerableA = (IEnumerable<T>) objA;
var enumerableB = (IEnumerable<T>) objB;
if (enumerableA.Count() != enumerableB.Count())
return false;
using (var enumeratorA = enumerableA.GetEnumerator())
using (var enumeratorB = enumerableB.GetEnumerator())
while (true)
bool moveNextA = enumeratorA.MoveNext();
bool moveNextB = enumeratorB.MoveNext();
if (!moveNextA || !moveNextB)
break;
var currentA = enumeratorA.Current;
var currentB = enumeratorB.Current;
if (!currentA.DeepCompare<T>(currentB))
return false;
return true;
foreach (var property in objA.GetType().GetProperties())
var valueA = property.GetValue(objA);
var valueB = property.GetValue(objB);
if (!valueA.DeepCompare(valueB))
return false;
return true;
【讨论】:
以上是关于比较两个复杂对象的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章
Java通过 getter 方法引用,来比较两个对象是否相等
Java通过 getter 方法引用,来比较两个对象是否相等
Java通过 getter 方法引用,来比较两个对象是否相等