Der C # -Compiler verlangt, dass ein benutzerdefinierter Typ, der den Operator definiert ==
, auch definiert werden muss !=
(siehe hier ).
Warum?
Ich bin gespannt, warum die Designer dies für notwendig hielten und warum der Compiler nicht für einen der Operatoren eine vernünftige Implementierung verwenden kann, wenn nur der andere vorhanden ist. Mit Lua können Sie beispielsweise nur den Gleichheitsoperator definieren und den anderen kostenlos erhalten. C # könnte dasselbe tun, indem Sie aufgefordert werden, entweder == oder beide == und! = Zu definieren und dann den fehlenden Operator! = Automatisch als zu kompilieren !(left == right)
.
Ich verstehe, dass es seltsame Eckfälle gibt, in denen einige Entitäten möglicherweise weder gleich noch ungleich sind (wie IEEE-754 NaNs), aber diese scheinen die Ausnahme und nicht die Regel zu sein. Dies erklärt also nicht, warum die C # -Compiler-Designer die Ausnahme zur Regel gemacht haben.
Ich habe Fälle von schlechter Verarbeitung gesehen, in denen der Gleichheitsoperator definiert ist, dann ist der Ungleichheitsoperator ein Copy-Paste, bei dem jeder Vergleich umgekehrt und jedes && auf ein || umgeschaltet wird (Sie verstehen ... im Grunde! (a == b) erweitert durch De Morgans Regeln). Das ist eine schlechte Praxis, die der Compiler durch Design beseitigen könnte, wie es bei Lua der Fall ist.
Hinweis: Gleiches gilt für Operatoren <> <=> =. Ich kann mir keine Fälle vorstellen, in denen Sie diese auf unnatürliche Weise definieren müssen. Mit Lua können Sie nur <und <= und> = und> natürlich durch die Negation der Formers definieren. Warum macht C # nicht dasselbe (zumindest "standardmäßig")?
BEARBEITEN
Anscheinend gibt es triftige Gründe, dem Programmierer zu erlauben, Überprüfungen auf Gleichheit und Ungleichheit durchzuführen, wie sie möchten. Einige der Antworten weisen auf Fälle hin, in denen dies hilfreich sein kann.
Der Kern meiner Frage ist jedoch, warum dies in C # zwangsweise erforderlich ist, wenn dies normalerweise nicht logisch erforderlich ist.
Es steht auch in auffallendem Kontrast zu Entwurfsentscheidungen für .NET-Schnittstellen wie Object.Equals
, bei IEquatable.Equals
IEqualityComparer.Equals
denen das Fehlen eines NotEquals
Gegenstücks zeigt, dass das Framework !Equals()
Objekte als ungleich betrachtet, und das ist es. Darüber hinaus hängen Klassen wie Dictionary
und Methoden wie .Contains()
ausschließlich von den oben genannten Schnittstellen ab und verwenden die Operatoren nicht direkt, selbst wenn sie definiert sind. Wenn ReSharper Gleichheitsmitglieder generiert, definiert es sowohl ==
als auch !=
in Bezug auf Equals()
und selbst dann nur dann, wenn der Benutzer überhaupt Operatoren generiert. Die Gleichheitsoperatoren werden vom Framework nicht benötigt, um die Objektgleichheit zu verstehen.
Grundsätzlich kümmert sich das .NET Framework nicht um diese Operatoren, sondern nur um einige Equals
Methoden. Die Entscheidung, dass sowohl == als auch! = Operatoren vom Benutzer gemeinsam definiert werden müssen, bezieht sich ausschließlich auf das Sprachdesign und nicht auf die Objektsemantik in Bezug auf .NET.
quelle
Antworten:
Ich kann nicht für die Sprachdesigner sprechen, aber nach allem, was ich begründen kann, scheint es eine absichtliche, richtige Designentscheidung zu sein.
Wenn Sie sich diesen grundlegenden F # -Code ansehen, können Sie diesen in eine funktionierende Bibliothek kompilieren. Dies ist ein gesetzlicher Code für F # und überlastet nur den Gleichheitsoperator, nicht die Ungleichung:
Das macht genau das, wie es aussieht. Es wird nur ein Gleichheitsvergleich erstellt
==
und überprüft, ob die internen Werte der Klasse gleich sind.Während Sie nicht eine Klasse wie dies in C # erstellen, Sie können eine , die für .NET kompiliert wurde. Es ist offensichtlich, dass unser überladener Operator für verwendet
==
wird. Wofür wird die Laufzeit verwendet!=
?Der C # EMCA-Standard enthält eine ganze Reihe von Regeln (Abschnitt 14.9), in denen erläutert wird, wie bestimmt wird, welcher Operator bei der Bewertung der Gleichheit verwendet werden soll. Um es zu vereinfachen und damit nicht ganz genau auszudrücken: Wenn die zu vergleichenden Typen vom gleichen Typ sind und ein überladener Gleichheitsoperator vorhanden ist, wird diese Überladung verwendet und nicht der von Object geerbte Standardreferenzgleichheitsoperator. Es ist daher nicht verwunderlich, dass, wenn nur einer der Operatoren vorhanden ist, der Standard-Referenzgleichheitsoperator verwendet wird, den alle Objekte haben, für den es keine Überladung gibt. 1
In dem Wissen, dass dies der Fall ist, lautet die eigentliche Frage: Warum wurde dies so entworfen und warum findet der Compiler es nicht selbst heraus? Viele Leute sagen, dass dies keine Entwurfsentscheidung war, aber ich denke gerne, dass dies so gedacht wurde, insbesondere in Bezug auf die Tatsache, dass alle Objekte einen Standardgleichheitsoperator haben.
Warum erstellt der Compiler den
!=
Operator nicht automatisch ? Ich kann es nicht genau wissen, es sei denn, jemand von Microsoft bestätigt dies, aber das kann ich anhand der Argumentation zu den Fakten feststellen.Um unerwartetes Verhalten zu verhindern
Vielleicht möchte ich einen Wertevergleich durchführen
==
, um die Gleichheit zu testen. Wenn es darum ging, war es!=
mir überhaupt egal, ob die Werte gleich waren, es sei denn, die Referenz war gleich, denn damit mein Programm sie als gleich betrachtet, ist es mir nur wichtig, ob die Referenzen übereinstimmen. Schließlich wird dies tatsächlich als Standardverhalten des C # beschrieben (wenn beide Operatoren nicht überladen wären, wie dies bei einigen in einer anderen Sprache geschriebenen .net-Bibliotheken der Fall wäre). Wenn der Compiler automatisch Code hinzufügte, konnte ich mich nicht mehr darauf verlassen, dass der Compiler Code ausgibt, der kompatibel sein sollte. Der Compiler sollte keinen versteckten Code schreiben, der Ihr Verhalten ändert, insbesondere wenn der von Ihnen geschriebene Code den Standards von C # und der CLI entspricht.In Bezug darauf , dass Sie gezwungen sind, es zu überlasten, anstatt zum Standardverhalten überzugehen, kann ich nur fest sagen, dass es im Standard (EMCA-334 17.9.2) 2 enthalten ist . Der Standard gibt nicht an, warum. Ich glaube, das liegt an der Tatsache, dass C # viel Verhalten von C ++ übernimmt. Weitere Informationen hierzu finden Sie weiter unten.
Wenn Sie
!=
und überschreiben==
, müssen Sie bool nicht zurückgeben.Dies ist ein weiterer wahrscheinlicher Grund. In C # ist diese Funktion:
ist so gültig wie dieser:
Wenn Sie etwas anderes als bool zurückgeben, kann der Compiler nicht automatisch auf einen entgegengesetzten Typ schließen. Ferner wird in dem Fall , in dem Ihr Bediener tut Rückkehr Bool, es macht einfach keinen Sinn machen für sie Code erzeugen generieren , die nur in diesem einen speziellen Fall existieren würde oder, wie ich oben sagte, Code, versteckt das Standardverhalten der CLR.
C # leiht sich viel von C ++ 3 aus
Als C # eingeführt wurde, gab es einen Artikel im MSDN-Magazin, der über C # schrieb:
Ja, das Entwurfsziel für C # bestand darin, fast die gleiche Leistung wie C ++ bereitzustellen und nur wenig für Annehmlichkeiten wie starre Typensicherheit und Speicherbereinigung zu opfern. C # wurde stark nach C ++ modelliert.
Sie werden nicht überrascht sein zu erfahren, dass in C ++ die Gleichheitsoperatoren kein bool zurückgeben müssen , wie in diesem Beispielprogramm gezeigt
Nun, C ++ nicht direkt benötigen Sie den komplementären Betreiber zu überlasten. Wenn Sie den Code im Beispielprogramm kompiliert haben, wird er fehlerfrei ausgeführt. Wenn Sie jedoch versucht haben, die Zeile hinzuzufügen:
Sie erhalten
Während C ++ selbst nicht erfordert, dass Sie paarweise überladen, wird dies nicht der Fall sein Gleichheitsoperator verwenden, den Sie für eine benutzerdefinierte Klasse nicht überladen haben. Es ist in .NET gültig, da alle Objekte ein Standardobjekt haben. C ++ nicht.
1. Nebenbei bemerkt, der C # -Standard verlangt weiterhin, dass Sie das Operatorpaar überladen, wenn Sie einen der Operatoren überladen möchten. Dies ist ein Teil des Standards und nicht nur der Compiler . Es gelten jedoch dieselben Regeln für die Bestimmung, welcher Operator aufgerufen werden soll, wenn Sie auf eine .net-Bibliothek zugreifen, die in einer anderen Sprache geschrieben ist, für die nicht dieselben Anforderungen gelten.
2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )
3. Und Java, aber darum geht es hier wirklich nicht
quelle
==
man etwas anderes als a zurückgeben darfbool
? Wenn Sie ein zurückgebenint
, können Sie es nicht mehr als bedingte Anweisung verwenden, z.if(a == b)
wird nicht mehr kompiliert. Was ist der Punkt?Wahrscheinlich, wenn jemand eine dreiwertige Logik implementieren muss (dh
null
). In solchen Fällen - zum Beispiel ANSI-Standard-SQL - können die Operatoren je nach Eingabe nicht einfach negiert werden.Sie könnten einen Fall haben, in dem:
Und
a == true
kehrtfalse
unda == false
auch zurückkehrtfalse
.quelle
a
es kein Boolescher Wert ist, sollten Sie ihn mit einer Aufzählung mit drei Zuständen vergleichen, nicht mit einer realentrue
oderfalse
.a == true
ist ,false
dann würde ich immer erwartena != true
zu seintrue
, obwohla == false
seinfalse
==
und nicht, warum er gezwungen werden sollte, beide zu überschreiben.!=
korrekt. In dem äußerst ungewöhnlichen Fall, in dem wir wollenx == y
undx != y
beide falsch sind (ich kann mir kein einziges sinnvolles Beispiel vorstellen) , könnten sie die Standardimplementierung dennoch überschreiben und ihre eigene bereitstellen. Machen Sie immer den allgemeinen Fall zum Standard!Abgesehen davon, dass C # in vielen Bereichen auf C ++ verzichtet, ist die beste Erklärung, die ich mir vorstellen kann, dass Sie in einigen Fällen eine leichte Abweichung wünschen anderen Ansatz zum Nachweis von "Nicht-Gleichheit" als zum Nachweis von "Gleichheit" .
Offensichtlich können Sie beim Vergleich von Zeichenfolgen beispielsweise nur auf Gleichheit und
return
außerhalb der Schleife testen, wenn Sie nicht übereinstimmende Zeichen sehen. Bei komplizierteren Problemen ist es jedoch möglicherweise nicht so sauber. Der Blütenfilter fällt mir ein; es ist sehr einfach zu schnell sagen , wenn das Element nicht in der Menge, aber schwer zu sagen , ob das Element ist in dem Satz. Obwohl die gleichereturn
Technik angewendet werden könnte, ist der Code möglicherweise nicht so hübsch.quelle
!
Sperren Sie in eine einheitliche Definition für die Gleichstellung, wo eine informierte Codierer der Lage sein könnte zu optimieren beide==
und!=
.Wenn Sie sich Implementierungen von Überladungen von == und! = In der .net-Quelle ansehen, implementieren sie häufig nicht! = As! (Links == rechts). Sie implementieren es vollständig (wie ==) mit negierter Logik. Beispielsweise implementiert DateTime == as
und! = as
Wenn Sie (oder der Compiler, wenn er es implizit getan hat)! = As implementieren würden
dann nehmen Sie eine Annahme über die interne Implementierung von == und! = in den Dingen an, auf die Ihre Klasse verweist. Diese Annahme zu vermeiden, könnte die Philosophie hinter ihrer Entscheidung sein.
quelle
Um Ihre Bearbeitung zu beantworten und zu erklären, warum Sie gezwungen sind, beide zu überschreiben, wenn Sie eine überschreiben, liegt alles in der Vererbung.
Wenn Sie == überschreiben und höchstwahrscheinlich eine Art semantische oder strukturelle Gleichheit bereitstellen (z. B. sind DateTimes gleich, wenn ihre InternalTicks-Eigenschaften gleich sind, auch wenn sie unterschiedliche Instanzen sein können), ändern Sie das Standardverhalten des Operators von Objekt, das das übergeordnete Objekt aller .NET-Objekte ist. Der Operator == ist in C # eine Methode, deren Basisimplementierung Object.operator (==) einen referenziellen Vergleich durchführt. Object.operator (! =) Ist eine andere, andere Methode, die auch einen referenziellen Vergleich durchführt.
In fast jedem anderen Fall des Überschreibens von Methoden wäre es unlogisch anzunehmen, dass das Überschreiben einer Methode auch zu einer Verhaltensänderung einer antonymischen Methode führen würde. Wenn Sie eine Klasse mit den Methoden Increment () und Decrement () erstellen und Increment () in einer untergeordneten Klasse überschreiben würden, würden Sie erwarten, dass Decrement () auch mit dem Gegenteil Ihres überschriebenen Verhaltens überschrieben wird? Der Compiler kann nicht intelligent genug gemacht werden, um in allen möglichen Fällen eine Umkehrfunktion für eine Implementierung eines Operators zu generieren.
Obwohl Operatoren den Methoden sehr ähnlich implementiert sind, arbeiten sie konzeptionell paarweise. == und! =, <und> und <= und> =. In diesem Fall wäre es aus Sicht eines Verbrauchers unlogisch zu glauben, dass! = Anders funktioniert als ==. Der Compiler kann also nicht in allen Fällen davon ausgehen, dass a! = B ==! (A == b), aber es wird allgemein erwartet, dass == und! = Auf ähnliche Weise funktionieren, sodass der Compiler dies erzwingt Sie müssen paarweise implementieren, aber am Ende tun Sie das tatsächlich. Wenn für Ihre Klasse a! = B ==! (A == b), implementieren Sie einfach den Operator! = Mit! (==), aber wenn diese Regel nicht in allen Fällen für Ihr Objekt gilt (zum Beispiel Wenn der Vergleich mit einem bestimmten Wert (gleich oder ungleich) nicht gültig ist, müssen Sie schlauer als die IDE sein.
Die WIRKLICHE Frage, die gestellt werden sollte, ist, warum <und> und <= und> = Paare für Vergleichsoperatoren sind, die gleichzeitig implementiert werden müssen, wenn sie numerisch ausgedrückt werden! (A <b) == a> = b und! (A>) b) == a <= b. Sie sollten alle vier implementieren müssen, wenn Sie eine überschreiben, und Sie sollten wahrscheinlich auch == (und! =) Überschreiben müssen, weil (a <= b) == (a == b), wenn a semantisch ist gleich b.
quelle
Wenn Sie == für Ihren benutzerdefinierten Typ überladen und nicht! =, Wird dies vom Operator! = Für Objekt! = Objekt behandelt, da alles vom Objekt abgeleitet ist, und dies würde sich stark von CustomType! = CustomType unterscheiden.
Auch die Sprachentwickler wollten es wahrscheinlich auf diese Weise, um den Programmierern die größtmögliche Flexibilität zu ermöglichen, und auch, damit sie keine Annahmen darüber treffen, was Sie vorhaben.
quelle
Das fällt mir zuerst ein:
false
==
!=
(dh wenn sie aus irgendeinem Grund nicht verglichen werden können) ?quelle
Dictionary
undList
wenn Sie Ihren benutzerdefinierten Typ mit ihnen verwenden.Dictionary
verwendetGetHashCode
undEquals
nicht die Operatoren.List
verwendetEquals
.Nun, es ist wahrscheinlich nur eine Design-Wahl, aber wie Sie sagen,
x!= y
muss es nicht dasselbe sein wie!(x == y)
. Wenn Sie keine Standardimplementierung hinzufügen, können Sie sicher sein, dass Sie nicht vergessen, eine bestimmte Implementierung zu implementieren. Und wenn es tatsächlich so trivial ist, wie Sie sagen, können Sie einfach eins mit dem anderen implementieren. Ich verstehe nicht, wie schlecht das ist.Es kann auch einige andere Unterschiede zwischen C # und Lua geben ...
quelle
Die Schlüsselwörter in Ihrer Frage sind " Warum " und " Muss ".
Als Ergebnis:
Es ist wahr, es so zu beantworten, weil sie es so entworfen haben ... aber nicht den "Warum" -Teil Ihrer Frage zu beantworten.
Die Antwort, dass es manchmal hilfreich sein kann, beide unabhängig voneinander zu überschreiben, ist wahr ... aber nicht die Beantwortung des "Muss" -Teils Ihrer Frage.
Ich denke , die einfache Antwort ist , dass es nicht ist kein überzeugender Grund , warum C # erfordert Sie beide außer Kraft zu setzen.
Die Sprache sollte es Ihnen ermöglicht , nur außer Kraft zu setzen
==
, und bieten Ihnen eine Standardimplementierung!=
das ist!
das. Wenn Sie zufällig auch überschreiben möchten!=
, haben Sie es.Es war keine gute Entscheidung. Menschen entwerfen Sprachen, Menschen sind nicht perfekt, C # ist nicht perfekt. Achselzucken und QED
quelle
Nur um die hervorragenden Antworten hier zu ergänzen:
Überlegen Sie, was im Debugger passieren würde , wenn Sie versuchen, in einen
!=
Operator einzusteigen und in einem zu landen==
stattdessen ! Sprechen Sie über Verwirrung!Es ist sinnvoll, dass CLR Ihnen die Freiheit gibt, den einen oder anderen Operator wegzulassen, da es mit vielen Sprachen funktionieren muss. Aber es gibt viele Beispiele für C # nicht CLR Funktionen aussetzt (
ref
kehrt und Einheimischer, zum Beispiel), und viele Beispiele von Funktionen , die nicht in dem CLR Implementierung selbst (zB:using
,lock
,foreach
, etc.).quelle
Programmiersprachen sind syntaktische Neuanordnungen außergewöhnlich komplexer logischer Anweisungen. Können Sie in diesem Sinne einen Fall der Gleichheit definieren, ohne einen Fall der Ungleichheit zu definieren? Die Antwort ist nein. Damit ein Objekt a gleich Objekt b ist, muss auch die Umkehrung von Objekt a ungleich b wahr sein. Eine andere Möglichkeit, dies zu zeigen, ist
if a == b then !(a != b)
Dies bietet der Sprache die eindeutige Fähigkeit, die Gleichheit von Objekten zu bestimmen. Zum Beispiel kann der Vergleich NULL! = NULL einen Schraubenschlüssel in die Definition eines Gleichheitssystems werfen, das keine Nichtgleichheitsanweisung implementiert.
Nun, in Bezug auf die Idee von! = Einfach austauschbare Definition wie in
if !(a==b) then a!=b
Damit kann ich nicht streiten. Es war jedoch höchstwahrscheinlich eine Entscheidung der C # -Sprachenspezifikationsgruppe, dass der Programmierer gezwungen sein sollte, die Gleichheit und Nichtgleichheit eines Objekts explizit zusammen zu definieren
quelle
Null
wäre nur ein Problem, weilnull
es sich um eine gültige Eingabe handelt==
, die es nicht sein sollte, obwohl es offensichtlich sehr praktisch ist. Persönlich denke ich nur, dass es Unmengen von vagen, schlampigen Programmen erlaubt.Kurz gesagt, erzwungene Konsistenz.
'==' und '! =' sind immer wahre Gegensätze, egal wie Sie sie definieren, definiert als solche durch ihre verbale Definition von "gleich" und "nicht gleich". Indem Sie nur einen von ihnen definieren, öffnen Sie sich einer Inkonsistenz des Gleichheitsoperators, bei der sowohl '==' als auch '! =' Für zwei gegebene Werte beide wahr oder beide falsch sein können. Sie müssen beide definieren, da Sie bei der Definition des einen auch den anderen entsprechend definieren müssen, damit klar ist, wie Sie "Gleichheit" definieren. Die andere Lösung für den Compiler besteht darin, nur zuzulassen, dass Sie '==' OR '! =' Überschreiben und den anderen als inhärent negierend für den anderen belassen. Offensichtlich ist das beim C # -Compiler nicht der Fall, und da bin ich mir sicher. '
Die Frage, die Sie stellen sollten, lautet: "Warum muss ich die Operatoren überschreiben?" Das ist eine starke Entscheidung, die eine starke Argumentation erfordert. Für Objekte vergleichen '==' und '! =' Als Referenz. Wenn Sie sie überschreiben möchten, um sie NICHT anhand einer Referenz zu vergleichen, erstellen Sie eine allgemeine Operatorinkonsistenz, die für keinen anderen Entwickler erkennbar ist, der diesen Code lesen würde. Wenn Sie versuchen, die Frage zu stellen, ob der Status dieser beiden Instanzen äquivalent ist, sollten Sie IEquatible implementieren, Equals () definieren und diesen Methodenaufruf verwenden.
Schließlich definiert IEquatable () NotEquals () nicht aus demselben Grund: Es kann zu Inkonsistenzen bei Gleichheitsoperatoren kommen. NotEquals () sollte IMMER zurückkehren! Equals (). Indem Sie die Definition von NotEquals () für die Klasse öffnen, die Equals () implementiert, erzwingen Sie erneut das Problem der Konsistenz bei der Bestimmung der Gleichheit.
Edit: Dies ist einfach meine Argumentation.
quelle
operator ==
und der Compiler würde daraus schließenoperator !=
. Immerhin ist es das, was es füroperator +
und tutoperator +=
.a != b
wie!(a == b)
... (das ist nicht das, was C # tut).Wahrscheinlich hatte nur etwas, an das sie nicht gedacht hatten, keine Zeit zu tun.
Ich benutze immer deine Methode, wenn ich == überlade. Dann benutze ich es einfach in der anderen.
Sie haben Recht, mit ein wenig Arbeit könnte der Compiler uns dies kostenlos zur Verfügung stellen.
quelle