37

誰かがそれを要求するかどうか、または一般的に要求すべきかどうかについて意見がありますかIEquatable<T>(それが)?IComparable<T>Tsealedclass

この質問は、不変クラスの実装を支援することを目的とした一連の基本クラスを作成しているときに発生しました。基本クラスが提供することを目的とした機能の一部は、同等性比較の自動実装です(同等性比較を制御するためにフィールドに適用できる属性とともにクラスのフィールドを使用します)。終了したらかなりいいはずです。式ツリーを使用して、それぞれのコンパイル済み比較関数を動的に作成しているTので、比較関数は通常の等式比較関数のパフォーマンスに非常に近いはずです。(キー入力された不変の辞書System.Typeとダブルチェックロックを使用して、生成された比較関数を適度にパフォーマンスの高い方法で保存しています)

ただし、発生したことの1つは、メンバーフィールドの同等性をチェックするために使用する関数です。私の最初の意図は、各メンバーフィールドのタイプ(これを呼び出しますX)が実装されているかどうかを確認することでしたIEquatable<X>Xしかし、少し考えてみると、そうでない限り、これは安全に使用できるとは思いませんsealed。その理由は、そうXでない場合、の仮想メソッドに等価性チェックを適切に委任sealedしているかどうかがわかりません。これにより、サブタイプが等価性の比較をオーバーライドできるようになります。XX

次に、これにより、より一般的な質問が発生します。型が封印されていない場合、これらのインターフェイスを実際に実装する必要がありますか?Xインターフェイスコントラクトは2つのタイプを比較することであり、そうでない場合もある2つのタイプを比較することではないと主張するので、私はそうは思わないでしょうX(もちろん、Xまたはサブタイプである必要があります)。

皆さんはどう思いますか?封印されていないクラスでは避けるべきIEquatable<T>であり、避けるべきですか?IComparable<T>(また、これにfxcopルールがあるかどうか疑問に思います)

私の現在の考えは、生成された比較関数をであるメンバーフィールドでのみ使用しIEquatable<T>、代わりに、フィールドがのサブタイプを格納する可能性があり、のほとんどの実装が継承用に適切に設計されているとは思えないため、実装されている場合でも、仮想ifが封印されていない場合に使用することです。 。TsealedObject.Equals(Object obj)TTIEquatable<T>TIEquatable<T>

4

4 に答える 4

21

私はこの質問について少し考えていましたが、少し検討した結果、実装することに同意し、封印されたタイプでのみ実行する必要があります IEquatable<T>IComparable<T>

少し行ったり来たりしましたが、次のテストを考えました。次のような状況でfalseを返す必要がありますか?私見、2つのオブジェクトは等しいかそうでないかのどちらかです。

public void EqualitySanityCheck<T>(T left, T right) where T : IEquatable<T> {
  var equals1 = left.Equals(right);
  var equals2 = ((IEqutable<T>)left).Equals(right);
  Assert.AreEqual(equals1,equals2);
}

特定のオブジェクトに対するの結果は、比較対象が同等のタイプであると想定したIEquatable<T>場合と同じ動作をする必要があります。Object.Equalsオブジェクト階層に2回実装IEquatable<T>すると、システムで平等を表現する2つの異なる方法が可能になります。複数の実装がありますが、単一の実装しかないため、IEquatable<T>Object.Equalsが異なるシナリオをいくつでも簡単に考案できます。したがって、上記は失敗し、コードに少し混乱が生じます。 IEquatable<T>Object.Equals

IEquatable<T>オブジェクトのプロパティのサブセットを比較したいので、オブジェクト階層のより高いポイントで実装することが有効であると主張する人もいるかもしれません。その場合、IEqualityComparer<T>それらのプロパティを比較するために特別に設計されたを優先する必要があります。

于 2009-12-08T17:21:30.607 に答える
5

一般に、シールされていないクラスにIEquatable <T>を実装したり、ほとんどのクラスに非ジェネリックIComparableを実装したりしないことをお勧めしますが、IComparable<T>についても同じことは言えません。2つの理由:

  1. 同じタイプである場合とそうでない場合があるオブジェクトを比較する手段がすでに存在します:Object.Equals。IEquatable <T>にはGetHashCodeが含まれていないため、その動作は基本的にObject.Equalsの動作と一致する必要があります。Object.Equalsに加えてIEquatable<T>を実装する唯一の理由は、パフォーマンスです。IEquatable <T>は、Object.Equalsに比べてパフォーマンスがわずかに向上し、封印されたクラスタイプに適用すると大幅に向上し、構造体タイプに適用すると大幅に向上します。封印されていない型のIEquatable<T>.Equalsの実装が、その動作がオーバーライドされる可能性のあるObject.Equalsの動作と一致することを保証できる唯一の方法は、Object.Equalsを呼び出すことです。IEquatable <T> .EqualsがObject.Equalsを呼び出さなければならない場合、パフォーマンス上の利点はなくなります。
  2. 基本クラスが、すべてのサブクラスで一貫している基本クラスのプロパティのみを含む定義された自然な順序を持つことが、場合によっては可能で、意味があり、便利です。2つのオブジェクトが等しいかどうかをチェックする場合、結果は、オブジェクトを基本型と見なすか派生型と見なすかに依存しないようにする必要があります。ただし、オブジェクトをランク付けする場合、結果は、比較の基礎として使用されているタイプに依存することがよくあります。派生クラスオブジェクトはIComparable<TheirOwnType>を実装する必要がありますが、基本タイプの比較メソッドをオーバーライドすることはできません。2つの派生クラスオブジェクトが親タイプとして比較される場合は「ランク付けされていない」と比較されるのは完全に合理的ですが、派生タイプとして比較される場合は一方が他方の上に比較されます。

継承可能なクラスでの非ジェネリックIComparableの実装は、IComparable<T>の実装よりもおそらく疑わしいものです。おそらく最善の方法は、子クラスに他の順序が必要になることが予想されない場合に基本クラスに実装を許可することですが、子クラスには親クラスの実装を再実装またはオーバーライドしないようにします。

于 2011-10-07T21:30:09.540 に答える
1

Equals私が見たほとんどの実装は、比較されているオブジェクトのタイプをチェックします。それらが同じでない場合、メソッドはfalseを返します。

これにより、サブタイプがその親タイプと比較される問題が適切に回避され、クラスをシールする必要がなくなります。

この明らかな例は、2Dポイント(A)と3Dポイント(B)を比較しようとすることです。2Dの場合、3Dポイントのx値とy値は等しいかもしれませんが、3Dポイントの場合、z値はほとんどの場合、異なります。

これは、それA == Bは正しいが、B == A間違っていることを意味します。ほとんどの人は、Equals演算子が可換であることを好みます。この場合、型をチェックすることは明らかに良い考えです。

しかし、サブクラス化し、新しいプロパティを追加しない場合はどうなりますか?まあ、それは答えるのが少し難しいです、そしておそらくあなたの状況に依存します。

于 2009-12-09T14:29:54.510 に答える
0


今日、 https://blog.mischel.com/2013/01/05/inheritance-and-iequatable-do-not-mix/を読んでいるときに、このトピックに出くわしました。
実装しない理由があることに同意しますIEquatable<T>。それが間違った方法で行われる可能性があります。

ただし、リンクされた記事を読んだ後、さまざまな非封印された継承クラスで使用する独自の実装をテストしたところ、正しく機能していることがわかりました。
を実装するときIEquatable<T>、私はこの記事を参照しました:
http
://www.loganfranken.com/blog/687/overriding-equals-in-c-part-1/ これは、で使用するコードをかなりよく説明していますEquals()。ただし、継承については触れていないので、自分で調整しました。結果は次のとおりです。

そして、元の質問に答えるために:私はそれが非封印されたクラスに実装されるべき
で あるとは言いませんが、それは間違いなく問題なく実装できると言います。

//============================================================================
class CBase : IEquatable<CBase>
{
  private int m_iBaseValue = 0;

  //--------------------------------------------------------------------------
  public CBase (int i_iBaseValue)
  {
    m_iBaseValue = i_iBaseValue;
  }

  //--------------------------------------------------------------------------
  public sealed override bool Equals (object i_value)
  {
    if (ReferenceEquals (null, i_value))
      return false;
    if (ReferenceEquals (this, i_value))
      return true;

    if (i_value.GetType () != GetType ())
      return false;

    return Equals_EXEC ((CBase)i_value);
  }

  //--------------------------------------------------------------------------
  public bool Equals (CBase i_value)
  {
    if (ReferenceEquals (null, i_value))
      return false;
    if (ReferenceEquals (this, i_value))
      return true;

    if (i_value.GetType () != GetType ())
      return false;

    return Equals_EXEC (i_value);
  }

  //--------------------------------------------------------------------------
  protected virtual bool Equals_EXEC (CBase i_oValue)
  {
    return i_oValue.m_iBaseValue == m_iBaseValue;
  }
}

//============================================================================
class CDerived : CBase, IEquatable<CDerived>
{
  public int m_iDerivedValue = 0;

  //--------------------------------------------------------------------------
  public CDerived (int i_iBaseValue,
                  int i_iDerivedValue)
  : base (i_iBaseValue)
  {
    m_iDerivedValue = i_iDerivedValue;
  }

  //--------------------------------------------------------------------------
  public bool Equals (CDerived i_value)
  {
    if (ReferenceEquals (null, i_value))
      return false;
    if (ReferenceEquals (this, i_value))
      return true;

    if (i_value.GetType () != GetType ())
      return false;

    return Equals_EXEC (i_value);
  }

  //--------------------------------------------------------------------------
  protected override bool Equals_EXEC (CBase i_oValue)
  {
    CDerived oValue = i_oValue as CDerived;
    return base.Equals_EXEC (i_oValue)
        && oValue.m_iDerivedValue == m_iDerivedValue;
  }
}

テスト:

private static void Main (string[] args)
{
// Test with Foo and Fooby for verification of the problem.
  // definition of Foo and Fooby copied from
  // https://blog.mischel.com/2013/01/05/inheritance-and-iequatable-do-not-mix/
  // and not added in this post
  var fooby1 = new Fooby (0, "hello");
  var fooby2 = new Fooby (0, "goodbye");
  Foo foo1 = fooby1;
  Foo foo2 = fooby2;

// all false, as expected
  bool bEqualFooby12a = fooby1.Equals (fooby2);
  bool bEqualFooby12b = fooby2.Equals (fooby1);
  bool bEqualFooby12c = object.Equals (fooby1, fooby2);
  bool bEqualFooby12d = object.Equals (fooby2, fooby1);

// 2 true (wrong), 2 false
  bool bEqualFoo12a = foo1.Equals (foo2);  // unexpectedly "true": wrong result, because "wrong" overload is called!
  bool bEqualFoo12b = foo2.Equals (foo1);  // unexpectedly "true": wrong result, because "wrong" overload is called!
  bool bEqualFoo12c = object.Equals (foo1, foo2);
  bool bEqualFoo12d = object.Equals (foo2, foo1);

// own test
  CBase oB = new CBase (1);
  CDerived oD1 = new CDerived (1, 2);
  CDerived oD2 = new CDerived (1, 2);
  CDerived oD3 = new CDerived (1, 3);
  CDerived oD4 = new CDerived (2, 2);

  CBase oB1 = oD1;
  CBase oB2 = oD2;
  CBase oB3 = oD3;
  CBase oB4 = oD4;

// all false, as expected
  bool bEqualBD1a = object.Equals (oB, oD1);
  bool bEqualBD1b = object.Equals (oD1, oB);
  bool bEqualBD1c = oB.Equals (oD1);
  bool bEqualBD1d = oD1.Equals (oB);

// all true, as expected
  bool bEqualD12a = object.Equals (oD1, oD2);
  bool bEqualD12b = object.Equals (oD2, oD1);
  bool bEqualD12c = oD1.Equals (oD2);
  bool bEqualD12d = oD2.Equals (oD1);
  bool bEqualB12a = object.Equals (oB1, oB2);
  bool bEqualB12b = object.Equals (oB2, oB1);
  bool bEqualB12c = oB1.Equals (oB2);
  bool bEqualB12d = oB2.Equals (oB1);

// all false, as expected
  bool bEqualD13a = object.Equals (oD1, oD3);
  bool bEqualD13b = object.Equals (oD3, oD1);
  bool bEqualD13c = oD1.Equals (oD3);
  bool bEqualD13d = oD3.Equals (oD1);
  bool bEqualB13a = object.Equals (oB1, oB3);
  bool bEqualB13b = object.Equals (oB3, oB1);
  bool bEqualB13c = oB1.Equals (oB3);
  bool bEqualB13d = oB3.Equals (oB1);

// all false, as expected
  bool bEqualD14a = object.Equals (oD1, oD4);
  bool bEqualD14b = object.Equals (oD4, oD1);
  bool bEqualD14c = oD1.Equals (oD4);
  bool bEqualD14d = oD4.Equals (oD1);
  bool bEqualB14a = object.Equals (oB1, oB4);
  bool bEqualB14b = object.Equals (oB4, oB1);
  bool bEqualB14c = oB1.Equals (oB4);
  bool bEqualB14d = oB4.Equals (oB1);
}
于 2018-11-29T23:36:00.603 に答える