C# Language
Равные и GetHashCode
Поиск…
замечания
Каждая реализация 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
объявляется в самом классе 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
возвращают одинаковый хэш-код для двух человек.