Qual è la differenza tra IEquatable e semplicemente ignorando Object.Equals ()?


185

Voglio che la mia Foodclasse sia in grado di testare ogni volta che è uguale a un'altra istanza di Food. Lo userò successivamente contro un elenco e voglio usare il suo List.Contains()metodo. Devo implementare IEquatable<Food>o semplicemente sostituire Object.Equals()? Da MSDN:

Questo metodo determina l'uguaglianza utilizzando il comparatore di uguaglianza predefinito, come definito dall'implementazione dell'oggetto del metodo IEquatable.Equals per T (il tipo di valori nell'elenco).

Quindi la mia prossima domanda è: quali funzioni / classi del framework .NET fanno uso Object.Equals()? Dovrei usarlo in primo luogo?



possibile duplicato di Understanding IEquatable
nawfal

Risposte:


214

Il motivo principale è la prestazione. Quando i generici sono stati introdotti in .NET 2.0 sono stati in grado di aggiungere un po 'di lezioni di pulito, come List<T>, Dictionary<K,V>, HashSet<T>, ecc Queste strutture fanno pesante uso di GetHashCodee Equals. Ma per i tipi di valore questo ha richiesto il pugilato. IEquatable<T>consente a una struttura di implementare un Equalsmetodo fortemente tipizzato , quindi non è necessario il pugilato. Prestazioni quindi molto migliori quando si utilizzano tipi di valore con raccolte generiche.

I tipi di riferimento non ne beneficiano molto, ma l' IEquatable<T>implementazione ti consente di evitare un cast dal System.Objectquale può fare la differenza se viene chiamato frequentemente.

Come notato sul blog di Jared Parson, tuttavia, è ancora necessario implementare le sostituzioni di oggetti.


Esiste un cast tra tipi di riferimento? Ho sempre pensato che i cast fossero solo "dichiarazioni" che fai al compilatore quando assegni alcuni cast non ovvi da un tipo di oggetto a un altro. Questo è, dopo aver compilato, che il codice non avrebbe nemmeno saputo che c'era un cast lì.
divorato elisio il

7
Questo è vero in C ++ ma non nei linguaggi .NET che applicano la sicurezza dei tipi. Esiste un cast di runtime e se il cast non riesce, viene generata un'eccezione. Quindi c'è una piccola penalità di runtime da pagare per il casting. Il compilatore può ottimizzare gli upstream Per esempio oggetto o = (oggetto) "stringa"; Ma downcasting - string s = (string) o; - deve avvenire in fase di esecuzione.
Josh,

1
Vedo. Per caso hai un posto dove posso ottenere quel tipo di informazioni "più profonde" su .NET? Grazie!
divorato elisio il

7
Consiglierei CLR via C # di Jeff Richter e C # in Depth di Jon Skeet. Per quanto riguarda i blog, i blog di Wintellect sono buoni, i blog msdn, ecc.
Josh,

L' IEquatable<T>interfaccia fa altro che ricordare a uno sviluppatore di includere un public bool Equals(T other) membro nella classe o nella struttura? La presenza o l'assenza dell'interfaccia non fa alcuna differenza in fase di esecuzione. Il sovraccarico di Equalssembrerebbe essere tutto ciò che è necessario.
mikemay,

48

Secondo il MSDN :

Se si implementa IEquatable<T>, è necessario sostituire anche le implementazioni della classe base Object.Equals(Object)e in GetHashCode modo che il loro comportamento sia coerente con quello del IEquatable<T>.Equals metodo. Se esegui l'override Object.Equals(Object), l'implementazione override viene anche chiamata nelle chiamate al Equals(System.Object, System.Object)metodo statico sulla tua classe. Ciò garantisce che tutte le invocazioni del Equalsmetodo restituiscano risultati coerenti.

Quindi sembra che non ci sia una reale differenza funzionale tra i due, tranne che uno dei due potrebbe essere chiamato a seconda di come viene utilizzata la classe. Dal punto di vista delle prestazioni, è meglio utilizzare la versione generica perché non vi è alcuna penalità di boxe / unboxing ad essa associata.

Dal punto di vista logico, è anche meglio implementare l'interfaccia. Sostituire l'oggetto in realtà non dice a nessuno che la tua classe è effettivamente equabile. L'override può essere semplicemente una classe do nothing o un'implementazione superficiale. Usando l'interfaccia dice esplicitamente: "Ehi, questa cosa è valida per il controllo dell'uguaglianza!" È solo un design migliore.


9
Le strutture dovrebbero sicuramente implementare iEquatable (of theirOwnType) se verranno utilizzate come chiavi in ​​un dizionario o in una raccolta simile; offrirà un notevole incremento delle prestazioni. Le classi non ereditabili riceveranno un leggero incremento delle prestazioni implementando IEquatable (of theirOwnType). Le classi ereditabili dovrebbero // non // implementare IEquatable.
supercat

30

Estendere ciò che Josh ha detto con un esempio pratico. +1 a Josh - Stavo per scrivere lo stesso nella mia risposta.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

In questo modo, ho un metodo Equals () riutilizzabile pronto all'uso per tutte le mie classi derivate.


Ancora un'altra domanda. Qual è il vantaggio di usare "obj come EntityBase" anziché (EntityBase) obj? Solo una questione di stile o c'è qualche vantaggio?
divorò l'elisio il

22
in caso di "obj come EntityBase" - se obj non è di tipo EntityBase, passerà "null" e continuerà senza errori o eccezioni, ma in caso di "(EntityBase) obj", proverà con forza a lanciare obj a EntityBase e se l'oggetto non è di tipo EntityBase, genererà InvalidCastException. E sì, "as" può essere applicato solo ai tipi di riferimento.
questo. __curious_geek

1
Il collegamento di Josh al blog di Jared Par sembra suggerire che è necessario sostituire anche GetHashCode. Non è così?
Amichevole

3
Non ho davvero il valore extra fornito dalla tua implementazione. Puoi chiarire il problema che la tua classe base astratta risolve?
Mert Akcakaya,

1
@Amicable: sì, ogni volta che si esegue l'override di Object.Equals (Object), è necessario eseguire l'override di GetHashCode affinché i contenitori funzionino.
namford,

0

Se chiamiamo object.Equals , impone costosi inscatolamento su tipi di valore. Ciò è indesiderabile in scenari sensibili alle prestazioni. La soluzione è quella di utilizzare IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

L'idea alla base IEquatable<T>è che dà lo stesso risultato diobject.Equals ma più rapidamente. Il vincolo where T : IEquatable<T>deve essere utilizzato con tipi generici come di seguito.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

altrimenti, si lega a slower object.Equals().

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.