一尘不染

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

c#

我有两个复杂的对象,例如 Object1Object2它们具有大约5个级别的子对象。

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

在C#4.0中怎么做?


阅读 488

收藏
2020-05-19

共1个答案

一尘不染

在所有自定义类型上实现IEquatable<T>(通常与覆盖继承Object.EqualsObject.GetHashCode方法一起)。对于复合类型,请在包含的类型Equals内调用包含的类型的方法。对于包含的集合,请使用SequenceEqual扩展方法,该方法在内部IEquatable<T>.EqualsObject.Equals在每个元素上调用。显然,这种方法将需要您扩展类型的定义,但其结果比任何涉及序列化的通用解决方案都快。

编辑 :这是三个层次的嵌套的人为的示例。

对于值类型,通常可以只调用它们的Equals方法。即使从未显式分配字段或属性,它们仍将具有默认值。

对于引用类型,应首先调用ReferenceEquals,以检查引用是否相等–当您碰巧引用同一对象时,这可以提高效率。它还将处理两个引用均为空的情况。如果该检查失败,请确认您实例的字段或属性不为null(以避免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>为此类情况实现可变类型的实现了。平等有两个概念: 同一性
和对 等性 。在内存表示级别上,通常将它们区分为“引用相等”和“值相等”(请参阅​​“
相等比较”)。但是,相同的区别也可以应用于域级别。假设您的Person班级拥有一个PersonId属性,该属性对于每个实际的人都是唯一的。具有相同PersonId但不同Age值的两个对象是否应视为相等或不同?上面的答案假设一个在等价之后。但是,IEquality<T>接口(例如集合),它们假定此类实现提供了
identity
。例如,如果要填充HashSet<T>,通常会期望TryGetValue(T,T)调用返回仅共享参数标识的现有元素,而不必返回内容完全相同的等效元素。该概念由以下注释强制执行GetHashCode

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

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