L'esercizio di programmazione illustra diverse proprietà dell'uguaglianza in C#:
Object.Equals()== e !=. Questo è considerato più idiomatico rispetto all'uso del metodo Equals(), disponibile anch'esso per questi tipi. Chi programma in Java, quando torna al proprio linguaggio di origine, deve fare attenzione al fatto che, con le stringhe, == confronta per valore in C# ma per riferimento in Java.Equals() ereditato da object. Se con il test di uguaglianza vuoi assicurarti che due oggetti siano esattamente la stessa istanza, allora ti basterà affidarti all'implementazione di object. In caso contrario, devi eseguire l'override di object.Equals().Equals().Equals() di cui è stato eseguito l'override conterrà test di uguaglianza sui membri di tipi semplici, usando ==, e sui tipi di riferimento, con chiamate ricorsive a 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() si usa per confrontare due oggetti e stabilire se sono una sola e medesima istanza. Questo rende più chiaro il codice ed è indispensabile quando Equals() e == sono stati sottoposti a override o sovraccaricati.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), gli IDE in genere generano l'overload protected bool Equals(FacialFeatures other) da usare quando è coinvolta l'ereditarietà. Una classe derivata può chiamare Equals() della classe base e poi aggiungere il proprio test.== a meno che tu non abbia sovraccaricato l'operatore ==, oltre che il metodo Equals() nella classe (vedi l'esercizio operator-overloading), o a meno che non ti interessi soltanto che i riferimenti siano uguali.structs sono trattati nell'esercizio structs.tuples è trattata nell'esercizio tuples.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(), ma in assenza di LINQ occorre iterare su entrambe le collezioni e confrontare gli elementi uno per uno.== e != con le proprie classi, vedi l'esercizio operator-overloading.Object.GetHashCode()object.GetHashCode() restituisce un codice hash sotto forma di numero intero a 32 bit. Il codice hash è usato dalle classi dizionario e set, come Dictionary<T> e HashSet<T>, per archiviare e recuperare oggetti in modo efficiente. Nel caso dei dizionari, l'hashing riguarda le chiavi.Equals(), si esegua l'override anche di GetHashCode(). Esiste una relazione tra Equals() e GetHashCode() che deve valere perché le classi dizionario e set di hash, e ogni altra che usi un codice hash, si comportino correttamente. Ci si aspetta che il metodo venga implementato in modo da non tendere trappole ai manutentori che in futuro potrebbero aggiungere una collezione basata su codice hash.Equal() restituisce true), allora GetHashCode() dei due oggetti deve restituire lo stesso valore. Questo non vale nel verso opposto. Non è una relazione simmetrica. Immagina una funzione di ricerca che prima va in un «bucket» in base al codice hash e poi seleziona l'elemento specifico usando il test di uguaglianza.HashCode.Combine() passando i valori usati nel test di uguaglianza (o un sottoinsieme). Tieni presente che più informazioni fornisci a Combine(), più è probabile che l'implementazione dell'hash sia efficiente.public class Assessment
{
private int rating;
private Person boss;
public override int GetHashCode()
{
return HashCode.Combine(rating, boss);
}
}
Per migliorare leggermente le prestazioni, soprattutto quando gli oggetti appartengono a collezioni, puoi aggiungere un membro di overload, public bool Equals(T other).
Questo farà risparmiare una certa quantità di controlli di tipo per i tipi di riferimento e un passaggio di boxing per i tipi valore, perché non dovranno essere convertiti in un oggetto (boxed) come argomento di public override bool Equals(object other).
Se aggiungi l'interfaccia IEquatable<T> a una classe, questa richiederà che l'overload venga implementato. A meno che il codice non contenga routine che accettano oggetti di tipo IEquatable<T> (e che presumibilmente fanno qualcosa di interessante legato all'uguaglianza, indipendentemente dalla classe che la implementa), non c'è davvero nessun altro motivo valido per includere l'interfaccia.
IEqualityComparer<T>Se hai una classe che può essere identificata in modo univoco in due modi diversi, per esempio una classe Person con un SSID e un indirizzo email univoco, .NET offre un modo per far sì che due collezioni diverse usino test di uguaglianza e codici hash diversi. Ciascuna può avere un'implementazione diversa di IEqualityComparer<T>, con i propri metodi Equals() e GetHashCode(). Puoi avere un dizionario con chiave il SSID e un altro con chiave l'indirizzo email.
Quando si usa IEqualityComparer<T>, in genere si esegue comunque l'override di Equals() e GetHashCode() nella classe dell'elemento, per evitare problemi al di fuori delle classi di collezione.
Una considerazione da fare quando si usa IEqualityComparer<T> è che i metodi privati e simili non saranno disponibili per il test di uguaglianza.
Se è in gioco una sola collezione con hash, forse è meglio evitare IEqualityComparer<T> e incapsulare uguaglianza e codice hash nell'oggetto stesso. Non è l'ideale, tanto per cominciare, che una dipendenza fondamentale come quella tra oggetto e collezione con hash non possa essere imposta dal compilatore; ma con la logica di hashing e confronto altrove, sarebbe ancora più facile per un manutentore modificare i membri di una classe senza considerare le conseguenze sulla collezione.
Una primitiva che può mettere in difficoltà chi programma senza fare attenzione è il test di uguaglianza dei valori in virgola mobile. Se ne parla nel documento about.md del concetto floating-point-numbers.
Questo articolo mostra alcune delle decisioni da prendere in merito a uguaglianza ed ereditarietà. Ti riguarderanno solo se stai manipolando istanze di una classe e di una classe derivata e devi testare l'uguaglianza tra le due.