Le compilateur C # requiert que chaque fois qu'un type personnalisé définit un opérateur ==
, il doit également définir !=
(voir ici ).
Pourquoi?
Je suis curieux de savoir pourquoi les concepteurs l'ont jugé nécessaire et pourquoi le compilateur ne peut-il pas adopter par défaut une implémentation raisonnable pour l'un ou l'autre des opérateurs alors que seul l'autre est présent. Par exemple, Lua vous permet de définir uniquement l'opérateur d'égalité et vous obtenez l'autre gratuitement. C # pourrait faire de même en vous demandant de définir soit == soit les deux == et! =, Puis de compiler automatiquement l'opérateur! = Manquant en tant que !(left == right)
.
Je comprends qu'il y a des cas étranges où certaines entités peuvent ne pas être égales ou inégales (comme IEEE-754 NaN), mais celles-ci semblent être l'exception, pas la règle. Cela n'explique donc pas pourquoi les concepteurs du compilateur C # ont fait de l'exception la règle.
J'ai vu des cas de mauvaise exécution où l'opérateur d'égalité est défini, puis l'opérateur d'inégalité est un copier-coller avec chaque comparaison inversée et chaque && basculé vers un || (vous obtenez le point ... en gros! (a == b) développé par les règles de De Morgan). C'est une mauvaise pratique que le compilateur pourrait éliminer par conception, comme c'est le cas avec Lua.
Remarque: Il en va de même pour les opérateurs <> <=> =. Je ne peux pas imaginer des cas où vous aurez besoin de les définir de manière non naturelle. Lua vous permet de définir uniquement <et <= et définit> = et> naturellement à travers la négation des formateurs. Pourquoi C # ne fait-il pas de même (au moins «par défaut»)?
ÉDITER
Apparemment, il existe des raisons valables pour permettre au programmeur de mettre en œuvre des contrôles d'égalité et d'inégalité comme bon lui semble. Certaines réponses indiquent des cas où cela peut être bien.
Le noyau de ma question, cependant, est pourquoi cela est requis de force en C # alors que ce n'est généralement pas nécessaire logiquement ?
C'est également en contraste frappant avec les choix de conception pour les interfaces .NET comme Object.Equals
, IEquatable.Equals
IEqualityComparer.Equals
où l'absence d'un NotEquals
homologue montre que le cadre considère les !Equals()
objets comme inégaux et c'est tout. De plus, les classes comme Dictionary
et les méthodes comme .Contains()
dépendent exclusivement des interfaces susmentionnées et n'utilisent pas directement les opérateurs même s'ils sont définis. En fait, lorsque ReSharper génère des membres d'égalité, il définit à la fois ==
et !=
en termes de Equals()
et même alors seulement si l'utilisateur choisit de générer des opérateurs. Les opérateurs d'égalité ne sont pas nécessaires au framework pour comprendre l'égalité des objets.
Fondamentalement, le framework .NET ne se soucie pas de ces opérateurs, il ne se soucie que de quelques Equals
méthodes. La décision d'exiger que les opérateurs == et! = Soient définis en tandem par l'utilisateur est uniquement liée à la conception du langage et non à la sémantique des objets en ce qui concerne .NET.
la source
Réponses:
Je ne peux pas parler pour les concepteurs de langage, mais d'après ce que je peux raisonner, il semble que c'était une décision de conception intentionnelle et appropriée.
En regardant ce code F # de base, vous pouvez le compiler dans une bibliothèque de travail. C'est un code légal pour F #, et ne surcharge que l'opérateur d'égalité, pas l'inégalité:
Cela fait exactement à quoi il ressemble. Il crée un comparateur d'égalité
==
uniquement et vérifie si les valeurs internes de la classe sont égales.Bien que vous ne puissiez pas créer une classe comme celle-ci en C #, vous pouvez en utiliser une qui a été compilée pour .NET. Il est évident qu'il utilisera notre opérateur surchargé pour
==
Alors, à quoi sert le runtime!=
?La norme C # EMCA contient tout un tas de règles (section 14.9) expliquant comment déterminer quel opérateur utiliser lors de l'évaluation de l'égalité. Pour le dire trop simplifié et donc pas parfaitement précis, si les types qui sont comparés sont du même type et qu'un opérateur d'égalité surchargé est présent, il utilisera cette surcharge et non l'opérateur d'égalité de référence standard hérité d'Object. Il n'est donc pas surprenant que si un seul des opérateurs est présent, il utilisera l'opérateur d'égalité de référence par défaut, que tous les objets ont, il n'y a pas de surcharge pour lui. 1
Sachant que c'est le cas, la vraie question est: pourquoi a-t-il été conçu de cette manière et pourquoi le compilateur ne le comprend-il pas tout seul? Beaucoup de gens disent que ce n'était pas une décision de conception, mais j'aime à penser que cela a été pensé de cette façon, en particulier en ce qui concerne le fait que tous les objets ont un opérateur d'égalité par défaut.
Alors, pourquoi le compilateur ne crée-t-il pas automatiquement l'
!=
opérateur? Je ne peux pas être sûr à moins que quelqu'un de Microsoft ne le confirme, mais c'est ce que je peux déterminer en raisonnant sur les faits.Pour éviter un comportement inattendu
Peut-être que je veux faire une comparaison de valeurs
==
pour tester l'égalité. Cependant, en ce qui concerne,!=
je ne me soucie pas du tout si les valeurs sont égales à moins que la référence soit égale, car pour que mon programme les considère comme égales, je ne me soucie que si les références correspondent. Après tout, cela est en fait décrit comme le comportement par défaut du C # (si les deux opérateurs n'étaient pas surchargés, comme ce serait le cas pour certaines bibliothèques .net écrites dans une autre langue). Si le compilateur ajoutait du code automatiquement, je ne pouvais plus compter sur le compilateur pour produire du code qui devrait être conforme. Le compilateur ne doit pas écrire de code caché qui modifie le comportement du vôtre, en particulier lorsque le code que vous avez écrit respecte les normes de C # et de CLI.En ce qui vous oblige à le surcharger, au lieu d'aller au comportement par défaut, je peux seulement dire fermement qu'il est dans la norme (EMCA-334 17.9.2) 2 . La norme ne précise pas pourquoi. Je crois que cela est dû au fait que C # emprunte beaucoup de comportement à C ++. Voir ci-dessous pour en savoir plus.
Lorsque vous remplacez
!=
et==
, vous n'avez pas à renvoyer bool.C'est une autre raison probable. En C #, cette fonction:
est aussi valide que celui-ci:
Si vous retournez autre chose que bool, le compilateur ne peut pas déduire automatiquement un type opposé. En outre, dans le cas où votre opérateur ne bool retour, il n'a tout simplement pas de sens pour eux de créer de générer un code qui existerait seulement dans ce un cas spécifique ou, comme je l' ai dit plus haut, le code qui cache le comportement par défaut du CLR.
C # emprunte beaucoup au C ++ 3
Lorsque C # a été introduit, il y avait un article dans le magazine MSDN qui écrivait, parlant de C #:
Oui, l'objectif de conception pour C # était de donner presque la même quantité de puissance que C ++, en ne sacrifiant qu'un peu pour des commodités telles que la sécurité de type rigide et la collecte des ordures. C # a été fortement modélisé après C ++.
Vous ne serez peut-être pas surpris d'apprendre qu'en C ++, les opérateurs d'égalité n'ont pas à renvoyer bool , comme le montre cet exemple de programme
Maintenant, C ++ ne vous oblige pas directement à surcharger l'opérateur complémentaire. Si vous avez compilé le code dans l'exemple de programme, vous verrez qu'il s'exécute sans erreur. Cependant, si vous avez essayé d'ajouter la ligne:
tu auras
Ainsi, bien que C ++ lui-même ne vous oblige pas à surcharger par paires, il ne vous permettra pas d'utiliser un opérateur d'égalité que vous n'avez pas surchargé sur une classe personnalisée. Il est valide dans .NET, car tous les objets en ont un par défaut; C ++ ne fonctionne pas.
1. En remarque, la norme C # vous oblige toujours à surcharger la paire d'opérateurs si vous voulez surcharger l'un ou l'autre. Ceci fait partie de la norme et pas simplement du compilateur . Cependant, les mêmes règles concernant la détermination de l'opérateur à appeler s'appliquent lorsque vous accédez à une bibliothèque .net écrite dans une autre langue qui n'a pas les mêmes exigences.
2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )
3. Et Java, mais ce n'est vraiment pas le point ici
la source
==
de retourner autre chose qu'unbool
? Si vous retournez unint
, vous ne pouvez plus l'utiliser comme instruction conditionnelle, par exemple.if(a == b)
ne compilera plus. À quoi ça sert?Probablement si quelqu'un a besoin d'implémenter une logique à trois valeurs (c.
null
-à-d.). Dans des cas comme celui-ci - SQL standard ANSI, par exemple - les opérateurs ne peuvent pas simplement être annulés en fonction de l'entrée.Vous pourriez avoir un cas où:
Et
a == true
revientfalse
eta == false
revient égalementfalse
.la source
a
n'est pas un booléen, vous devriez le comparer avec une énumération à trois états, pas un réeltrue
oufalse
.a == true
c'est le cas,false
je m'attendrais toujoursa != true
à l'êtretrue
, malgré lea == false
faitfalse
==
, pas pourquoi il devrait être forcé de passer outre les deux.!=
serait correcte. Dans le cas extrêmement rare où nous voudrionsx == y
et quex != y
les deux soient faux (je ne peux pas penser à un seul exemple qui ait du sens) , ils pourraient toujours remplacer l'implémentation par défaut et fournir la leur. Toujours faire du cas commun la valeur par défaut!À part cela, C # diffère en C ++ dans de nombreux domaines, la meilleure explication à laquelle je peux penser est que dans certains cas, vous voudrez peut-être prendre un peu approche différente pour prouver "pas l'égalité" que pour prouver "l'égalité".
Évidemment, avec la comparaison de chaînes, par exemple, vous pouvez simplement tester l'égalité et
return
sortir de la boucle lorsque vous voyez des caractères qui ne correspondent pas. Cependant, il pourrait ne pas être aussi propre avec des problèmes plus compliqués. Le filtre de floraison me vient à l'esprit; il est très facile de dire rapidement si l'élément n'est pas dans l'ensemble, mais difficile de dire si l'élément est dans l'ensemble. Bien que la mêmereturn
technique puisse s'appliquer, le code n'est peut-être pas aussi joli.la source
!
vous enferme dans une définition unique de l'égalité, où un codeur averti pourrait être en mesure d'optimiser les deux==
et!=
.Si vous regardez les implémentations de surcharges de == et! = Dans la source .net, elles n'implémentent souvent pas! = Comme! (Gauche == droite). Ils l'implémentent complètement (comme ==) avec une logique négative. Par exemple, DateTime implémente == comme
et! = as
Si vous (ou le compilateur le faisait implicitement) deviez implémenter! = As
alors vous faites une hypothèse sur l'implémentation interne de == et! = dans les choses que votre classe référence. Éviter cette hypothèse peut être la philosophie derrière leur décision.
la source
Pour répondre à votre modification, concernant la raison pour laquelle vous êtes obligé de remplacer les deux si vous en remplacez un, tout est dans l'héritage.
Si vous remplacez ==, le plus susceptible de fournir une sorte d'égalité sémantique ou structurelle (par exemple, DateTimes sont égaux si leurs propriétés InternalTicks sont égales même si elles peuvent être différentes instances), alors vous changez le comportement par défaut de l'opérateur de Object, qui est le parent de tous les objets .NET. L'opérateur == est, en C #, une méthode dont l'implémentation de base Object.operator (==) effectue une comparaison référentielle. Object.operator (! =) Est une autre méthode différente, qui effectue également une comparaison référentielle.
Dans presque tous les autres cas de substitution de méthode, il serait illogique de présumer que la substitution d'une méthode entraînerait également un changement de comportement vers une méthode antonymique. Si vous avez créé une classe avec les méthodes Increment () et Decrement () et que vous avez remplacé Increment () dans une classe enfant, vous attendriez-vous à ce que Decrement () soit également remplacé par le contraire de votre comportement substitué? Le compilateur ne peut pas être rendu suffisamment intelligent pour générer une fonction inverse pour toute implémentation d'un opérateur dans tous les cas possibles.
Cependant, les opérateurs, bien qu'implémentés de manière très similaire aux méthodes, fonctionnent conceptuellement par paires; == et! =, <et> et <= et> =. Il serait illogique dans ce cas du point de vue d'un consommateur de penser que! = Fonctionne différemment de ==. Donc, le compilateur ne peut pas supposer que a! = B ==! (A == b) dans tous les cas, mais on s'attend généralement à ce que == et! = Fonctionnent de la même manière, donc le compilateur force vous mettre en œuvre par paires, mais vous finissez par le faire. Si, pour votre classe, a! = B ==! (A == b), alors implémentez simplement l'opérateur! = En utilisant! (==), mais si cette règle ne s'applique pas dans tous les cas à votre objet (par exemple , si la comparaison avec une valeur particulière, égale ou inégale, n'est pas valide), alors vous devez être plus intelligent que l'IDE.
La VRAIE question à poser est pourquoi <et> et <= et> = sont des paires d'opérateurs comparatifs qui doivent être implémentées simultanément, quand en termes numériques! (A <b) == a> = b et! (A> b) == a <= b. Vous devriez être tenu de mettre en œuvre les quatre si vous en remplacez un, et vous devriez probablement aussi être obligé de remplacer == (et! =), Car (a <= b) == (a == b) si a est sémantiquement égal à b.
la source
Si vous surchargez == pour votre type personnalisé, et non! = Alors il sera géré par l'opérateur! = Pour l'objet! = Objet car tout est dérivé de l'objet, et ce serait très différent de CustomType! = CustomType.
De plus, les créateurs de langage l'ont probablement voulu de cette façon pour permettre le plus de flexibilité possible aux codeurs, et aussi pour qu'ils ne fassent pas d'hypothèses sur ce que vous avez l'intention de faire.
la source
C'est ce qui me vient à l'esprit en premier:
false
fois pour==
et!=
(c'est-à-dire s'ils ne peuvent pas être comparés pour une raison quelconque)la source
Dictionary
etList
si vous utilisez votre type personnalisé avec elles.Dictionary
utiliseGetHashCode
etEquals
non les opérateurs.List
utiliseEquals
.Eh bien, c'est probablement juste un choix de conception, mais comme vous le dites,
x!= y
il ne doit pas nécessairement être le même que!(x == y)
. En n'ajoutant pas d'implémentation par défaut, vous êtes certain de ne pas oublier d'implémenter une implémentation spécifique. Et si c'est en effet aussi trivial que vous le dites, vous pouvez simplement implémenter l'un en utilisant l'autre. Je ne vois pas en quoi c'est une «mauvaise pratique».Il peut également y avoir d'autres différences entre C # et Lua ...
la source
Les mots clés de votre question sont « pourquoi » et « doit ».
Par conséquent:
Répondre de cette façon parce qu'ils l'ont conçu ainsi, c'est vrai ... mais ne pas répondre à la partie "pourquoi" de votre question.
Répondre qu'il peut parfois être utile de remplacer ces deux éléments indépendamment, est vrai ... mais ne pas répondre à la partie "incontournable" de votre question.
Je pense que la réponse simple est qu'il n'y a pas une raison convaincante pourquoi C # exige que vous de passer outre les deux.
La langue devrait vous permettre de passer outre que
==
, et vous fournir une implémentation par défaut de!=
c'est!
que. Si vous souhaitez également remplacer, essayez-le!=
.Ce n'était pas une bonne décision. Les humains conçoivent des langages, les humains ne sont pas parfaits, C # n'est pas parfait. Haussement d'épaules et QED
la source
Juste pour ajouter aux excellentes réponses ici:
Considérez ce qui se passerait dans le débogueur , lorsque vous essayez d'entrer dans un
!=
opérateur et de vous retrouver dans un==
opérateur à la place! Parlez de confusion!Il est logique que CLR vous permette de laisser de côté l'un ou l'autre des opérateurs - car il doit fonctionner avec de nombreuses langues. Mais il y a beaucoup d'exemples de C # ne pas exposer les caractéristiques CLR (
ref
retours et locaux, par exemple), et beaucoup d'exemples de mise en œuvre de fonctionnalités non dans le CLR lui - même (par exemple:using
,lock
,foreach
, etc.).la source
Les langages de programmation sont des réarrangements syntaxiques d'instructions logiques exceptionnellement complexes. Dans cet esprit, pouvez-vous définir un cas d'égalité sans définir un cas de non-égalité? La réponse est non. Pour qu'un objet a soit égal à l'objet b, alors l'inverse de l'objet a différent de b doit également être vrai. Une autre façon de le montrer est
if a == b then !(a != b)
cela permet au langage de déterminer l’égalité des objets. Par exemple, la comparaison NULL! = NULL peut jeter une clé dans la définition d'un système d'égalité qui n'implémente pas une déclaration de non-égalité.
Maintenant, en ce qui concerne l'idée de! = Étant simplement une définition remplaçable comme dans
if !(a==b) then a!=b
Je ne peux pas contester cela. Cependant, c'était probablement une décision du groupe de spécification du langage C # que le programmeur soit obligé de définir explicitement l'égalité et la non-égalité d'un objet ensemble
la source
Null
ne serait un problème que parce qu'ilnull
s'agit d'une entrée valide==
, ce qui ne devrait pas être le cas, bien qu'évidemment, c'est extrêmement pratique. Personnellement, je pense simplement que cela permet de vastes quantités de programmation vague et bâclée.Bref, une cohérence forcée.
'==' et '! =' sont toujours de vrais opposés, quelle que soit la façon dont vous les définissez, définis comme tels par leur définition verbale de "égal" et "non égal". En ne définissant qu'un seul d'entre eux, vous vous ouvrez à une incohérence d'opérateur d'égalité où '==' et '! =' Peuvent tous les deux être vrais ou tous les deux faux pour deux valeurs données. Vous devez définir les deux, car lorsque vous choisissez d'en définir un, vous devez également définir l'autre de manière appropriée afin de clarifier clairement votre définition de «l'égalité». L'autre solution pour le compilateur est de vous autoriser uniquement à remplacer '==' OU '! =' Et à laisser l'autre comme négativement intrinsèque à l'autre. Évidemment, ce n'est pas le cas avec le compilateur C # et je suis sûr qu'il y a '
La question que vous devriez vous poser est "pourquoi dois-je remplacer les opérateurs?" C'est une décision forte à prendre qui nécessite un raisonnement fort. Pour les objets, '==' et '! =' Comparent par référence. Si vous devez les remplacer pour ne PAS comparer par référence, vous créez une incohérence d'opérateur générale qui n'est apparente à aucun autre développeur qui lirait ce code. Si vous essayez de poser la question «l'état de ces deux instances est-il équivalent?», Vous devez implémenter IEquatible, définir Equals () et utiliser cet appel de méthode.
Enfin, IEquatable () ne définit pas NotEquals () pour le même raisonnement: possibilité d'ouvrir des incohérences d'opérateur d'égalité. NotEquals () doit TOUJOURS retourner! Equals (). En ouvrant la définition de NotEquals () à la classe implémentant Equals (), vous forcez à nouveau la question de la cohérence dans la détermination de l'égalité.
Edit: C'est tout simplement mon raisonnement.
la source
operator ==
et le compilateur en déduiraitoperator !=
. Après tout, c'est ce qu'il fait pouroperator +
etoperator +=
.a != b
comme!(a == b)
... ( ce qui est pas ce que C # fait).Probablement juste une chose à laquelle ils n'avaient pas pensé n'avait pas le temps de faire.
J'utilise toujours votre méthode lorsque je surcharge ==. Ensuite, je l'utilise simplement dans l'autre.
Vous avez raison, avec un peu de travail, le compilateur pourrait nous le donner gratuitement.
la source