Szukaj…


Uwagi

Każde wdrożenie Equals musi spełniać następujące wymagania:

  • Odruchowe : Obiekt musi się równać.
    x.Equals(x) zwraca wartość true .

  • Symetryczny : nie ma różnicy, jeśli porównam x do y lub y do x - wynik jest taki sam.
    x.Equals(y) zwraca tę samą wartość co y.Equals(x) .

  • Przechodnie : Jeśli jeden obiekt jest równy drugiemu, a ten jest równy trzeciemu, pierwszy musi być równy trzeciemu.
    jeśli (x.Equals(y) && y.Equals(z)) zwraca true , to x.Equals(z) zwraca true .

  • Spójny : Jeśli porównasz obiekt z innym wiele razy, wynik będzie zawsze taki sam.
    Kolejne wywołania x.Equals(y) zwracają tę samą wartość, o ile obiekty, do których odnoszą się xiy, nie są modyfikowane.

  • Porównanie do null : Żaden obiekt nie jest równy null .
    x.Equals(null) zwraca false .

Implementacje GetHashCode :

  • Kompatybilny z Equals : Jeśli dwa obiekty są równe (co oznacza, że Equals zwraca true), wówczas GetHashCode musi zwrócić tę samą wartość dla każdego z nich.

  • Duży zakres: Jeśli dwa obiekty nie są równe ( Equals mówi false), nie powinno być wysokie prawdopodobieństwo ich kody hash są różne. Idealne haszowanie często nie jest możliwe, ponieważ istnieje ograniczona liczba wartości do wyboru.

  • Tani : We wszystkich przypadkach obliczenie kodu skrótu powinno być tanie.

Patrz: Wytyczne dotyczące przeciążania równa się () i operator ==

Domyślne zachowanie równe.

Equals jest zadeklarowana w samej klasie Object .

public virtual bool Equals(Object obj);

Domyślnie Equals ma następujące zachowanie:

  • Jeśli instancja jest typem odwołania, wówczas Equals zwróci wartość true tylko wtedy, gdy odwołania są takie same.

  • Jeśli instancja jest typem wartości, wówczas Equals zwróci wartość true tylko wtedy, gdy typ i wartość są takie same.

  • string jest specjalnym przypadkiem. Zachowuje się jak typ wartości.

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;
        }
    }
}

Pisanie dobrego zastąpienia GetHashCode

GetHashCode ma znaczący wpływ na wydajność na Dictionary <> i HashTable.

Dobre metody GetHashCode

  • powinien mieć równomierny rozkład
    • każda liczba całkowita powinna mieć mniej więcej taką samą szansę na powrót w przypadkowej instancji
    • jeśli twoja metoda zwraca tę samą liczbę całkowitą (np. stałą „999”) dla każdej instancji, będziesz mieć słabą wydajność
  • powinno być szybkie
    • NIE są to hashery kryptograficzne, w których spowolnienie jest cechą
    • im wolniejsza jest funkcja skrótu, tym wolniej działa słownik
  • musi zwrócić ten sam kod HashCode w dwóch instancjach, które Equals ocenia na true
    • jeśli nie (np. ponieważ GetHashCode zwraca liczbę losową), elementów nie można znaleźć na List , Dictionary itp.

Dobrą metodą zaimplementowania GetHashCode jest użycie jednej liczby pierwszej jako wartości początkowej i dodanie do niej skrótów pól typu pomnożonych przez inne liczby pierwsze:

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;
    }
}

Do funkcji skrótu należy używać tylko pól używanych w metodzie Equals .

Jeśli musisz traktować ten sam typ na różne sposoby dla Dictionary / HashTables, możesz użyć IEqualityComparer.

Zastąp równe i GetHashCode na niestandardowych typach

Dla klasy Person taka jak:

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

Ale zdefiniowanie Equals i GetHashCode w następujący sposób:

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

Również użycie LINQ do różnych zapytań dotyczących osób sprawdzi zarówno Equals jak i 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

Równa się i GetHashCode w IEqualityComparator

Dla danego typu 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

Ale zdefiniowanie Equals i GetHashCode w 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

Zauważ, że dla tego zapytania dwa obiekty zostały uznane za równe, jeśli zarówno Equals zwróciło wartość true, a GetHashCode zwrócił ten sam kod skrótu dla dwóch osób.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow