Die Übung veranschaulicht eine Reihe von Eigenschaften der Gleichheit in C#:
Object.Equals()== und != auf Gleichheit geprüft. Das gilt als idiomatischer als die Verwendung der Methode Equals(), die es für diese Typen ebenfalls gibt. Java-Programmierer sollten bei Strings darauf achten, dass == in C# nach dem Wert vergleicht, in Java dagegen nach der Referenz, wenn sie zu ihrer früheren Sprache zurückkehren.object geerbten Methode Equals() verglichen. Wenn es dir beim Gleichheitstest darum geht, sicherzustellen, dass zwei Objekte genau dieselbe Instanz sind, dann genügt die Implementierung von object. Wenn nicht, musst du object.Equals() überschreiben.Equals() überschrieben werden.Equals() enthält Gleichheitstests für Member einfacher Typen mit == und für Referenztypen mit rekursiven Aufrufen von Equals().class StatusBar
{
private readonly int width = 200, height = 20;
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return width == (other as StatusBar).width && height == (other as StatusBar).height;
}
}
class Window
{
private readonly string title = "Main";
private readonly StatusBar statusBar = new StatusBar();
public override bool Equals(object other)
{
// ... null and type checks and performance optimisations
return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
}
}
object.ReferenceEquals() wird verwendet, um zwei Objekte zu vergleichen und festzustellen, ob sie ein und dieselbe Instanz sind. Das sorgt für Klarheit und ist notwendig, wenn Equals() und == überschrieben bzw. überladen wurden.var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true
public override bool Equals(object obj) erzeugen IDEs normalerweise die Überladung protected bool Equals(FacialFeatures other), die bei Vererbung zum Einsatz kommt. Eine abgeleitete Klasse kann Equals() der Basisklasse aufrufen und dann ihren eigenen Test hinzufügen.== nur, wenn du den ==-Operator und auch die Methode Equals() in deiner Klasse überladen hast (siehe die Übung operator-overloading) oder wenn es dir nur darum geht, dass die Referenzen gleich sind.structs werden in der Übung structs behandelt.tuples wird in der Übung tuples behandelt.protected bool Equals(T other)
{
return field1 == other.field1 && field2.Equals(other.field2);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((T) obj);
}
SequenceEquals(), aber ohne LINQ bleibt nur, beide Sammlungen zu durchlaufen und die Elemente einzeln zu vergleichen.== und != mit deinen eigenen Klassen verwendest, wird in der Übung operator-overloading besprochen.Object.GetHashCode()object.GetHashCode() gibt einen Hashcode in Form einer 32-Bit-Ganzzahl zurück. Der Hashcode wird von Wörterbuch- und Set-Klassen wie Dictionary<T> und HashSet<T> verwendet, um Objekte performant zu speichern und abzurufen. Bei Wörterbüchern bezieht sich das Hashing auf die Schlüssel.GetHashCode() ebenfalls überschreibst, wenn du Equals() überschreibst. Zwischen Equals() und GetHashCode() besteht eine Beziehung, die gelten muss, damit Wörterbuch- und HashSet-Klassen und alle anderen, die einen Hashcode verwenden, korrekt funktionieren. Du solltest die Methode so implementieren, dass du Maintainern keine Fallen stellst, die später eine hashcodebasierte Sammlung hinzufügen.Equal() gibt wahr zurück), dann müssen GetHashCode() für die beiden Objekte denselben Wert zurückgeben. Umgekehrt gilt das nicht. Die Beziehung ist nicht symmetrisch. Stell dir eine Suchfunktion vor, die anhand des Hashcodes zuerst einen „Bucket“ ansteuert und dann das jeweilige Element über den Gleichheitstest herausgreift.HashCode.Combine(), dem du die im Gleichheitstest verwendeten Werte (oder eine Teilmenge davon) übergibst. Bedenke: Je mehr Informationen du Combine() gibst, desto performanter ist die Hash-Implementierung wahrscheinlich.public class Assessment
{
private int rating;
private Person boss;
public override int GetHashCode()
{
return HashCode.Combine(rating, boss);
}
}
Um die Leistung etwas zu verbessern, besonders wenn Objekte zu Sammlungen gehören, kannst du ein überladenes Member public bool Equals(T other) hinzufügen.
Das spart eine gewisse Menge an Typprüfungen für Referenztypen und spart einen Boxing-Schritt für Werttypen, da diese nicht in ein Objekt umgewandelt (geboxt) werden müssen, um als Argument an public override bool Equals(object other) übergeben zu werden.
Wenn du das Interface IEquatable<T> zu deiner Klasse hinzufügst, muss die Überladung implementiert werden. Wenn dein Code keine Routinen enthält, die Objekte vom Typ IEquatable<T> entgegennehmen (und damit vermutlich etwas Interessantes rund um Gleichheit tun, unabhängig von der implementierenden Klasse), gibt es keinen anderen zwingenden Grund, das Interface einzubinden.
IEqualityComparer<T>Wenn du eine Klasse hast, die auf zwei verschiedene Arten eindeutig identifiziert werden kann, etwa eine Person-Klasse mit einer SSID und einer eindeutigen E-Mail-Adresse, dann bietet .NET eine Möglichkeit, zwei verschiedene Sammlungen unterschiedliche Hashcode- und Gleichheitstests verwenden zu lassen. Jede kann eine andere Implementierung von IEqualityComparer<T> mit eigener Equals()- und GetHashCode()-Methode haben. Du kannst ein Wörterbuch mit dem Schlüssel SSID und ein weiteres mit dem Schlüssel E-Mail-Adresse haben.
Wenn IEqualityComparer<T> im Spiel ist, solltest du in deiner Elementklasse trotzdem Equals() und GetHashCode() überschreiben, um Probleme außerhalb der Sammlungsklassen zu vermeiden.
Ein Aspekt bei der Verwendung von IEqualityComparer<T> ist, dass private Methoden usw. für den Gleichheitstest nicht zur Verfügung stehen.
Wenn nur eine hashbasierte Sammlung im Spiel ist, ist es vielleicht besser, IEqualityComparer<T> zu vermeiden und Gleichheit und Hashcode im Objekt selbst zu kapseln. Es ist ohnehin nicht ideal, dass eine so wichtige Abhängigkeit wie die zwischen Objekt und hashbasierter Sammlung nicht vom Compiler erzwungen werden kann. Wenn die Hashing- und Vergleichslogik aber an anderer Stelle liegt, wäre es für einen Maintainer noch leichter, die Member einer Klasse zu ändern, ohne die Folgen für die Sammlung zu bedenken.
Ein primitiver Typ, der unvorsichtige Programmierer herausfordern kann, ist der Test auf Gleichheit von Gleitkommawerten. Das wird im about.md-Dokument für das Konzept floating-point-numbers behandelt.
Dieser Artikel zeigt einige der Entscheidungen, die bei Gleichheit und Vererbung getroffen werden müssen. Sie betreffen dich nur, wenn du mit Instanzen einer Klasse und einer abgeleiteten Klasse arbeitest und die Gleichheit zwischen beiden testen musst.