Sto lavorando a un progetto C # sul quale, fino ad ora, ho utilizzato oggetti e fabbriche immutabili per garantire che gli oggetti di tipo Foo
possano sempre essere confrontati per l’uguaglianza con ==
.
Foo
oggetti Foo
non possono essere modificati una volta creati e la fabbrica restituisce sempre lo stesso object per un determinato set di argomenti. Funziona alla grande, e in tutto il codice base assumiamo che ==
sempre per controllare l’uguaglianza.
Ora ho bisogno di aggiungere alcune funzionalità che introducano un caso limite per il quale questo non sempre funziona. La cosa più semplice da fare è sovraccaricare l’ operator ==
per quel tipo, in modo che nessuno degli altri codici nel progetto debba essere modificato. Ma questo mi sembra un odore di codice: l’ operator ==
overload operator ==
e non Equals
sembra strano, e io sono abituato alla convenzione che ==
controlla l’uguaglianza dei riferimenti, e Equals
controlla l’uguaglianza degli oggetti (o qualunque sia il termine).
È una preoccupazione legittima, o dovrei semplicemente andare avanti e sovraccaricare l’ operator ==
?
Credo che lo standard sia che per la maggior parte dei tipi, .Equals controlla la similarità dell’object, e operator ==
controlla l’uguaglianza di riferimento.
Credo che la best practice sia che per i tipi immutabili, l’operatore ==
dovrebbe verificare la similarità, oltre a .Equals
. E se vuoi sapere se sono veramente lo stesso object, usa .ReferenceEquals
. Vedi la class String
C # per un esempio di questo.
C’è una grande differenza tra overload ==
e overriding Equals.
Quando hai l’espressione
if (x == y) {
Il metodo che verrà utilizzato per confrontare le variabili x e y è deciso al momento della compilazione . Questo è il sovraccarico dell’operatore. Il tipo usato quando si dichiara xey viene usato per definire quale metodo viene usato per confrontarli. Il tipo effettivo all’interno di xey (ovvero una sottoclass o implementazione di interfaccia) è irrilevante. Considera quanto segue.
object x = "hello"; object y = 'h' + "ello"; // ensure it's a different reference if (x == y) { // evaluates to FALSE
e il seguente
string x = "hello"; string y = 'h' + "ello"; // ensure it's a different reference if (x == y) { // evaluates to TRUE
Ciò dimostra che il tipo utilizzato per dichiarare le variabili xey viene utilizzato per determinare quale metodo viene utilizzato per valutare ==.
Per confronto, Equals viene determinato in fase di esecuzione in base al tipo effettivo all’interno della variabile x. Equals è un metodo virtuale su Object che altri tipi possono eseguire e sovrascrivere. Pertanto i seguenti due esempi valgono entrambi.
object x = "hello"; object y = 'h' + "ello"; // ensure it's a different reference if (x.Equals(y)) { // evaluates to TRUE
e il seguente
string x = "hello"; string y = 'h' + "ello"; // ensure it's a different reference if (x.Equals(y)) { // also evaluates to TRUE
Sente decisamente gli odori. In caso di sovraccarico ==
è necessario assicurarsi che sia Equals()
che GetHashCode()
siano coerenti. Vedere le linee guida MSDN .
E l’unica ragione per cui questo sembra OK è che descrivi il tuo tipo come immutabile.
Per i tipi immutabili non penso che ci sia qualcosa di sbagliato nell’avere sovraccarico ==
per supportare l’uguaglianza dei valori. Non penso di sovrascrivere ==
senza Equals
a livello di override per avere comunque la stessa semantica. Se si esegue l’override ==
e è necessario controllare l’uguaglianza dei riferimenti per qualche motivo, è ansible utilizzare Object.ReferenceEquals(a,b)
.
Vedi questo articolo di Microsoft per alcune linee guida utili
Esempio che mostra come implementarlo secondo le linee guida MSFT (sotto). Notare che quando si esegue l’override di Equals è necessario sovrascrivere GetHashCode (). Spero che questo aiuti la gente.
public class Person { public Guid Id { get; private set; } public Person(Guid id) { Id = id; } public Person() { Id = System.Guid.NewGuid(); } public static bool operator ==(Person p1, Person p2) { bool rc; if (System.Object.ReferenceEquals(p1, p2)) { rc = true; } else if (((object)p1 == null) || ((object)p2 == null)) { rc = false; } else { rc = (p1.Id.CompareTo(p2.Id) == 0); } return rc; } public static bool operator !=(Person p1, Person p2) { return !(p1 == p2); } public override bool Equals(object obj) { bool rc = false; if (obj is Person) { Person p2 = obj as Person; rc = (this == p2); } return rc; } public override int GetHashCode() { return Id.GetHashCode(); } }
Secondo le migliori pratiche di Microsofts, il risultato del metodo Equals e il sovraccarico di equals (==) dovrebbero essere gli stessi.
CA2224: l’override equivale a un operatore di overload uguale a