1. ホーム
  2. c#

[解決済み] 2つの複雑なオブジェクトを比較する最適な方法

2022-06-27 15:52:29

質問

私は、以下のような2つの複雑なオブジェクトを持っています。 Object1 Object2 . 5段階程度の子オブジェクトを持っています。

それらが同じかどうかを言うために最も速い方法が必要です。

C#4.0ではどうすればいいのでしょうか?

どのように解決するのですか?

実装 IEquatable<T> (を実装する(通常は継承された Object.Equals Object.GetHashCode メソッド) をすべてのカスタムタイプで実行します。複合型の場合は、含まれる型の Equals メソッドを呼び出します。含まれるコレクションの場合は SequenceEqual 拡張メソッドを使用します。 IEquatable<T>.Equals または Object.Equals を各要素に追加します。この方法は、明らかに型の定義を拡張する必要がありますが、その結果はシリアライズを含むどのような一般的な解決策よりも高速になります。

編集 : ここでは3段階のネストで工夫された例を紹介します。

値型については、通常、単にその Equals メソッドを呼び出すだけです。たとえフィールドやプロパティが明示的に割り当てられなかったとしても、それらはデフォルト値を持つことになります。

参照型については、まず ReferenceEquals これは参照の等質性をチェックするもので、偶然同じオブジェクトを参照することになった場合の効率アップに役立ちます。これは、両方の参照が NULL である場合も処理します。このチェックに失敗した場合は、インスタンスのフィールドやプロパティがNULLでないことを確認します("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> を実装することから離れました。等号には2つの概念があります。 同一性 そして 等価 . メモリ表現レベルでは、これらは一般に「参照等価」と「値等価」として区別される(詳細は 等価比較 ). しかし、同じ区別がドメインレベルでも適用できる。仮に、あなたの Person クラスが PersonId プロパティがあり、現実世界の人ごとに一意である。2 つのオブジェクトが同じ PersonId を持つが、異なる Age の値は等しいとみなされるのでしょうか、それとも異なるとみなされるのでしょうか?上記の回答は、等価であることを前提にしています。しかし、多くの使用法がある IEquality<T> インターフェイスの多くの使用法、例えばコレクションなどでは、そのような実装が提供するのは アイデンティティ . 例えば、もしあなたが HashSet<T> を生成する場合、通常は TryGetValue(T,T) の呼び出しは、内容が完全に同じである等価な要素である必要はなく、単に引数のアイデンティティを共有する既存の要素を返すことを期待します。この概念は GetHashCode :

一般に、ミュータブルな参照型では、オーバーライドする必要があります。 GetHashCode() の場合のみです。

  • mutableではないフィールドからハッシュコードを計算できる場合、または
  • mutableなオブジェクトのハッシュコードが、そのハッシュコードに依存するコレクションに含まれている間、変更されないことを保証することができます。