Question

Quelqu'un a des opinions sur l'opportunité ou non IEquatable<T> ou IComparable<T> doit généralement exiger que T est sealed (si c'est un class)?

Cette question me vint que j'écris un ensemble de classes de base destinées à aider à la mise en œuvre des classes immuables. Une partie de la fonctionnalité que la classe de base est destinée à fournir est la mise en œuvre automatique des comparaisons d'égalité (en utilisant les champs de la classe ainsi que des attributs qui peuvent être appliquées aux champs pour contrôler les comparaisons d'égalité). Il devrait être assez agréable quand je suis fini - J'utilise des arbres d'expression pour créer dynamiquement une fonction de comparaison compilée pour chaque T, de sorte que la fonction de comparaison devrait être très proche de la performance d'une fonction régulière de comparaison d'égalité. (J'utilise un dictionnaire immuable calée sur le verrouillage de contrôle System.Type et double pour enregistrer les fonctions de comparaison générées d'une manière qui est raisonnablement performant)

Une chose qui a surgi bien, est ce que les fonctions à utiliser pour vérifier l'égalité des champs membres. Mon intention initiale était de vérifier si le type de chaque champ membre (que je vais appeler X) met en œuvre IEquatable<X>. Cependant, après réflexion, je ne pense pas que ce soit sûr à utiliser, à moins X est sealed. La raison étant que si X ne sealed, je ne peux pas savoir avec certitude si X délègue de façon appropriée des contrôles d'égalité à une méthode virtuelle sur X, permettant ainsi un sous-type de passer outre la comparaison de l'égalité.

Cela amène ensuite une question plus générale - si un type est pas étanche, il doit vraiment mettre en œuvre ces interfaces ALL ?? AT Je ne pense pas, car je dirais que le contrat d'interfaces est de comparer entre deux types de X, pas deux types qui peuvent ou peuvent ne pas être X (bien qu'ils doivent bien entendu être X ou un sous-type).

Qu'est-ce que vous en pensez? Et devrait IEquatable<T> IComparable<T> être évité pour les classes non scellées? (Me fait aussi me demande s'il y a une règle de FxCop pour cela)

Ma pensée actuelle est d'avoir ma fonction de comparaison générée utiliser uniquement IEquatable<T> sur les champs membres dont T est sealed, et au lieu d'utiliser le Object.Equals(Object obj) virtuel si T est descellé même si T implémente IEquatable<T>, puisque le champ pourrait potentiellement sous-types de magasins de T et je doute que la plupart des implémentations de IEquatable<T> sont conçus de façon appropriée pour l'héritage.

Était-ce utile?

La solution

J'ai pensé à cette question un peu et après un peu de considération, je suis d'accord que la mise en œuvre IEquatable<T> et IComparable<T> ne devraient se faire sur les types scellés.

Je suis retourné en arrière pour un peu, mais je pensais à l'essai suivant. Dans quelles circonstances les entités suivantes devraient jamais revenir faux? À mon humble avis, 2 objets sont égaux ou ils ne sont pas.

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);
}

Le résultat de IEquatable<T> sur un objet donné doit avoir le même comportement que Object.Equals en supposant que le comparateur est du type équivalent. La mise en œuvre IEquatable<T> deux fois dans une hiérarchie d'objets permet, et implique, il y a 2 façons différentes d'exprimer l'égalité dans votre système. Il est facile d'inventer un certain nombre de scénarios où IEquatable<T> et Object.Equals différeraient car il y a plusieurs implémentations IEquatable<T> mais un seul Object.Equals. Par conséquent ce qui précède échouerait et créer un peu de confusion dans votre code.

Certaines personnes peuvent faire valoir que la mise en œuvre IEquatable<T> à un point plus élevé dans la hiérarchie des objets est valide parce que vous voulez comparer un sous-ensemble des propriétés des objets. Dans ce cas, vous devriez favoriser une IEqualityComparer<T> qui est spécifiquement conçu pour comparer ces propriétés.

Autres conseils

Je recommande généralement contre la mise en œuvre IEquatable sur une catégorie non scellée, ou la mise en œuvre IComparable non générique sur la plupart, mais on ne peut pas en dire IComparable . Deux raisons:

  1. Il existe déjà un moyen de comparaison des objets qui peuvent ou peuvent ne pas être le même type: Object.Equals. Depuis IEquatable ne comprend pas GetHashCode, son comportement a essentiellement pour correspondre à celui de Object.Equals. La seule raison de la mise en œuvre IEquatable , en plus de Object.Equals est la performance. IEquatable offre une petite amélioration de la performance par rapport Object.Equals lorsqu'il est appliqué à des types de classe scellés, et une grande amélioration lorsqu'elle est appliquée à la structure types. La seule façon une mise en œuvre de le type non scellé de IEquatable Equals peut faire en sorte que son comportement correspond à celui d'un mais, Object.Equals peut-redéfinis, est d'appeler Object.Equals. Si IEquatable Equals doit appeler Object.Equals, tout avantage possible de la performance disparaît.
  2. Il est parfois possible, significatif et utile, pour une classe de base pour avoir un ordre naturel défini des propriétés impliquant uniquement la classe de base, qui sera cohérente à travers tous les sous-classes. Lors de la vérification de deux objets pour l'égalité, le résultat ne devrait pas dépendre que l'on considère les objets comme étant un type de base ou d'un type dérivé. Pour le classement des objets, cependant, le résultat doit souvent dépendre du type utilisé comme base de comparaison. objets dérivés de la classe doivent mettre en œuvre IComparable mais ne doivent pas remplacer la méthode de comparaison du type de base. Il est tout à fait raisonnable pour deux objets de la classe dérivée de comparer comme « non classé » par rapport comme type de parent, mais pour un à comparer sur l'autre par rapport que le type dérivé.

La mise en œuvre de IComparable non générique dans les classes héritable est peut-être plus discutable que la mise en œuvre de IComparable . Probablement la meilleure chose à faire est de permettre une classe de base pour la mettre en œuvre si elle est pas prévu que la classe enfant aura besoin d'un autre ordre, mais pour les classes d'enfants de ne pas réimplémenter ou remplacer les implémentations de classe parent.

La plupart des implémentations de Equals que j'ai vu vérifier les types des objets comparés, si elles ne sont pas les mêmes, alors la méthode retourne false.

Cela évite soigneusement le problème d'un sous-type étant comparé à son type de parent, niant ainsi la nécessité d'une classe d'étanchéité.

Un exemple évident serait d'essayer de comparer un point 2D (A) avec un point 3D (B): pour une 2D les valeurs x et y d'un point 3D peut être égal, mais pour un point 3D, la valeur z sera très probablement différent.

Cela signifie que A == B serait vrai, mais B == A serait faux. La plupart des gens comme les opérateurs de Equals commutatif, de vérifier les types est clairement une bonne idée dans ce cas.

Mais si vous sous-classe et vous ne pas ajouter de nouvelles propriétés? Eh bien, c'est un peu plus difficile à répondre, et peut-être dépend de votre situation.

Je suis tombé sur ce sujet aujourd'hui lors de la lecture
https://blog.mischel.com / 2013/01/05 / héritage et-IEquatable-do-not-mix /
et je suis d'accord, qu'il y a des raisons de ne pas mettre en œuvre IEquatable<T>, parce que les chances existent que ce sera fait d'une manière erronée.

Cependant, après avoir lu l'article lié je l'ai testé ma propre mise en œuvre que j'utilise sur différentes classes non scellées, héritées, et je trouve que cela fonctionne correctement.
Lors de la mise en œuvre IEquatable<T>, je me suis référé à cet article:
http://www.loganfranken.com/blog / 687 / prioritaire est égal à-en-c-partie-1 /
Il donne une assez bonne explication ce code à utiliser dans Equals(). Il ne traite pas l'héritage, donc je l'écoute moi-même. Voici le résultat.

Et pour répondre à la question initiale:
Je ne dis pas que devrait être mis en œuvre sur les classes non scellées, mais je dis que cela pourrait certainement être mis en œuvre sans problème.

//============================================================================
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;
  }
}

Test:

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);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top