Was ist der Unterschied zwischen IEquatable und dem Überschreiben von Object.Equals ()?

183

Ich möchte, dass meine FoodKlasse testen kann, wann immer es einer anderen Instanz von entspricht Food. Ich werde es später für eine Liste verwenden, und ich möchte seine List.Contains()Methode verwenden. Soll ich implementieren IEquatable<Food>oder einfach überschreiben Object.Equals()? Von MSDN:

Diese Methode ermittelt die Gleichheit mithilfe des Standardgleichheitsvergleichs, der durch die Implementierung der IEquatable.Equals-Methode für T (den Wertetyp in der Liste) durch das Objekt definiert wird.

Meine nächste Frage lautet also: Welche Funktionen / Klassen des .NET Frameworks werden verwendet Object.Equals()? Soll ich es überhaupt benutzen?

verschlungenes Elysium
quelle
3
Eine sehr gute Erklärung hier blogs.msdn.com/b/jaredpar/archive/2009/01/15/…
nawfal
mögliches Duplikat von Understanding IEquatable
nawfal

Antworten:

213

Der Hauptgrund ist die Leistung. Wenn Generika in .NET 2.0 eingeführt wurden sie in der Lage waren , ein paar ordentlichen Klassen hinzuzufügen wie List<T>, Dictionary<K,V>, HashSet<T>usw. machen diese Strukturen starke Nutzung von GetHashCodeund Equals. Für Werttypen erforderte dies jedoch Boxen. IEquatable<T>Ermöglicht einer Struktur die Implementierung einer stark typisierten EqualsMethode, sodass kein Boxen erforderlich ist. Somit viel bessere Leistung bei Verwendung von Werttypen mit generischen Sammlungen.

Referenztypen profitieren nicht so sehr, aber mit der IEquatable<T>Implementierung können Sie eine Besetzung vermeiden, System.Objectdie einen Unterschied machen kann, wenn sie häufig aufgerufen wird.

Wie in Jared Parsons Blog erwähnt , müssen Sie die Objektüberschreibungen dennoch implementieren.

Josh
quelle
Gibt es eine Besetzung zwischen Referenztypen? Ich habe immer gedacht, dass Casts nur "Aussagen" sind, die Sie dem Compiler machen, wenn Sie einige nicht offensichtliche Casts von einer Art von Objekt einer anderen zuweisen. Nachdem Sie kompiliert haben, würde der Code nicht einmal wissen, dass dort eine Besetzung vorhanden ist.
verschlungenes Elysium
7
Dies gilt für C ++, jedoch nicht für .NET-Sprachen, die die Typensicherheit erzwingen. Es gibt eine Laufzeitumwandlung, und wenn die Umwandlung nicht erfolgreich ist, wird eine Ausnahme ausgelöst. Es gibt also eine kleine Laufzeitstrafe für das Casting. Der Compiler kann Upcasts optimieren. Zum Beispiel Objekt o = (Objekt) "string"; Aber Downcasting - String s = (String) o; - muss zur Laufzeit geschehen.
Josh
1
Aha. Haben Sie zufällig einen Ort, an dem ich solche "tieferen" Informationen über .NET erhalten kann? Vielen Dank!
verschlungenes Elysium
7
Ich würde CLR über C # von Jeff Richter und C # in Depth von Jon Skeet empfehlen. Was Blogs betrifft, sind Wintellect-Blogs gut, MSDN-Blogs usw.
Josh
Erinnert die IEquatable<T>Schnittstelle einen Entwickler lediglich daran, ein public bool Equals(T other) Mitglied in die Klasse oder Struktur aufzunehmen? Das Vorhandensein oder Fehlen der Schnittstelle macht zur Laufzeit keinen Unterschied. Die Überlastung von Equalsscheint alles zu sein, was notwendig ist.
Mikemay
47

Laut MSDN :

Wenn Sie implementieren IEquatable<T>, sollten Sie auch die Basisklasse Implementierungen außer Kraft setzen Object.Equals(Object)und GetHashCode damit ihr Verhalten mit dem der konsistenten IEquatable<T>.Equals Methode. Wenn Sie überschreiben Object.Equals(Object), wird Ihre überschriebene Implementierung auch in Aufrufen der statischen Equals(System.Object, System.Object)Methode Ihrer Klasse aufgerufen . Dies stellt sicher, dass alle Aufrufe der EqualsMethode konsistente Ergebnisse liefern.

Es scheint also keinen wirklichen funktionalen Unterschied zwischen den beiden zu geben, außer dass beide aufgerufen werden könnten, abhängig davon, wie die Klasse verwendet wird. Unter Leistungsgesichtspunkten ist es besser, die generische Version zu verwenden, da damit keine Box- / Unboxing-Strafe verbunden ist.

Aus logischer Sicht ist es auch besser, die Schnittstelle zu implementieren. Das Überschreiben des Objekts sagt niemandem wirklich, dass Ihre Klasse tatsächlich gleichwertig ist. Die Überschreibung kann nur eine Do-Nothing-Klasse oder eine flache Implementierung sein. Über die Schnittstelle wird explizit gesagt: "Hey, dieses Ding ist für die Gleichheitsprüfung gültig!" Es ist einfach besseres Design.

CodexArcanum
quelle
9
Strukturen sollten auf jeden Fall iEquatable (von ihrem eigenen Typ) implementieren, wenn sie als Schlüssel in einem Wörterbuch oder einer ähnlichen Sammlung verwendet werden sollen. es wird einen großen Leistungsschub bieten. Nicht vererbbare Klassen erhalten durch die Implementierung von IEquatable (ihres eigenen Typs) eine leichte Leistungssteigerung. Vererbbare Klassen sollten IEquatable // nicht // implementieren.
Supercat
30

Erweitern Sie das, was Josh gesagt hat, um ein praktisches Beispiel. +1 an Josh - Ich wollte das gleiche in meiner Antwort schreiben.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

Auf diese Weise habe ich eine wiederverwendbare Equals () -Methode, die für alle meine abgeleiteten Klassen sofort funktioniert.

Dies. __curious_geek
quelle
Nur noch eine Frage. Was ist der Vorteil der Verwendung von "obj als EntityBase" anstelle von (EntityBase) obj? Nur eine Frage des Stils oder gibt es überhaupt einen Vorteil?
verschlungenes Elysium
22
im Fall von "obj as EntityBase" - wenn obj nicht vom Typ EntityBase ist, wird "null" übergeben und ohne Fehler oder Ausnahme fortgesetzt. Im Fall von "(EntityBase) obj" wird jedoch gewaltsam versucht, das Objekt zu konvertieren zu EntityBase und wenn das Objekt nicht vom Typ EntityBase ist, wird InvalidCastException ausgelöst. Und ja, "as" kann nur auf Referenztypen angewendet werden.
das. __curious_geek
1
Joshs Link zu Jared Pars Blog scheint darauf hinzudeuten, dass Sie auch GetHashCode überschreiben müssen. Ist das nicht der Fall?
Freundlich
3
Ich bekomme nicht wirklich den zusätzlichen Wert, den Ihre Implementierung bietet. Können Sie das Problem klären, das Ihre abstrakte Basisklasse löst?
Mert Akcakaya
1
@Amicable - Ja, wenn Sie Object.Equals (Object) überschreiben, müssen Sie auch GetHashCode überschreiben, damit Container funktionieren.
Namford
0

Wenn wir anrufen object.Equals, zwingt es zu teurem Boxen auf Werttypen. Dies ist in leistungsabhängigen Szenarien unerwünscht. Die Lösung ist zu verwenden IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

Die Idee dahinter IEquatable<T>ist, dass es das gleiche Ergebnis liefert, object.Equalsaber schneller. Die Einschränkung where T : IEquatable<T>muss mit generischen Typen wie unten verwendet werden.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

Andernfalls bindet es an slower object.Equals().

Seyedraouf Modarresi
quelle