Indicazioni per l'override del metodo Equals() e dell'operatore di uguaglianza == (Guida per programmatori C#)

Aggiornamento: novembre 2007

In C# esistono due tipi diversi di uguaglianza: di riferimenti e di valori. L'uguaglianza di valori viene intesa nel significato generico di uguaglianza, ossia due oggetti che contengono gli stessi valori. Ad esempio, due integer di valore 2 presentano un'uguaglianza di valori. L'uguaglianza di riferimenti non riguarda due oggetti da confrontare, ma due riferimenti allo stesso oggetto. Questa situazione può verificarsi tramite una semplice assegnazione, come illustrato nell'esempio seguente:

System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Questo esempio di codice contiene un unico oggetto ma diversi riferimenti a tale oggetto: a e b. Poiché entrambi fanno riferimento allo stesso oggetto, presentano un'uguaglianza di riferimenti. Due oggetti con uguaglianza di riferimenti presentano anche un'uguaglianza di valori, mentre l'uguaglianza dei valori non garantisce l'uguaglianza dei riferimenti.

Per verificare l'uguaglianza di riferimenti, utilizzare ReferenceEquals. Per verificare l'uguaglianza dei valori, utilizzare Equals.

Override di Equals

Essendo Equals un metodo virtuale, consente a qualsiasi classe di eseguire l'override della relativa implementazione. Qualsiasi classe che rappresenta un valore, sostanzialmente qualunque tipo di valore, o un insieme di valori come gruppo, ad esempio una classe di numeri complessi, deve eseguire l'override di Equals. Se il tipo implementa IComparable, deve eseguire l'override di Equals.

La nuova implementazione di Equals deve rispettare tutte le garanzie di Equals:

  • x.Equals(x) restituisce true.

  • x. Equals (y) restituisce lo stesso valore di y. Equals (x).

  • Se (x. Equals (y) && y. Equals (z)) restituisce true, anche x. Equals (z) restituisce true.

  • Le successive chiamate di x. Equals (y) restituiscono lo stesso valore purché gli oggetti a cui x e y fanno riferimento non vengano modificati.

  • x. Equals (null) restituisce false soltanto per tipi di valore non nullable. Per ulteriori informazioni, vedere Tipi nullable (Guida per programmatori C#).

La nuova implementazione di Equals non deve generare eccezioni. Qualsiasi classe che esegua l'override di Equals deve eseguire anche l'override di Object.GetHashCode. È inoltre opportuno che, in aggiunta a Equals (oggetto), tutte le classi implementino anche Equals (tipo) per i rispettivi tipi, in modo da migliorare le prestazioni. Esempio:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Qualsiasi classe derivata che può chiamare Equals sulla classe base deve effettuare questa operazione prima di terminare il confronto. Nell'esempio seguente, Equals chiama la classe base Equals, che verifica la presenza di un parametro null e confronta il tipo del parametro con il tipo della classe derivata. In questo modo l'attività di controllo del nuovo campo di dati dichiarato sulla classe derivata viene lasciata all'implementazione di Equals sulla classe derivata:

class ThreeDPoint : TwoDPoint
{
    public readonly int z;

    public ThreeDPoint(int x, int y, int z)
        : base(x, y)
    {
        this.z = z;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter cannot be cast to ThreeDPoint return false:
        ThreeDPoint p = obj as ThreeDPoint;
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return base.Equals(obj) && z == p.z;
    }

    public bool Equals(ThreeDPoint p)
    {
        // Return true if the fields match:
        return base.Equals((TwoDPoint)p) && z == p.z;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode() ^ z;
    }
}

Override dell'operatore ==

Per impostazione predefinita, l'operatore == verifica l'uguaglianza di riferimenti stabilendo se due riferimenti indicano lo stesso oggetto. Pertanto, i tipi di riferimento non devono implementare l'operatore == per ottenere questa funzionalità. Se un tipo non è modificabile, ossia non è possibile modificare i dati contenuti nell'istanza, per confrontare l'uguaglianza di valori può risultare utile eseguire l'overload dell'operatore == anziché verificare l'uguaglianza di riferimenti. Gli oggetti non modificabili possono infatti essere considerati identici purché abbiano lo stesso valore. L'override dell'operatore == non è consigliato nel caso dei tipi modificabili.

Le implementazioni dell'operatore di overload == non devono generare eccezioni. Qualsiasi tipo che esegua l'overload dell'operatore == deve eseguire l'overload anche dell'operatore !=. Esempio:

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}
Nota:

Un errore comune negli overload dell'operatore == è quello di utilizzare (a == b), (a == null) o (b == null) per verificare l'uguaglianza di riferimenti. Questa operazione genera una chiamata all'operatore di overload ==, determinando un ciclo infinito. Per ovviare a questo problema, utilizzare ReferenceEquals oppure effettuare il cast del tipo in Object.

Vedere anche

Concetti

Guida per programmatori C#

Riferimenti

Istruzioni, espressioni e operatori (Guida per programmatori C#)

Operatori (Guida per programmatori C#)

Operatori che supportano l'overload (Guida per programmatori C#)

Operatore == (Riferimenti per C#)

Operatore () (Riferimenti per C#)