358 Stimmen

Warum müssen wir in C# sowohl == als auch != definieren?

Der C#-Compiler verlangt, dass immer dann, wenn ein benutzerdefinierter Typ den Operator == muss sie auch definieren != (siehe hier ).

Warum?

Ich bin neugierig, warum die Entwickler dies für notwendig hielten und warum der Compiler nicht auf eine vernünftige Implementierung für einen der beiden Operatoren zurückgreifen kann, wenn nur der andere vorhanden ist. In Lua kann man zum Beispiel nur den Gleichheitsoperator definieren und bekommt den anderen umsonst. C# könnte dasselbe tun, indem es Sie auffordert, entweder == oder sowohl == als auch != zu definieren, und dann den fehlenden !=-Operator automatisch kompiliert als !(left == right) .

Ich verstehe, dass es seltsame Eckfälle gibt, in denen einige Einheiten weder gleich noch ungleich sein können (wie IEEE-754 NaN's), aber diese scheinen die Ausnahme zu sein, nicht die Regel. Das erklärt also nicht, warum die Entwickler des C#-Compilers die Ausnahme zur Regel gemacht haben.

Ich habe Fälle gesehen, in denen der Gleichheitsoperator definiert wurde und der Ungleichheitsoperator ein Copy-Paste ist, bei dem jeder einzelne Vergleich umgekehrt und jedes && in einen || umgewandelt wurde (Sie verstehen schon... im Grunde !(a==b) erweitert durch De Morgans Regeln). Das ist eine schlechte Praxis, die der Compiler durch Design eliminieren könnte, wie es bei Lua der Fall ist.

Anmerkung: Das Gleiche gilt für die Operatoren < > <= >=. Ich kann mir keine Fälle vorstellen, in denen man diese auf unnatürliche Weise definieren muss. Lua lässt Sie nur < und <= definieren und definiert >= und > auf natürliche Weise durch die Negation der Formers. Warum tut C# nicht dasselbe (zumindest "standardmäßig")?

EDIT

Offensichtlich gibt es triftige Gründe dafür, dem Programmierer zu erlauben, Prüfungen auf Gleichheit und Ungleichheit nach Belieben zu implementieren. Einige der Antworten weisen auf Fälle hin, in denen das gut sein könnte.

Der Kern meiner Frage ist jedoch, warum dies zwangsweise in C# erforderlich ist, wenn in der Regel es ist nicht logischerweise notwendig?

Es steht auch in auffälligem Kontrast zu den Designentscheidungen für .NET-Schnittstellen wie Object.Equals , IEquatable.Equals IEqualityComparer.Equals wo das Fehlen eines NotEquals Gegenstück zeigt, dass der Rahmen Folgendes berücksichtigt !Equals() Objekte als ungleich und das war's. Außerdem werden Klassen wie Dictionary und Methoden wie .Contains() hängen ausschließlich von den vorgenannten Schnittstellen ab und verwenden die Operatoren nicht direkt, auch wenn sie definiert sind. In der Tat, wenn ReSharper Gleichheitsmitglieder erzeugt, definiert es sowohl == y != in Bezug auf Equals() und selbst dann nur, wenn der Benutzer beschließt, überhaupt Operatoren zu erzeugen. Die Gleichheitsoperatoren werden vom Framework nicht benötigt, um Objektgleichheit zu verstehen.

Im Grunde genommen kümmert sich das .NET-Framework nicht um diese Operatoren, es kümmert sich nur um ein paar Equals Methoden. Die Entscheidung, dass die Operatoren == und != vom Benutzer gemeinsam definiert werden müssen, hat rein mit dem Sprachdesign zu tun und nicht mit der Objektsemantik, soweit es .NET betrifft.

28 Stimmen

+1 für eine ausgezeichnete Frage. Es scheint überhaupt keinen Grund zu geben... natürlich könnte der Compiler davon ausgehen, dass a != b in !(a == b) übersetzt werden kann, solange ihm nichts anderes gesagt wird. Ich bin auch neugierig, warum man sich dazu entschlossen hat, dies zu tun.

0 Stimmen

Wenn es so einfach ist wie !(left == right) dann ist das kein Problem für Sie. Wenn die Negation viel komplizierter ist, hat der Compiler keine Chance zu optimieren - aber Sie. Daher ist es nur konsequent, Sie zu zwingen, beide Fälle selbst zu behandeln.

3 Stimmen

Die goldene Regel Nr. 1 bei der Entwicklung von Software, APIs und GUIs lautet: Machen Sie immer den Normalfall zum Standard! Es sieht so aus, als hätte das jemand versehentlich nicht getan.

167voto

Christopher Currens Punkte 28587

Ich kann nicht für die Sprachdesigner sprechen, aber soweit ich das beurteilen kann, scheint es eine absichtliche, richtige Designentscheidung gewesen zu sein.

Wenn Sie sich diesen grundlegenden F#-Code ansehen, können Sie ihn zu einer funktionierenden Bibliothek kompilieren. Dies ist legaler Code für F# und überlädt nur den Gleichheitsoperator, nicht die Ungleichung:

module Module1

type Foo() =
    let mutable myInternalValue = 0
    member this.Prop
        with get () = myInternalValue
        and set (value) = myInternalValue <- value

    static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
    //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

Es tut genau das, wonach es aussieht. Es erstellt einen Gleichheitsvergleicher auf == und prüft, ob die internen Werte der Klasse gleich sind.

Sie können eine solche Klasse zwar nicht in C# erstellen, aber Sie können kann verwenden Sie eine, die für .NET kompiliert wurde. Es ist offensichtlich, dass es unseren überladenen Operator für == Was verwendet die Laufzeitumgebung also für != ?

Der C# EMCA-Standard enthält eine ganze Reihe von Regeln (Abschnitt 14.9), die erklären, wie man festlegt, welcher Operator bei der Auswertung von Gleichheit zu verwenden ist. Um es stark vereinfacht und daher nicht ganz korrekt auszudrücken, wenn die zu vergleichenden Typen vom gleichen Typ sind und ein überladener Gleichheitsoperator vorhanden ist, wird dieser Überladungsoperator und nicht der von Object geerbte Standardreferenzgleichheitsoperator verwendet. Es ist also keine Überraschung, dass, wenn nur einer der Operatoren vorhanden ist, der Standard-Referenzgleichheitsoperator verwendet wird, den alle Objekte haben, da es keine Überladung dafür gibt. 1

Wenn man weiß, 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 Design-Entscheidung war, aber ich denke, dass es so gedacht war, insbesondere im Hinblick auf die Tatsache, dass alle Objekte einen Standard-Gleichheitsoperator haben.

Warum erstellt der Compiler also nicht automatisch die != Betreiber? Ich kann es nicht mit Sicherheit wissen, es sei denn, jemand von Microsoft bestätigt dies, aber das ist es, was ich aufgrund der Faktenlage feststellen kann.


Um unerwartetes Verhalten zu verhindern

Vielleicht möchte ich einen Wertevergleich durchführen für == um die Gleichheit zu testen. Als es jedoch darum ging != Es war mir völlig egal, ob die Werte gleich waren, es sei denn, die Referenz war gleich, denn damit mein Programm sie als gleich ansieht, ist es mir nur wichtig, dass die Referenzen übereinstimmen. Schließlich ist dies als Standardverhalten von C# vorgesehen (wenn beide Operatoren nicht überladen sind, wie es bei einigen in einer anderen Sprache geschriebenen .net-Bibliotheken der Fall ist). Wenn der Compiler automatisch Code hinzufügt, kann ich mich nicht mehr darauf verlassen, dass der Compiler Code ausgibt, der konform sein sollte. Der Compiler sollte keinen versteckten Code schreiben, der das Verhalten des eigenen Codes verändert, insbesondere wenn der von Ihnen geschriebene Code den Standards von C# und der CLI entspricht.

In Bezug auf sie erzwingen. Sie zu überladen, anstatt auf das Standardverhalten zurückzugreifen, kann ich nur mit Nachdruck sagen, dass es in der Norm steht (EMCA-334 17.9.2) 2 . In der Norm wird nicht angegeben, warum. Ich glaube, dass dies auf die Tatsache zurückzuführen ist, dass C# viel Verhalten von C++ entlehnt. Siehe unten für mehr dazu.


Wenn Sie die != y == müssen Sie nicht bool zurückgeben.

Dies ist ein weiterer wahrscheinlicher Grund. In C#, diese Funktion:

public static int operator ==(MyClass a, MyClass b) { return 0; }

ist ebenso gültig wie diese:

public static bool operator ==(MyClass a, MyClass b) { return true; }

Wenn Sie etwas anderes als bool zurückgeben, wird der Compiler kann nicht automatisch auf einen anderen Typus schließen. Darüber hinaus wird in dem Fall, dass Ihr Operator tut return bool, es macht einfach keinen Sinn für sie erstellen Code, der nur in diesem einen bestimmten Fall oder, wie ich oben sagte, Code, der das Standardverhalten der CLR versteckt existieren würde.


C# nimmt viele Anleihen bei C++ 3

Als C# eingeführt wurde, gab es einen Artikel im MSDN-Magazin, in dem über C# gesprochen wurde:

Viele Entwickler wünschen sich eine Sprache, die so einfach zu schreiben, zu lesen und zu pflegen ist wie Visual Basic, die aber dennoch die Leistungsfähigkeit und Flexibilität von C++ bietet.

Ja, das Design-Ziel für C# war es, fast die gleiche Leistung wie C++ zu bieten und nur ein wenig für Annehmlichkeiten wie starre Typsicherheit und Garbage-Collection zu opfern. C# wurde stark nach C++ modelliert.

Es wird Sie vielleicht nicht überraschen zu erfahren, dass in C++ die Gleichheitsoperatoren müssen nicht bool zurückgeben , wie in dieses Beispielprogramm

Nun, C++ bietet nicht direkt erfordern Sie können den Komplementäroperator überlasten. Wenn Sie den Code im Beispielprogramm kompiliert haben, werden Sie sehen, dass er ohne Fehler läuft. Wenn Sie jedoch versuchen, die Zeile hinzuzufügen:

cout << (a != b);

erhalten Sie

Compiler-Fehler C2678 (MSVC) : binary '!=' : no operator found which takes a left-hand operand of type 'Test' (or there is no acceptable conversion)`.

C++ selbst verlangt zwar nicht, dass man paarweise überladen kann, aber es wird nicht können Sie einen Gleichheitsoperator verwenden, den Sie nicht auf eine benutzerdefinierte Klasse überladen haben. Er ist in .NET gültig, weil alle Objekte einen Standardoperator haben; in C++ ist das nicht der Fall.


1. Nebenbei bemerkt erfordert der C#-Standard immer noch, dass Sie die beiden Operatoren überladen, wenn Sie einen von ihnen überladen wollen. Dies ist ein Teil der Standard und nicht einfach die Compiler . Die gleichen Regeln bezüglich der Bestimmung des aufzurufenden Operators gelten jedoch auch, wenn Sie auf eine .net-Bibliothek zugreifen, die in einer anderen Sprache geschrieben wurde, die nicht die gleichen Anforderungen hat.

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

82 Stimmen

" Sie müssen nicht bool zurückgeben "Ich denke, das ist unsere Antwort; gut gemacht. Wenn !(o1 == o2) nicht einmal wohldefiniert ist, dann kann man nicht erwarten, dass sich die Norm darauf stützt.

13 Stimmen

Ironischerweise haben Sie in einem Thema über logische Operatoren ein doppeltes Negativ in dem Satz "C# erlaubt es Ihnen nicht, nur eines von beiden zu überschreiben" verwendet, was eigentlich logisch bedeutet "C# erlaubt es Ihnen, nur eines von beiden zu überschreiben".

18 Stimmen

@Dan Diplo, Es ist okay, es ist ja nicht so, als wäre es mir verboten, das nicht zu tun oder nicht.

55voto

Yuck Punkte 46967

Wahrscheinlich für den Fall, dass jemand eine dreiwertige Logik implementieren muss (d.h. null ). In solchen Fällen - z. B. ANSI-Standard-SQL - können die Operatoren nicht einfach je nach Eingabe negiert werden.

Sie könnten einen Fall haben, in dem:

var a = SomeObject();

Und a == true retours false y a == false gibt auch zurück false .

1 Stimmen

Ich würde sagen, in diesem Fall, da a kein Boolescher Wert ist, sollten Sie ihn mit einer dreistufigen Aufzählung vergleichen, nicht mit einem echten true o false .

1 Stimmen

@Brian Gordon: Wahrscheinlich richtig. Mein Beispiel ist vielleicht ein wenig konstruiert, aber ich glaube, dass die Überlegungen dahinter immer noch zutreffen. Es gibt Fälle, in denen Sie nicht wollen != automatisch (und unabänderlich) als das logische Gegenteil von == .

1 Stimmen

@Yuck: Aber warum definiert der Benutzer in diesem Fall nicht, was er mit "!=" meint? Eine vernünftige Reaktion wäre in diesem Fall höchstens die Ausgabe einer Warnung, wenn die Kompilierung pedantisch ist.

25voto

Rag Punkte 6151

Abgesehen davon, dass C# in vielen Bereichen C++ unterlegen ist, ist die beste Erklärung, die mir einfällt, dass man in manchen Fällen eine leicht eine andere Herangehensweise an den Nachweis der "Nicht-Gleichheit" als an den Nachweis der "Gleichheit".

Beim Vergleich von Zeichenketten zum Beispiel können Sie einfach auf Gleichheit testen und return aus der Schleife, wenn Sie nicht übereinstimmende Zeichen sehen. Bei komplizierteren Problemen ist dies jedoch möglicherweise nicht so sauber. Die Glockenfilter kommt mir in den Sinn; es ist sehr einfach, schnell zu erkennen, ob das Element nicht in der Menge, aber schwer zu sagen, ob das Element ist im Set. Während die gleiche return Technik angewandt werden könnte, wäre der Code vielleicht nicht so schön.

6 Stimmen

Tolles Beispiel; ich glaube, Sie haben den Grund dafür erkannt. Eine einfache ! sperrt Sie in eine einzige Definition für Gleichheit, wo ein informierter Programmierer in der Lage sein könnte, zu optimieren beide == y != .

15 Stimmen

Dies erklärt, warum sie die Möglichkeit erhalten sollten, die Option beides außer Kraft zu setzen, und nicht, warum sie es sein sollten gezwungen zu.

1 Stimmen

In diesem Sinne müssen sie nicht optimieren beide, sie müssen nur eine klare Definition liefern von beidem.

23voto

Wenn man sich die Implementierungen der Überladungen von == und != im .net-Quelltext ansieht, wird != oft nicht als !(links == rechts) implementiert. Sie implementieren es vollständig (wie ==) mit negierter Logik. Zum Beispiel implementiert DateTime == als

return d1.InternalTicks == d2.InternalTicks;

und != als

return d1.InternalTicks != d2.InternalTicks;

Wenn Sie (oder der Compiler, wenn er es implizit tut) != als

return !(d1==d2);

dann machen Sie eine Vermutung über die interne Implementierung von == und != in den Dingen, auf die Ihre Klasse verweist. Diese Annahme zu vermeiden, könnte die Philosophie hinter ihrer Entscheidung sein.

17voto

KeithS Punkte 67713

Um Ihre Bearbeitung zu beantworten, warum Sie gezwungen sind, beide zu überschreiben, wenn Sie eine überschreiben, es ist alles in der Vererbung.

Wenn Sie == überschreiben, höchstwahrscheinlich, um eine Art semantische oder strukturelle Gleichheit bereitzustellen (z. B. sind DateTimes gleich, wenn ihre InternalTicks-Eigenschaften gleich sind, auch wenn es sich um unterschiedliche Instanzen handelt), dann ändern Sie das Standardverhalten des Operators von Object, dem Elternteil aller .NET-Objekte. Der == Operator ist in C# eine Methode, deren Basisimplementierung Object.operator(==) einen referenziellen Vergleich durchführt. Object.operator(!=) ist eine weitere, andere Methode, die ebenfalls einen referenziellen Vergleich durchführt.

In fast jedem anderen Fall von Methodenüberschreibung wäre es unlogisch anzunehmen, dass die Überschreibung einer Methode auch zu einem Verhaltenswechsel zu einer antonymischen Methode führen würde. Wenn Sie eine Klasse mit Increment()- und Decrement()-Methoden erstellen und Increment() in einer untergeordneten Klasse überschreiben, würden Sie dann erwarten, dass auch Decrement() mit dem Gegenteil des überschriebenen Verhaltens überschrieben wird? Der Compiler kann nicht so intelligent sein, dass er für jede Implementierung eines Operators in allen möglichen Fällen eine inverse Funktion erzeugt.

Obwohl Operatoren sehr ähnlich wie Methoden implementiert sind, arbeiten sie konzeptionell in Paaren: == und !=, < und > sowie <= und >=. In diesem Fall wäre es vom Standpunkt des Verbrauchers aus unlogisch zu denken, dass != anders funktioniert als ==. Man kann den Compiler also nicht dazu bringen, anzunehmen, dass a!=b == !(a==b) in allen Fällen funktioniert, aber es wird im Allgemeinen erwartet, dass == und != auf ähnliche Weise funktionieren, so dass der Compiler Sie zwingt, paarweise zu implementieren, wie auch immer Sie das letztendlich tun. Wenn für Ihre Klasse a!=b == !(a==b) gilt, dann implementieren Sie den !=-Operator einfach 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), dann müssen Sie schlauer sein als die IDE.

Die WIRKLICHE Frage, die man sich stellen sollte, ist, warum < und > und <= und >= Paare für vergleichende Operatoren sind, die gleichzeitig implementiert werden müssen, wenn in numerischen Begriffen !(a < b) == a >= b und !(a > b) == a <= b. Man sollte verpflichtet sein, alle vier zu implementieren, wenn man einen überschreibt, und man sollte wahrscheinlich verpflichtet sein, auch == (und !=) zu überschreiben, denn (a <= b) == (a == b), wenn a semantisch gleich b ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X