Hintergrund
Ich verwende die schnittstellenbasierte Programmierung für ein aktuelles Projekt und bin beim Überladen von Operatoren (insbesondere der Operatoren für Gleichheit und Ungleichheit) auf ein Problem gestoßen.
Annahmen
- Ich verwende C # 3.0, .NET 3.5 und Visual Studio 2008
UPDATE - Die folgende Annahme war falsch!
- Das Erfordernis aller Vergleiche zur Verwendung von Equals anstelle von operator == ist keine praktikable Lösung, insbesondere wenn Sie Ihre Typen an Bibliotheken (z. B. Sammlungen) übergeben.
Der Grund, warum ich besorgt war, dass Equals anstelle von operator == verwendet werden muss, ist, dass ich in den .NET-Richtlinien nirgendwo finden konnte, dass Equals anstelle von operator == verwendet oder sogar vorgeschlagen wird. Nach dem erneuten Lesen der Richtlinien zum Überschreiben von Gleichheit und Operator == habe ich Folgendes gefunden:
Standardmäßig prüft der Operator == die Referenzgleichheit, indem er ermittelt, ob zwei Referenzen dasselbe Objekt angeben. Daher müssen Referenztypen den Operator == nicht implementieren, um diese Funktionalität zu erhalten. Wenn ein Typ unveränderlich ist, dh die in der Instanz enthaltenen Daten nicht geändert werden können, kann das Überladen des Operators == zum Vergleichen der Wertgleichheit anstelle der Referenzgleichheit nützlich sein, da sie als unveränderliche Objekte als gleich lange betrachtet werden können da sie den gleichen Wert haben. Es ist keine gute Idee, operator == in nicht unveränderlichen Typen zu überschreiben.
und diese gleichwertige Schnittstelle
Die IEquatable-Schnittstelle wird von generischen Auflistungsobjekten wie Dictionary, List und LinkedList verwendet, wenn die Gleichheit in Methoden wie Contains, IndexOf, LastIndexOf und Remove getestet wird. Es sollte für jedes Objekt implementiert werden, das möglicherweise in einer generischen Sammlung gespeichert ist.
Einschränkungen
- Für jede Lösung muss es nicht erforderlich sein, die Objekte von ihren Schnittstellen auf ihre konkreten Typen zu werfen.
Problem
- Immer wenn beide Seiten des Operators == eine Schnittstelle sind, stimmt keine Signatur der Operator == -Überladungsmethode der zugrunde liegenden konkreten Typen überein, und daher wird die Standardmethode Object operator == aufgerufen.
- Beim Überladen eines Operators für eine Klasse muss mindestens einer der Parameter des binären Operators der enthaltende Typ sein, andernfalls wird ein Compilerfehler generiert (Fehler BC33021 http://msdn.microsoft.com/en-us/library/watt39ff .aspx )
- Es ist nicht möglich, die Implementierung auf einer Schnittstelle anzugeben
Siehe Code und Ausgabe unten, um das Problem zu demonstrieren.
Frage
Wie stellen Sie bei Verwendung der Schnittstellenbasisprogrammierung die richtigen Operatorüberladungen für Ihre Klassen bereit?
Verweise
Für vordefinierte Werttypen gibt der Gleichheitsoperator (==) true zurück, wenn die Werte seiner Operanden gleich sind, andernfalls false. Für andere Referenztypen als Zeichenfolge gibt == true zurück, wenn sich die beiden Operanden auf dasselbe Objekt beziehen. Für den Zeichenfolgentyp vergleicht == die Werte der Zeichenfolgen.
Siehe auch
Code
using System;
namespace OperatorOverloadsWithInterfaces
{
public interface IAddress : IEquatable<IAddress>
{
string StreetName { get; set; }
string City { get; set; }
string State { get; set; }
}
public class Address : IAddress
{
private string _streetName;
private string _city;
private string _state;
public Address(string city, string state, string streetName)
{
City = city;
State = state;
StreetName = streetName;
}
#region IAddress Members
public virtual string StreetName
{
get { return _streetName; }
set { _streetName = value; }
}
public virtual string City
{
get { return _city; }
set { _city = value; }
}
public virtual string State
{
get { return _state; }
set { _state = value; }
}
public static bool operator ==(Address lhs, Address rhs)
{
Console.WriteLine("Address operator== overload called.");
// If both sides of the argument are the same instance or null, they are equal
if (Object.ReferenceEquals(lhs, rhs))
{
return true;
}
return lhs.Equals(rhs);
}
public static bool operator !=(Address lhs, Address rhs)
{
return !(lhs == rhs);
}
public override bool Equals(object obj)
{
// Use 'as' rather than a cast to get a null rather an exception
// if the object isn't convertible
Address address = obj as Address;
return this.Equals(address);
}
public override int GetHashCode()
{
string composite = StreetName + City + State;
return composite.GetHashCode();
}
#endregion
#region IEquatable<IAddress> Members
public virtual bool Equals(IAddress other)
{
// Per MSDN documentation, x.Equals(null) should return false
if ((object)other == null)
{
return false;
}
return ((this.City == other.City)
&& (this.State == other.State)
&& (this.StreetName == other.StreetName));
}
#endregion
}
public class Program
{
static void Main(string[] args)
{
IAddress address1 = new Address("seattle", "washington", "Awesome St");
IAddress address2 = new Address("seattle", "washington", "Awesome St");
functionThatComparesAddresses(address1, address2);
Console.Read();
}
public static void functionThatComparesAddresses(IAddress address1, IAddress address2)
{
if (address1 == address2)
{
Console.WriteLine("Equal with the interfaces.");
}
if ((Address)address1 == address2)
{
Console.WriteLine("Equal with Left-hand side cast.");
}
if (address1 == (Address)address2)
{
Console.WriteLine("Equal with Right-hand side cast.");
}
if ((Address)address1 == (Address)address2)
{
Console.WriteLine("Equal with both sides cast.");
}
}
}
}
Ausgabe
Address operator== overload called
Equal with both sides cast.
quelle
Antworten:
Kurze Antwort: Ich denke, Ihre zweite Annahme könnte fehlerhaft sein.
Equals()
ist der richtige Weg, um die semantische Gleichheit zweier Objekte zu überprüfen , nichtoperator ==
.Lange Antwort: Die Überlastungsauflösung für Operatoren wird zur Kompilierungszeit und nicht zur Laufzeit durchgeführt .
Solange der Compiler die Typen der Objekte, auf die er einen Operator anwendet, nicht definitiv kennt, wird er nicht kompiliert. Da der Compiler nicht sicher sein kann, ob
IAddress
ein Objekt überschrieben werden soll==
, wird auf die Standardimplementierungoperator ==
von zurückgegriffenSystem.Object
.Versuchen Sie, eine
operator +
for zu definierenAddress
und zweiIAddress
Instanzen hinzuzufügen , um dies klarer zu sehen . Sofern Sie nicht explizit darauf setzenAddress
, kann es nicht kompiliert werden. Warum? Weil der Compiler nicht erkennen kann, dass es sich bei einer bestimmtenIAddress
um eine handeltAddress
, und es keine Standardimplementierung gibtoperator +
, auf die zurückgegriffen werden kannSystem.Object
.Ein Teil Ihrer Frustration beruht wahrscheinlich auf der Tatsache, dass ein
Object
implementiertoperator ==
wird und alles ein istObject
, sodass der Compiler Operationen wiea == b
für alle Typen erfolgreich auflösen kann . Wenn Sie überschrieben haben==
, haben Sie erwartet, dass Sie dasselbe Verhalten sehen, aber nicht, und das liegt daran, dass die besteObject
Implementierung die ursprüngliche Implementierung ist, die der Compiler finden kann .Meiner Ansicht nach sollten Sie genau das tun.
Equals()
ist der richtige Weg, um die semantische Gleichheit zweier Objekte zu überprüfen . Manchmal ist semantische Gleichheit nur Referenzgleichheit. In diesem Fall müssen Sie nichts ändern. In anderen Fällen, wie in Ihrem Beispiel, überschreibenEquals
Sie, wenn Sie einen stärkeren Gleichstellungsvertrag als die Referenzgleichheit benötigen. Beispielsweise möchten Sie möglicherweise zwei alsPersons
gleich betrachten, wenn sie dieselbe Sozialversicherungsnummer haben, oder zwei alsVehicles
gleich, wenn sie dieselbe Fahrgestellnummer haben.Aber
Equals()
undoperator ==
sind nicht dasselbe. Wann immer Sie überschreiben müssenoperator ==
, sollten Sie überschreibenEquals()
, aber fast nie umgekehrt.operator ==
ist eher eine syntaktische Bequemlichkeit. In einigen CLR-Sprachen (z. B. Visual Basic.NET) können Sie den Gleichheitsoperator nicht einmal überschreiben.quelle
Is
anstelle der Referenzgleichheit verwendet wird Teilen=
wie C #.Wir sind auf dasselbe Problem gestoßen und haben eine hervorragende Lösung gefunden: Benutzerdefinierte Muster neu schärfen.
Wir haben ALLE unsere Benutzer so konfiguriert, dass sie zusätzlich zu ihrem eigenen einen gemeinsamen globalen Musterkatalog verwenden, und ihn in SVN abgelegt, damit er für alle versioniert und aktualisiert werden kann.
Der Katalog enthielt alle Muster, von denen bekannt ist, dass sie in unserem System falsch sind:
$i1$ == $i2$
(wobei i1 und i2 Ausdrücke unseres Schnittstellentyps sind oder abgeleitet werden.Das Ersetzungsmuster ist
$i1$.Equals($i2$)
und der Schweregrad ist "Als Fehler anzeigen".
Ebenso haben wir
$i1$ != $i2$
Hoffe das hilft. PS Global Catalogs ist die Funktion in Resharper 6.1 (EAP) und wird in Kürze als endgültig markiert.
Update : Ich habe ein Resharper-Problem eingereicht , um alle Schnittstellen '==' als Warnung zu markieren, es sei denn, es wird mit null verglichen. Bitte stimmen Sie ab, wenn Sie der Meinung sind, dass dies eine würdige Funktion ist.
Update2 : Resharper hat auch das Attribut [CannotApplyEqualityOperator], das helfen kann.
quelle