Object.Equals in Struct überschreiben

  • C#

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Object.Equals in Struct überschreiben

    Hallo Forum,

    ich implementiere gerade Brüche in C#. Da ein Bruch keine Identität besitzt verwende ich Structs. Das Problem ist, dass ich den ==-Operator überschrieben habe aber nicht die Equals-Methode. Wenn ich das tue bekomme ich den Fehler, dass der as-Operator mit einem Referenztyp oder einem Typ, der NULL-Werte zulässt, stehen muss.

    C#-Quellcode

    1. public override bool Equals(Object obj)
    2. {
    3. Fraction fractionObj = obj as Fraction; // Fehler
    4. if (fractionObj == null)
    5. return false;
    6. else
    7. return fractionObj == this;
    8. }


    Der Fehler verschwindet, wenn ich Fraction als Klasse definiere, das möchte ich allerdings nicht. Jetzt hab ich zwei Fragen: Wie kommt es zu diesem Fehler und wie behebe ich ihn?

    Danke im Voraus

    @RushDen Nö.

    TheVBTutorialsVB schrieb:

    Wie kommt es zu diesem Fehler
    steht in der Fehlerbeschreibung:
    Der as-Operator muss mit einem Referenztyp oder einem Typ, der NULL-Werte zulässt, verwendet werden. ('Fraction' ist ein Werttyp, der keine NULL-Werte zulässt.)

    C#-Quellcode

    1. Fraction fractionObj = (Fraction)obj; // kein Fehler
    Eine Struktur-Instanz kann nucht null sein, da sie ein Wertetyp ist.
    Warnung:
    Warnung 1 'Fraction' überschreibt Object.Equals(object o), aber nicht Object.GetHashCode().
    Feddich.
    Fraction

    C#-Quellcode

    1. public struct Fraction
    2. {
    3. public int a;
    4. public int b;
    5. public override bool Equals(Object obj)
    6. {
    7. Fraction fractionObj;
    8. try
    9. {
    10. fractionObj = (Fraction)obj;
    11. // Member vergleichen
    12. return (fractionObj.a == this.a) && (fractionObj.b == this.b);
    13. }
    14. catch
    15. {
    16. // falscher Typ
    17. return false;
    18. }
    19. }
    20. public override int GetHashCode()
    21. {
    22. return base.GetHashCode();
    23. }
    24. }

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Erscheint mir äußerst suspekt, gerade von RodFromGermany Code zu sehen, der hier Try-Catch verwendet.

    C#-Quellcode

    1. public override bool Equals(Object obj)
    2. {
    3. if (!obj is Fraction) return false;
    4. Fraction Other = (Fraction)obj;
    5. return (Other.a == this.a) && (Other.b == this.b);
    6. }
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Hab ich auch schon gemacht, ich bin nur verwirrt, dass ich trotzdem noch die Wahnung kriege, dass ich Equals implementieren muss. GetHashCode muss ich theoretisch auch überschreiben, aber wenn ich es nicht benutze brauch ichs nicht zu implementieren oder?

    Die Warnung kommt nur, weil du die genannten Operatoren definierst. Nimmst du rods Klasse wie sie ist, kommt keine Warnung (jdfs nicht bei mir).

    Die Warnung ist auch logisch: Wenn definiert ist, wie 2 Objekte sich gleichen, dann ist dringend empfohlen, dass auch deren .GetHashCode-Methode gleiche Ergebnisse erbringt. Sonst gibts böse Überraschungen mit Dictionaries, Hashsets und ähnlichen Klassen, die annehmen, dass zwei Objekte bei gleichem Hashcode als gleich gelten, und andernfalls nicht.
    Auch die Equals-Methode(n) sollte(n) unbedingt zu den Operatoren konsistente Aussagen treffen.

    Hier mal der Pattern im Gesamtpaket, sogar IEquatable<T> habich reingemacht:

    C#-Quellcode

    1. public struct Fraction : IEquatable<Fraction> {
    2. public int a;
    3. public int b;
    4. public override bool Equals(Object obj) {
    5. try { return this == (Fraction)obj; }
    6. catch { return false; } // gibt der Vergleich einen Fehler, sind die Objekte sicher nicht gleich
    7. }
    8. public static bool operator ==(Fraction x, Fraction y) { return x.a == y.a && x.b == y.b; }
    9. public static bool operator !=(Fraction x, Fraction y) { return !(x == y); }
    10. public override int GetHashCode() { return base.GetHashCode(); }
    11. public bool Equals(Fraction other) { return (this == other); }
    12. }
    Beachte, dass nur an einer einzigen Stelle definiert ist, was gleich ist, alles andere verweist darauf.
    So kann das leicht gewartet werden, aber so kann der Code auch ohne Änderungen für beliebige andere Equatable Struct-Typen verwandt werden - nur der == - OP ist anzupassen, alles annere kann so bleiben.
    Stimmt nicht ganz - GetHashcode ist auch anzupassen - dass hier die Durchreiche an base ausreicht liegt am hiesigen einfachen Aufbau der struct.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

    du brauchst die Gethashcode-Methode.
    Eine Exception wäre das allerletzte.
    Dann überschreib auch die ToString-Methode und schmeiss da auch eine.
    /Sarkasmus

    Im Ernst: Wenn du mit dem Kram anfängst, dann mach ihn richtig, und implementiere konsistentes Verhalten.

    sonst lass es.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Wofür ist GetHashCode überhaupt gedacht? Ich hab noch nie in meinem Leben mit HashTables oder ähnlichen gearbeitet und noch nie die GetHashCode-Methode von irgendwas aufgerufen. Also wie müsste ich sie implementieren? Bzw was gibt base.GetHashcode zurück?

    haben wir doch gezeigt, wie das geht, Rod und ich. ist in diesem Falle doch kinderleicht.

    GethashCode ist ein Standard-Verfahren für Gleichheitstest: Jedes Objekt kann seinen HashCode (int) angeben, und wenn 2 Objekte denselben Hashcode liefern, gelten sie als gleich.
    Wenns ums Finden von Dubletten geht (wie bei HashSet und Dictionary) gibts da einen Super-schnellen HashAlgorithmus, der halt mit int funktioniert.
    Und das funktioniert auch mit Klassen und Structs, die keinerlei besondere Gleichheits-Definitionen bereitstellen - ohne Überschreibung wird der Hashcode einfach aus dem Byte-Muster des Objekts ermittelt.

    Niko Ortner schrieb:

    Erscheint mir äußerst suspekt, gerade von RodFromGermany Code zu sehen, der hier Try-Catch verwendet.
    Erst probieren, dann äußern.
    Der is-Operator greift nur bei bei Referenztypen, die ja eine Struktur bekanntlich nicht ist, die ist ein Wertetyp. Die lässt sich nur hart casten, und da muss mit einer InvalidCastException gerechnet werden, die natürlich noch explizit reingeschrieben werden kann.

    TheVBTutorialsVB schrieb:

    GetHashCode
    liefert eine Warnung, die beim Implementieren verschwindet. Den Rest hat der @ErfinderDesRades erläutert.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Erst probieren, dann äußern.

    Danke, gleichfalls.
    Klar ist Fraction hier ein Wertetyp, der obj-Parameter jedoch nicht. Und natürlich macht es Sinn, ohne Exception prüfen zu können, ob obj eine geboxte Fraction-Instanz beinhaltet.
    Was ich vergessen habe ist die zweite Klammer um die Prüfung: if (!(obj is Fraction)) statt if (!obj is Fraction), aber das sollte nicht das Problem sein.


    Edit: Bezüglich GetHashCode: Gute Lektüre: ericlippert.com/2011/02/28/gui…nd-rules-for-gethashcode/
    What is GetHashCode used for?

    It is by design useful for only one thing: putting an object in a hash table. Hence the name.
    [...]
    Remember, objects can be put into hash tables in ways that you didn’t expect. A lot of the LINQ sequence operators use hash tables internally.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Niko Ortner“ ()

    Hi
    für Strukturen geht folgende Lösung über Nullable<T>:

    C#-Quellcode

    1. public struct SomeStruct
    2. {
    3. public override bool Equals(object obj)
    4. {
    5. if (obj == null)
    6. return false;
    7. var other = obj as SomeStruct?;
    8. if (other == null)
    9. return false;
    10. //...
    11. }
    12. }

    In der Regel implementiert man IEquatable für die Struktur, sodass sich folgendes ergibt:

    C#-Quellcode

    1. public struct SomeStruct
    2. {
    3. public override bool Equals(object obj)
    4. {
    5. if (obj == null)
    6. return false;
    7. var other = obj as SomeStruct?;
    8. if (other == null)
    9. return false;
    10. return Equals(other.Value);
    11. }
    12. public bool Equals(SomeStruct other)
    13. {
    14. //...
    15. }
    16. }


    Zusätzlich muss außerdem eben auch die GetHashCode-Funktion überschrieben werden. In der Regel gibt man bei Equals(SomeStruct other) einfach this == other zurück und überlädt ==-, sowie !=-operator.

    Viele Grüße
    ~blaze~