比较两个复杂对象的最佳方法

Dev*_*per 102 .net c#

我有两个像Object1和的复杂对象Object2.他们有大约5个级别的子对象.

我需要最快的方法来说明它们是否相同.

怎么可以在C#4.0中完成?

Dou*_*las 91

在所有自定义类型上实现IEquatable<T>(通常与覆盖继承Object.EqualsObject.GetHashCode方法一起使用).对于复合类型,在包含类型中调用包含类型的Equals方法.对于包含的集合,请使用SequenceEqual扩展方法,该方法在内部调用IEquatable<T>.EqualsObject.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:这个答案是几年前写的.从那以后,我开始倾向于IEquality<T>为这种情况实现可变类型.平等有两种概念:身份等同.在内存表示级别,这些通常被区分为"引用相等"和"值相等"(参见Equality Comparisons).但是,相同的区别也适用于域级别.假设你的Person班级有一个PersonId属性,每个不同的现实世界的人都是唯一的.两个具有相同PersonId但不同Age值的对象应该被视为相同还是不同?上面的答案假设一个人在等价之后.但是,IEquality<T>接口的许多用法,例如集合,假设这样的实现提供了身份.例如,如果要填充a HashSet<T>,通常会期望TryGetValue(T,T)调用返回仅共享参数标识的现有元素,而不一定是内容完全相同的等效元素.这个概念由以下注释强制执行GetHashCode:

通常,对于可变引用类型,GetHashCode()只应在以下情况下覆盖:

  • 您可以从不可变的字段计算哈希码; 要么
  • 您可以确保在对象包含在依赖于其哈希代码的集合中时,可变对象的哈希码不会更改.

  • 你可以在数组上调用LINQ [`Enumerable.SequenceEqual`](http://msdn.microsoft.com/en-us/library/bb348567.aspx)方法:`this.Addresses.SequenceEqual(other.Addresses)` .这将在内部调用每对相应地址的`Address.Equals`方法,因为`Address`类实现了`IEquatable <Address>`接口. (2认同)
  • 开发人员可能会检查的另一类比较是“WorksLike”。对我来说,这意味着即使两个实例可能具有一些不相等的属性值,程序也会通过处理这两个实例产生相同的结果。 (2认同)

Joe*_*Fan 80

序列化两个对象并比较生成的字符串

  • 我只是因为我从来没有想过以这种方式进行基于价值观的平等比较.这很好,很简单.用这段代码看一些基准是很好的. (9认同)
  • 这是一个巨大的成本.您正在生成数据流,附加字符串,然后测试字符串相等性.数量级,就在那里.更不用说序列化将默认使用反射. (5认同)
  • 也许,它应该工作.谢谢! (3认同)
  • 我不明白为什么会有。序列化通常是一个优化过程,在任何情况下您都需要访问每个属性的值。 (3认同)
  • 数据流没什么大不了的,我不明白为什么你需要追加字符串...测试字符串相等是那里最优化的操作之一....你可能有一点反思...但是在整个序列化不会比其他方法更"数量级".如果您怀疑性能问题,您应该做基准测试......我没有遇到过这种方法的性能问题 (2认同)

Jon*_*han 29

您可以使用扩展方法,递归来解决此问题:

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;
}
Run Code Online (Sandbox Code Playgroud)

或使用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;
}
Run Code Online (Sandbox Code Playgroud)

  • 有没有理由使用`DeepCompare`而不是简单地递归调用`CompareEx`? (3认同)
  • 这可能会不必要地比较整个结构。用`return false`代替`result`会使效率更高。 (3认同)

Jot*_*aBe 21

如果您不想实现IEquatable,您可以始终使用Reflection来比较所有属性: - 如果它们是值类型,只需比较它们 - 如果它们是引用类型,则递归调用函数以比较其"内部"属性.

我不是在考虑性能,而是考虑简单性.然而,这取决于对象的确切设计.根据您的对象形状,它可能会变得复杂(例如,如果属性之间存在循环依赖关系).但是,您可以使用几种解决方案,例如:

另一种选择是将对象序列化为文本,例如使用JSON.NET,并比较序列化结果.(JSON.NET可以处理属性之间的循环依赖关系).

我不知道你是最快的意思是实现它的最快方式还是快速运行的代码.在知道是否需要之前,您不应该进行优化.过早优化是万恶之源


ozz*_*836 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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在任何其他文件中引用此静态类后,您可以执行以下操作:

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();
Run Code Online (Sandbox Code Playgroud)

现在您可以简单地使用.Equals来比较它们.我用它来检查对象是否也在集合中.它工作得很好.

  • 如果新任总监告诉您淘汰C#并用Python替换它,该怎么办?作为开发人员,我们需要学习如果问题必须在某个地方停止该怎么办。解决问题,继续下一个。如果您有时间,请返回... (2认同)
  • Python在语法和用法上更像MATLAB.必须有一个非常好的理由从静态类型安全语言转移到像python这样的mishmash脚本. (2认同)
  • 然而,它在像Django这样的框架中用于创建Web应用程序...... (2认同)

ash*_*r64 9

您现在可以使用 json.net。只需继续 Nuget 并安装即可。

你可以这样做:

public bool Equals(SamplesItem sampleToCompare)
{
    string myself = JsonConvert.SerializeObject(this);
    string other = JsonConvert.SerializeObject(sampleToCompare);

    return myself == other;
}
Run Code Online (Sandbox Code Playgroud)

如果您想变得更有趣,您也许可以为对象创建一个扩展方法。请注意,这仅比较公共属性。如果您想在进行比较时忽略公共属性,则可以使用该[JsonIgnore]属性。


viv*_*una 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
Run Code Online (Sandbox Code Playgroud)


Sk *_*que 7

序列化两个对象,然后计算哈希码,然后进行比较。


pay*_*ayo 5

我假设你并不是指字面上相同的对象

Object1 == Object2
Run Code Online (Sandbox Code Playgroud)

您可能正在考虑在两者之间进行内存比较

memcmp(Object1, Object2, sizeof(Object.GetType())
Run Code Online (Sandbox Code Playgroud)

但这甚至不是c#:)中的真实代码.因为所有数据都可能是在堆上创建的,所以内存不是连续的,您不能只是以不可知的方式比较两个对象的相等性.您将不得不以自定义方式逐个比较每个值.

考虑将IEquatable<T>接口添加到您的类,并Equals为您的类型定义自定义方法.然后,在该方法中,手动测试每个值.IEquatable<T>如果可以,请重复添加封闭类型并重复此过程.

class Foo : IEquatable<Foo>
{
  public bool Equals(Foo other)
  {
    /* check all the values */
    return false;
  }
}
Run Code Online (Sandbox Code Playgroud)