Sovraccarico dell’operatore con la programmazione basata sull’interfaccia in C #

sfondo

Sto usando la programmazione basata sull’interfaccia su un progetto corrente e ho incontrato un problema quando sovraccarico operatori (in particolare gli operatori di uguaglianza e disuguaglianza).


ipotesi

  • Sto usando C # 3.0, .NET 3.5 e Visual Studio 2008

AGGIORNAMENTO – Il seguente presupposto era falso!

  • Richiedere tutti i confronti per utilizzare Equals piuttosto che operator == non è una soluzione praticabile, specialmente quando si passano i tipi alle librerie (come le raccolte).

Il motivo per cui ero interessato a richiedere l’utilizzo di Equals piuttosto che operator == è che non ho potuto trovare da nessuna parte nelle linee guida .NET che affermava che avrebbe usato Equals piuttosto che operator == o addirittura suggerito. Tuttavia, dopo aver riletto le Linee guida per Overriding Equals e Operator == ho trovato questo:

Per impostazione predefinita, l’operatore == verifica l’uguaglianza di riferimento determinando se due riferimenti indicano lo stesso object. Pertanto, i tipi di riferimento non devono implementare l’operatore == per ottenere questa funzionalità. Quando un tipo è immutabile, cioè i dati contenuti nell’istanza non possono essere modificati, l’operatore di overload == per confrontare l’uguaglianza del valore anziché l’uguaglianza di riferimento può essere utile perché, come oggetti immutabili, possono essere considerati uguali a quelli lunghi come hanno lo stesso valore. Non è una buona idea sovrascrivere l’operatore == in tipi non immutabili.

e questa interfaccia equabile

L’interfaccia IEquatable viene utilizzata da oggetti di raccolta generici quali Dictionary, List e LinkedList durante il test di uguaglianza in metodi quali Contains, IndexOf, LastIndexOf e Remove. Dovrebbe essere implementato per qualsiasi object che potrebbe essere memorizzato in una collezione generica.


contraints

  • Qualsiasi soluzione non deve richiedere il casting degli oggetti dalle loro interfacce ai loro tipi concreti.

Problema

  • Quando mai entrambi i lati dell’operatore == sono un’interfaccia, nessun operatore == firma del metodo di sovraccarico dai tipi concreti sottostanti corrisponderà e quindi verrà chiamato il metodo == dell’operatore Object predefinito.
  • Quando si sovraccarica un operatore su una class, almeno uno dei parametri dell’operatore binario deve essere il tipo contenente, altrimenti viene generato un errore del compilatore (Errore BC33021 http://msdn.microsoft.com/en-us/library/watt39ff .aspx )
  • Non è ansible specificare l’implementazione su un’interfaccia

Vedere il codice e l’output di seguito per dimostrare il problema.


Domanda

Come si forniscono gli opportuni sovraccarichi dell’operatore per le classi quando si utilizza la programmazione dell’interfaccia di base?


Riferimenti

== Operatore (riferimento C #)

Per i tipi di valore predefiniti, l’operatore di uguaglianza (==) restituisce true se i valori dei suoi operandi sono uguali, false altrimenti. Per i tipi di riferimento diversi da string, == restituisce true se i suoi due operandi si riferiscono allo stesso object. Per il tipo di stringa, == confronta i valori delle stringhe.


Guarda anche


Codice

using System; namespace OperatorOverloadsWithInterfaces { public interface IAddress : IEquatable { 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 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."); } } } } 

Produzione

 Address operator== overload called Equal with both sides cast. 

Risposta breve: penso che la tua seconda ipotesi potrebbe essere errata. Equals() è il modo giusto per verificare l’uguaglianza semantica di due oggetti, non l’ operator == .


Risposta lunga: la risoluzione del sovraccarico per gli operatori viene eseguita in fase di compilazione, non in fase di esecuzione .

A meno che il compilatore non conosca in modo definitivo i tipi di oggetti a cui sta applicando un operatore, non verrà compilato. Dato che il compilatore non può essere sicuro che un IAddress sarà qualcosa che ha un override per == definito, IAddress all’implementazione operator == di System.Object operator == predefinito.

Per vederlo in modo più chiaro, provare a definire un operator + per Address e aggiungere due istanze di IAddress . A meno che non si scriva esplicitamente su Address , non verrà compilato. Perché? Perché il compilatore non può dire che un particolare IAddress è un Address e non vi è alcun operator + predefinito operator + implementazione a cui System.Object in System.Object .


Parte della tua frustrazione deriva probabilmente dal fatto che Object implementa un operator == , e tutto è un Object , quindi il compilatore può risolvere con successo operazioni come a == b per tutti i tipi. Quando hai eseguito l’over == , ti aspettavi di vedere lo stesso comportamento ma non lo facevi, e questo perché la migliore corrispondenza che il compilatore può trovare è l’implementazione Object originale.

Richiedere tutti i confronti per utilizzare Equals piuttosto che operator == non è una soluzione praticabile, specialmente quando si passano i tipi alle librerie (come le raccolte).

A mio avviso, questo è esattamente ciò che dovresti fare. Equals() è il modo giusto per verificare l’uguaglianza semantica di due oggetti. A volte l’uguaglianza semantica è solo l’uguaglianza di riferimento, nel qual caso non sarà necessario modificare nulla. In altri casi, come nel tuo esempio, dovrai eseguire l’override di Equals quando hai bisogno di un contratto di uguaglianza più forte rispetto all’uguaglianza di riferimento. Ad esempio, potresti voler considerare due Persons uguali se hanno lo stesso numero di previdenza sociale o due Vehicles uguali se hanno lo stesso VIN.

Ma Equals() e operator == non sono la stessa cosa. Ogni volta che è necessario eseguire l’override operator == , è necessario eseguire l’override di Equals() , ma quasi mai il contrario. operator == è più una comodità sintattica. Alcune lingue CLR (ad es. Visual Basic.NET) non consentono nemmeno di sovrascrivere l’operatore di uguaglianza.

Ci siamo imbattuti nello stesso problema e abbiamo trovato una soluzione eccellente: i pattern personalizzati di Resharper.

Abbiamo configurato TUTTI i nostri utenti affinché usassero un comune catalogo di modelli globali oltre al proprio, e lo abbiamo inserito in SVN in modo che potesse essere versionato e aggiornato per tutti.

Il catalogo includeva tutti i pattern noti per essere errati nel nostro sistema:

$i1$ == $i2$ (dove i1 e i2 sono espressioni del nostro tipo di interfaccia o derivate.

il modello di sostituzione è

$i1$.Equals($i2$)

e la gravità è “Mostra come errore”.

Allo stesso modo abbiamo $i1$ != $i2$

Spero che questo ti aiuti. PS Cataloghi globali è la funzionalità di Resharper 6.1 (EAP), sarà contrassegnato come finale molto presto.

Aggiornamento : ho archiviato un problema di Resharper per contrassegnare tutta l’interfaccia ‘==’ un avviso a meno che non si stia confrontando con null. Per favore, vota se pensi che sia una caratteristica degna.

Update2 : Resharper ha anche l’attributo [CannotApplyEqualityOperator] che può essere d’aiuto.