我有两个复杂的对象,例如 Object1
和 Object2
。 他们有大约5个级别的子对象。
我需要用最快的方法来说明它们是否相同。
如何在C#4.0中完成?
答案 0 :(得分:86)
在所有自定义类型上实施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>
的可变类型。有两种平等概念: 身份 和 等效 。在内存表示级别,这些通常被区分为“引用相等”和“值相等”(参见Equality Comparisons)。但是,相同的区别也适用于域级别。假设您的Person
类具有PersonId
属性,每个不同的真人世界都是唯一的。两个具有相同PersonId
但不同Age
值的对象应该被视为相同还是不同?上面的答案假设一个人在等价之后。但是,IEquality<T>
接口有许多用法,例如集合,它们假设这样的实现提供了身份。例如,如果要填充HashSet<T>
,通常会期望TryGetValue(T,T)
调用返回仅共享参数标识的现有元素,而不一定是内容完全相同的等效元素。这个概念由GetHashCode
上的注释强制执行:
通常,对于可变引用类型,只有在以下情况下才应覆盖
GetHashCode()
- 您可以从不可变的字段计算哈希码;或
- 当对象包含在依赖于其哈希码的集合中时,您可以确保可变对象的哈希码不会更改。
答案 1 :(得分:72)
序列化两个对象并比较生成的字符串
答案 2 :(得分:22)
您可以使用扩展方法,递归来解决此问题:
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;
}
答案 3 :(得分:18)
如果您不想实现IEquatable,您始终可以使用Reflection来比较所有属性: - 如果它们是价值类型,只需比较它们 - 如果它们是引用类型,则递归调用函数以比较其“内部”属性。
我不是在考虑性能,而是考虑简单性。然而,这取决于对象的确切设计。根据您的对象形状,它可能会变得复杂(例如,如果属性之间存在循环依赖关系)。但是,您可以使用几种解决方案,例如:
另一种选择是将对象序列化为文本,例如使用JSON.NET,并比较序列化结果。 (JSON.NET可以处理属性之间的循环依赖关系。)
我不知道是否最快意味着实现它的最快方式或快速运行的代码。在知道是否需要之前,您不应该进行优化。 Premature optimization is the root of all evil
答案 4 :(得分:9)
序列化两个对象并通过@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来比较它们。 我用它来检查对象是否也在集合中。它的效果非常好。
答案 5 :(得分:5)
我假设你并不是指字面上相同的对象
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;
}
}
答案 6 :(得分:3)
我在下面找到了比较对象的功能。
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;
}
我正在使用它,它对我来说很好。
答案 7 :(得分:3)
您现在可以使用json.net。只需继续安装Nuget即可。
您可以执行以下操作:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
如果您想变得更奇特,则可以为对象创建扩展方法。请注意,这仅比较公共财产。而且,如果您想在进行比较时忽略公共属性,则可以使用[JsonIgnore]属性。
答案 8 :(得分:2)
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;
}
答案 9 :(得分:1)
执行此操作的一种方法是在所涉及的每种类型上覆盖Equals()
。例如,您的顶级对象将覆盖Equals()
以调用所有5个子对象的Equals()
方法。这些对象也应该覆盖Equals()
,假设它们是自定义对象,依此类推,直到可以通过对顶级对象执行相等性检查来比较整个层次结构。
答案 10 :(得分:1)
使用IEquatable<T>
接口,其方法为Equals
。
答案 11 :(得分:1)
根据这里已经给出的一些答案,我决定主要退回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.");
答案 12 :(得分:1)
感谢乔纳森(Jonathan)的榜样。我将其扩展为适用于所有情况(数组,列表,字典,原始类型)。
这是没有序列化的比较,不需要为比较对象实现任何接口。
/// <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
答案 13 :(得分:1)
序列化两个对象,然后计算哈希码,然后进行比较。
答案 14 :(得分:1)
如果你有一个要求,你想要一个不可变的类。我的意思是,一旦创建,就不能修改任何属性。在这种情况下,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
答案 15 :(得分:0)
我会说:
Object1.Equals(Object2)
将是您正在寻找的。那就是如果你想看看对象是否相同,这就是你似乎要问的。
如果要检查所有子对象是否相同,请使用Equals()
方法循环运行它们。