Est-ce un bon langage / efficace pour la mise en œuvre et les opérateurs Equals l'égalité / inégalité?
-
11-09-2019 - |
Question
J'ai eu quelques problèmes pour obtenir ce droit, donc je voulais demander si quelqu'un a des commentaires sur que ce soit un moyen efficace de mettre en œuvre la méthode Equals et l'égalité opérateurs / inégalité pour une classe personnalisée immuable. Ces opérateurs sont très souvent appelés par mon programme, alors je veux vous assurer que je les ai droit.
class MyObj
{
public static bool operator ==(MyObj a, MyObj b)
{
if (!object.ReferenceEquals(a, null))
return a.Equals(b);
else if (!object.ReferenceEquals(b, null))
return b.Equals(a);
else
// both are null
return true;
}
public static bool operator !=(MyObj a, MyObj b)
{
if (!object.ReferenceEquals(a, null))
return !a.Equals(b);
else if (!object.ReferenceEquals(b, null))
return !b.Equals(a);
else
// both are null
return false
}
public override bool Equals(object obj)
{
return this.Equals(obj as MyObj);
}
public bool Equals(MyObj obj)
{
if (object.ReferenceEquals(obj, null))
return false;
else
return (obj.FieldOne == this.FieldOne &&
obj.FieldTwo == this.FieldTwo && ...);
}
}
La solution
J'utilise le code suivant pour les types de référence, qui a moins le double emploi et se sent plus propre, à mon avis. Avoir une méthode statique « Equals » permet aux langages .NET sans surcharger l'opérateur de comparer vos instances sans avoir à tester null avant d'appeler la méthode d'instance. Si vous implémentez des opérateurs d'égalité, il pourrait également être mieux pour rendre votre classe immuable, si vous le pouvez.
class Foo : IEquatable<Foo>
{
public override bool Equals(object obj)
{
return Equals(obj as Foo);
}
public bool Equals(Foo other)
{
if (object.ReferenceEquals(other, null)) return false;
// Optional early out
if (object.ReferenceEquals(this, other)) return true;
// Compare fields here
}
public static bool Equals(Foo a, Foo b)
{
if (ReferenceEquals(a, null)) return ReferenceEquals(b, null);
return a.Equals(b);
}
public static bool operator ==(Foo a, Foo b)
{
return Equals(a, b);
}
public static bool operator !=(Foo a, Foo b)
{
return !Equals(a, b);
}
}
Autres conseils
Certaines choses je remarque:
- Parce que vous surchargeons
Equals
, vous devez également remplacerGetHashCode
. - Étant donné que votre méthode de
Equals(MyObj)
est une implémentation valide pour l'ensemble de l'interfaceIEquatable<MyObj>
,MyObj
doit en effet mettre en œuvre cette interface. Cela permettra égalementDictionary<>
et ce à profiter directement de votre méthode deEquals(MyObj)
, au lieu de passer parEquals(object)
.
Aussi je suis entièrement d'accord avec la mise en œuvre alternative Trillian, sauf que je l'aurais mis en œuvre a != b
directement !(a == b)
au lieu de !Equals(a, b)
. (Différence Trivial bien sûr.)
En gros oui, mais il y a une erreur à corriger.
La méthode Equals(object)
appelle lui-même au lieu d'appeler la méthode Equals(MyObj)
, ce qui provoque une boucle éternelle. Il devrait être:
public override bool Equals(object obj) {
MyObj other = obj as MyObj;
return this.Equals(other);
}
ou simplement:
public override bool Equals(object obj) {
return this.Equals(obj as MyObj);
}
En outre, vous pouvez simplifier l'opérateur d'inégalité à:
public static bool operator !=(MyObj a, MyObj b) {
return !(a == b);
}
Si vous êtes à la recherche d'efficacité, je recommande d'utiliser ce lieu de object.ReferenceEquals(foo, null)
:
(object)foo == null
Ceci est effectivement équivalent, mais évite un appel de fonction.
J'aime aussi mettre en œuvre IEquatable<T>
dans tous mes types qui remplacent Equals
. Pour les types de référence, je puis Equals(object)
avec impatience Equals(Foo)
.
public override bool Equals(object other){return Equals(other as Foo);}
Les opérateurs peuvent surcharges être simplifiées si:
public static bool operator==(Foo a, Foo b){
if((object)a == null)
return (object)b == null;
return a.Equals(b);
}
public static bool operator!=(Foo a, Foo b){
return !(a == b);
}
Si absolu l'efficacité est nécessaire, cependant, il vaut peut-être peu de dédoublement de code dans ces fonctions pour éviter les appels de fonctions supplémentaires, mais contrairement à l'aide (object)foo == null
au lieu de object.ReferenceEquals(foo, null)
, évitant ainsi l'appel de fonction nécessite code supplémentaire pour maintenir, de sorte que le petit gain peut ne pas valoir la peine.
Je préfère laisser toute la logique « si cela est nul alors faire d'autre ... » au cadre:
class MyObj : IEquatable<MyObj> {
public static bool operator ==( MyObj left, MyObj right ) {
return EqualityComparer<MyObj>.Default.Equals( left, right );
}
public static bool operator !=( MyObj left, MyObj right ) {
return !EqualityComparer<MyObj>.Default.Equals( left, right );
}
public override bool Equals( object obj ) {
return this.Equals( obj as MyObj );
}
public bool Equals( MyObj other ) {
return !object.ReferenceEquals( other, null )
&& obj.FieldOne == this.FieldOne
&& obj.FieldTwo == this.FieldTwo
&& ...
;
}
...
}
Voir aussi Quel est le meilleur algorithme pour une GetHashCode surchargée pour la mise en œuvre GetHashCode
.