web-dev-qa-db-ja.com

同じインターフェイスを実装するクラスの等価性はどのように定義する必要がありますか?

以下のC#の例のように、同じインターフェイスを実装する2つのクラスがあるとします。

public interface ICommonInterface
{
    string Text { get; }
    int Count { get; }
}

public class ImplementationA: ICommonInterface
{
    // Implementation A
}

public class ImplementationB: ICommonInterface
{
    // Implementation B
}

ITargetOccurenceの実装の同等性を、ITargetOccurence.TextITargetOccurence.Countの両方が同等であると定義したいと思います。

そのような平等を定義する良い方法は何ですか、そしてなぜですか?


私が検討した可能な解決策:


1)平等のための静的メソッドヘルパーの実装

インターフェースの同等性を、静的ヘルパークラスを介してアクセスできるものにすることができます。

public static class CommonInterfaceHelper
{
    public static bool AreEqual(ICommonInterface A, ICommonInterface B)
    {
        // ...
    }
}

ただし、これは言語の平等パターンを利用しません。


2)クラス内の等価演算子のオーバーライド

私はこの質問で提案されている実装と同様のことを行うことができます: https://stackoverflow.com/questions/22101703/overriding-equals-in-c-sharp-interface-implementation

public class ImplementationA: ICommonInterface
{
    public override bool Equals(object obj)
    {
       ICommonInterface other = obj as ICommonInterface;
       // ...
    }
}

// Same thing for ImplementationB

ただし、別のクラスのインスタンスに対してtrueを返す等式は直観に反すると思います。


3)ICommonInterfacestructに変更する

ICommonInterfaceをインターフェイスにする代わりに、同等性がより明確に定義されたstructにすることができます。結果の実装は次のようになります。

public struct CommonEnum
{
    string Text;
    int Count;
}

public class ImplementationA
{
    // Implementation A
    public static implicit operator CommonEnum(ImplementationA imp)
    {
        CommonEnum commonEnum = new CommonEnum();
        commonEnum.Text = imp.Text;
        commonEnum.Count = imp.Count;

    };
}

// Same thing for ImplementationB

ただし、これによりImplementationAImplementationBの関連付けが解除されます。


4)インターフェイス(ICommonInterface)に等式制約を設定しないでください

インターフェースは、クラスが持つべきメソッドの仕様です。その場合、ICommonInterfaceに「等式制約」を含めるべきではなく、等価性を比較する場合は各メソッドを個別にチェックする必要があると主張できます。


5)インターフェースで定義された方法を平等にする

(4)と同じ理由で、同等性が期待される場合、インターフェースは次のように定義する必要があると主張できます。

public interface ICommonInterface
{
    string Text { get; }
    int Count { get; }

    bool IsEqual(ICommonInterface other);
}

ただし、これは言語が提供する平等パターンを利用しません。


特定の優れた実践または原則に従って、この平等を定義する標準的な方法はありますか?

回答の受け入れ基準を明確にするために、この質問への回答は次のいずれかであると思います。

  • これは「意見に基づく」主題であり、所定のアプローチの1つが望ましいことを示す一連の優れた実践または原則がないという主張。
  • アプローチの1つは、いくつかのリンクまたは使用された原則の説明とともに、所定の一連の優れた実践または原則に従うことによって優先されるという主張。
3
Albuquerque

あなたの質問に対する単一の答えはありません:それはモデル化/抽象化しているwhatに強く依存します。要するに、提案されたソリューションのほとんどは条件付きで有効です!提案されたソリューションに関するいくつかのコメント

  • オプション1:この命題の背後にあるideaは、おそらく、無関係なカスタムの等式を定義できるようにするためです実装の。これは問題ありませんが、あなたができる最善ではありません。静的等価メソッドは(いわば)「構成可能」ではないため、それほど役に立ちません。 IEqualityComparer を優先します。なぜこれがより便利なのですか? DictionaryまたはHashSetに簡単に「プラグイン」できるため、等価メソッドは実際には、さらに「適応」する必要なく何かを実行します。つまり、静的メソッドよりもEqualityComparerを再利用する方が簡単です(クラスはメソッドよりも高レベルの抽象化です)。

  • オプション2:ほとんどの場合、equalityが実装の詳細であるため、これはほとんどの合理的なケースです。ただし、オブジェクトをインターフェイスにキャストしないでください/別のタイプの場合は決定できません、自分のタイプのみ。 「OOPの意味で)」公平になりたい場合、EqualsTypeは同じTypeにのみ関係する必要があります。それ以外の場合、最小の驚きの原則を破るだけでなく、モデル全体(型内の他の型について決定を下します)。これにより、柔軟性と保守性が失われますインターフェースの実装が増えることを期待しています。誰かがインターフェースを実装することを決め、突然、自分のインスタンスが他のタイプのインスタンスに「置き換え可能」であることがわかりました。要するに、Equalsメソッドは終了する可能性があります 忘れそうな場所 で呼び出されます。

特に、最後のリンクのRemarksセクションは次のように述べています:

Defaultプロパティは、T型がSystem.IEquatableインターフェイスを実装しているかどうかをチェックし、実装している場合は、その実装を使用するEqualityComparerを返します。それ以外の場合は、Tによって提供されるObject.EqualsおよびObject.GetHashCodeのオーバーライドを使用するEqualityComparerを返します。

  • オプション3:これは、平等を制御するためだけのかなり極端な方法です。おそらく、大きなハンマーで小さな釘を打っていますが、演算子が暗黙的である場合はなおさらです。これが全体として本当に良いアイデアではない理由についてコメントするのではなく、ソリューションの設計方法とは別に、ソリューションの使用方法についても検討する必要があることを思い出させてください。このオプションでは、誰かがknowに追加の詳細情報を要求します。詳細を確認するには、等しいかどうかを確認するときに、2つのCommonEnumオブジェクトを作成して比較する必要があります。これは、単純な自明のEqualsメソッドよりも説明が必要なものです。

  • オプション4および5:これらのインターフェースがわからないため、アドバイスできません実際には。たとえば、(何らかの理由で)座標のインターフェイスを設計している場合、ポイントmustであるため、同等であると主張できます/定義の一部。ただし、これよりも優れたオプションがまだあるため、正当な理由なく拡張するため、同等性はおそらくインターフェースの一部にはならないはずです。つまり、まったく使用する必要がなくなる可能性があるので、なぜそれを前もって定義するのでしょうか。bothモデル、and微妙な要素を設計しているため、ここには「ベストプラクティス」などはありません。 。いずれの場合でも、メソッドを強制する場合は、インターフェースを拡張するだけですIEquatable<T>代わりに。これは、参照する言語のパターンに関しては、より用途が広くなります。

TL;DRバージョンは、外部EqualityComparersを使用すると、提供できないシナリオは事実上なく、最も重要なシナリオ(HashSetsおよびDictionarysなど)は事実上ないという単純な提案です。 )そのまますぐに使用できます。それを、インターフェイスやクラスに触れる必要さえないという事実と組み合わせると、そこにそれができます。定義に手を加えることなく、用途に合わせた多目的な平等。 IEquatablemightと考えると、オブジェクトの直感性が低下します(開発者リーダーが処理する必要がある情報の追加レイヤーであるため、多くの場合「なぜ」が発生する可能性があるのか​​)、私はIEqualityComparerが最も目立たない慣行であると結論するのは難しいです。typical最も便利なオプションを使用してください。

P.S.:おまけとして、IEquatableがオプションである実際のシナリオを紹介します。巨大なコードベースを構築しました。これまでのところ、参照の等価性に基づくデフォルトの等価性で問題はありません。つまり、あなたが単に気にしすぎなかったEqualityComparersです。これまでのところ素晴らしい...次に、不変オブジェクトを(1つの異なるプロパティを使用して)インプレースで作成し、古いオブジェクトを置き換えることで、不変オブジェクトを変更する必要があります。しかし、膨大なコードの混乱のあらゆる場所で、そのオブジェクトをさまざまなものに結び付けた(つまり、それをキーとして使用した)場合は、これらの関係をすべて失うことになり、オブジェクトが使用されたすべての場所を実際に更新する必要はありません。新しい不変のインスタンスでキーを更新するためのキー。これには検索が多すぎますが必要になります。

このシナリオでは、手遅れであるにも関わらず、手遅れではないことがわかります。これでIEquatableを適切に実装できるようになり、突然、不変オブジェクトの2つのインスタンスが実際に同じキーを作成することになります(もちろん、等価性を定義するプロパティを変更していない限り)。これで、不変のインスタンスを置き換えることができ、すべてのディクショナリやハッシュセットなどは、キーと値の関係を壊しません!

8
Vector Zita