Поиск…


замечания

Каждая реализация Equals должна соответствовать следующим требованиям:

  • Рефлексивный : объект должен быть равен самому себе.
    x.Equals(x) возвращает true .

  • Симметрично : нет разницы, если я сравниваю x с y или y с x - результат тот же.
    x.Equals(y) возвращает то же значение, что и y.Equals(x) .

  • Транзитив : если один объект равен другому объекту, и он равен третьему, первый должен быть равен третьему.
    if (x.Equals(y) && y.Equals(z)) возвращает true , тогда x.Equals(z) возвращает true .

  • Согласовано : если вы сравниваете объект с другим несколько раз, результат всегда один и тот же.
    Последовательные вызовы x.Equals(y) возвращают одно и то же значение, если объекты, на которые ссылаются x и y, не изменяются.

  • Сравнение с null : ни один объект не равен null .
    x.Equals(null) возвращает false .

Реализации GetHashCode :

  • Совместимость с Equals : если два объекта равны (это означает, что Equals возвращает true), то GetHashCode должен возвращать одинаковое значение для каждого из них.

  • Большой диапазон : если два объекта не равны ( Equals говорит false), должна быть высокая вероятность, что их хэш-коды различны. Идеальное хеширование часто невозможно, так как существует ограниченное количество значений на выбор.

  • Дешево : во всех случаях вычислять хеш-код должно быть недорого.

См. Руководство по перегрузке Equals () и оператору ==

Поведение по умолчанию по умолчанию.

Equals объявляется в самом классе Object .

public virtual bool Equals(Object obj);

По умолчанию Equals имеет следующее поведение:

  • Если экземпляр является ссылочным типом, то Equals вернет true, только если ссылки совпадают.

  • Если экземпляр является типом значения, то Equals вернет true, только если тип и значение совпадают.

  • string - это особый случай. Он ведет себя как тип значения.

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

Запись хорошего GetHashCode переопределения

GetHashCode оказывает значительное влияние на словар <> и HashTable.

Хорошие методы GetHashCode

  • должны иметь равномерное распределение
    • каждое целое число должно иметь примерно равную вероятность возврата для случайного экземпляра
    • если ваш метод возвращает одно и то же целое число (например, константу «999») для каждого экземпляра, у вас будет плохая производительность
  • должен быть быстрым
    • Это НЕ криптографические хеши, где медлительность - это функция
    • чем медленнее ваша хэш-функция, тем медленнее ваш словарь
  • должен возвращать тот же HashCode в двух экземплярах, что Equals оценивает true
    • если они этого не делают (например, потому что GetHashCode возвращает случайное число), элементы не могут быть найдены в List , Dictionary или аналогичном.

Хорошим методом реализации GetHashCode является использование одного простого числа в качестве начального значения и добавление хэш-кодов полей типа, умноженного на другие простые числа, на:

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

Для хэш-функции должны использоваться только те поля, которые используются в Equals методе.

Если у вас есть необходимость рассматривать один и тот же тип по-разному для словаря / хэш-таблиц, вы можете использовать IEqualityComparer.

Переопределить Equals и GetHashCode на пользовательских типах

Для класса Person like:

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

Но определяя Equals и GetHashCode следующим образом:

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

Также использование LINQ для разных запросов для людей будет проверять как Equals и 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

Равно и GetHashCode в IEqualityComparator

Для данного типа 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

Но определяя Equals и GetHashCode в 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

Обратите внимание, что для этого запроса два объекта считаются равными, если оба значения Equals true, а GetHashCode возвращают одинаковый хэш-код для двух человек.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow