Ricerca…


Osservazioni

Ogni implementazione di Equals deve soddisfare i seguenti requisiti:

  • Riflessivo : un oggetto deve essere uguale a se stesso.
    x.Equals(x) restituisce true .

  • Simmetrico : non vi è alcuna differenza se si confronta x con y o y con x - il risultato è lo stesso.
    x.Equals(y) restituisce lo stesso valore di y.Equals(x) .

  • Transitivo : se un oggetto è uguale a un altro oggetto e questo è uguale a un terzo, il primo deve essere uguale al terzo.
    if (x.Equals(y) && y.Equals(z)) restituisce true , quindi x.Equals(z) restituisce true .

  • Coerentemente : se si confronta un oggetto con un altro più volte, il risultato è sempre lo stesso.
    Le successive chiamate di x.Equals(y) restituiscono lo stesso valore fintanto che gli oggetti referenziati da x e y non vengono modificati.

  • Confronto con null : nessun oggetto è uguale a null .
    x.Equals(null) restituisce false .

Implementazioni di GetHashCode :

  • Compatibile con Equals : se due oggetti sono uguali (ovvero che Equals restituisce true), allora GetHashCode deve restituire lo stesso valore per ognuno di essi.

  • Ampio intervallo : se due oggetti non sono uguali ( Equals dice false), ci dovrebbe essere un'alta probabilità che i loro codici hash siano distinti. Spesso l'hashing perfetto non è possibile in quanto esiste un numero limitato di valori tra cui scegliere.

  • Economico : dovrebbe essere poco costoso calcolare il codice hash in tutti i casi.

Vedi: Linee guida per il sovraccarico di Equals () e Operatore ==

Predefinito Comportamento uguale.

Equals è dichiarato nella classe Object stessa.

public virtual bool Equals(Object obj);

Per impostazione predefinita, Equals ha il seguente comportamento:

  • Se l'istanza è un tipo di riferimento, Equals restituirà true solo se i riferimenti sono uguali.

  • Se l'istanza è un tipo di valore, Equals restituirà true solo se il tipo e il valore sono uguali.

  • string è un caso speciale. Si comporta come un tipo di valore.

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            //areFooClassEqual: False
            Foo fooClass1 = new Foo("42");
            Foo fooClass2 = new Foo("42");
            bool areFooClassEqual = fooClass1.Equals(fooClass2);
            Console.WriteLine("fooClass1 and fooClass2 are equal: {0}", areFooClassEqual);
            //False

            //areFooIntEqual: True
            int fooInt1 = 42;
            int fooInt2 = 42;
            bool areFooIntEqual = fooInt1.Equals(fooInt2);
            Console.WriteLine("fooInt1 and fooInt2 are equal: {0}", areFooIntEqual);

            //areFooStringEqual: True
            string fooString1 = "42";
            string fooString2 = "42";
            bool areFooStringEqual = fooString1.Equals(fooString2);
            Console.WriteLine("fooString1 and fooString2 are equal: {0}", areFooStringEqual);
        }
    }

    public class Foo
    {
        public string Bar { get; }

        public Foo(string bar)
        {
            Bar = bar;
        }
    }
}

Scrittura di un buon override GetHashCode

GetHashCode ha importanti effetti sulle prestazioni del dizionario <> e HashTable.

Buoni metodi GetHashCode

  • dovrebbe avere una distribuzione uniforme
    • ogni numero intero dovrebbe avere una probabilità all'incirca uguale di restituire per un'istanza casuale
    • se il tuo metodo restituisce lo stesso numero intero (ad es. la costante '999') per ogni istanza, avrai cattive prestazioni
  • dovrebbe essere veloce
    • Questi NON sono hash crittografici, dove la lentezza è una caratteristica
    • più lenta è la tua funzione hash, più lento è il tuo dizionario
  • deve restituire lo stesso HashCode su due istanze che Equals restituisce true
    • se non lo fanno (es. perché GetHashCode restituisce un numero casuale), gli elementi potrebbero non essere trovati in un List , Dictionary o simile.

Un buon metodo per implementare GetHashCode consiste nell'utilizzare un numero primo come valore iniziale e aggiungere gli hashcode dei campi del tipo moltiplicato per altri numeri primi a quello:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 3049; // Start value (prime number).

        // Suitable nullity checks etc, of course :)
        hash = hash * 5039 + field1.GetHashCode();
        hash = hash * 883 + field2.GetHashCode();
        hash = hash * 9719 + field3.GetHashCode();
        return hash;
    }
}

Solo i campi che sono usati in Equals -method dovrebbero essere usati per la funzione hash.

Se si ha la necessità di trattare lo stesso tipo in modi diversi per Dizionario / HashTables, è possibile utilizzare IEqualityComparer.

Override Equals e GetHashCode su tipi personalizzati

Per una classe Person come:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Clothes { get; set; }
}

var person1 = new Person { Name = "Jon", Age = 20, Clothes = "some clothes" };
var person2 = new Person { Name = "Jon", Age = 20, Clothes = "some other clothes" };

bool result = person1.Equals(person2); //false because it's reference Equals

Ma definendo Equals e GetHashCode come segue:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Clothes { get; set; }

    public override bool Equals(object obj)
    {
        var person = obj as Person;
        if(person == null) return false;
        return Name == person.Name && Age == person.Age; //the clothes are not important when comparing two persons
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode()*Age;
    }
}

var person1 = new Person { Name = "Jon", Age = 20, Clothes = "some clothes" };
var person2 = new Person { Name = "Jon", Age = 20, Clothes = "some other clothes" };

bool result = person1.Equals(person2); // result is true

Anche usando LINQ per fare query diverse su persone controllerà sia Equals che GetHashCode :

var persons = new List<Person>
{
     new Person{ Name = "Jon", Age = 20, Clothes = "some clothes"},
     new Person{ Name = "Dave", Age = 20, Clothes = "some other clothes"},
     new Person{ Name = "Jon", Age = 20, Clothes = ""}
};

var distinctPersons = persons.Distinct().ToList();//distinctPersons has Count = 2

Uguale a GetHashCode in IEqualityComparator

Per tipo di dato Person :

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Clothes { get; set; }
}

List<Person> persons = new List<Person>
{
    new Person{ Name = "Jon", Age = 20, Clothes = "some clothes"},
    new Person{ Name = "Dave", Age = 20, Clothes = "some other clothes"},
    new Person{ Name = "Jon", Age = 20, Clothes = ""}
};

var distinctPersons = persons.Distinct().ToList();// distinctPersons has Count = 3

Ma definendo Equals e GetHashCode in IEqualityComparator :

public class PersonComparator : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.Name == y.Name && x.Age == y.Age; //the clothes are not important when comparing two persons;
    }

    public int GetHashCode(Person obj) { return obj.Name.GetHashCode() * obj.Age; }
}

var distinctPersons = persons.Distinct(new PersonComparator()).ToList();// distinctPersons has Count = 2

Si noti che per questa query, due oggetti sono stati considerati uguali se entrambi gli Equals restituito true e il GetHashCode ha restituito lo stesso codice hash per le due persone.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow