Як відомо, в Сі-Шарп всі класи є спадкоємцями базового класу object. У ньому є три віртуальних методи - ToString, Equals і GetHashCode. На цьому уроці ми поговоримо з вами про останні два методи, а також про оператор «==».
Скажу відразу, що питання різниці між оператором рівності «==» і методом Equals є класичним питанням на співбесіді на вакансію програміста Сі-Шарп.
Оператор рівності «==»
За замовчуванням при роботі з посилальними типами даних (всі класи крім string, інтерфейси, делегати) оператор «==» перевіряє рівність посилань. Він повертає true, коли обидва посилання вказують на один об'єкт, в іншому випадку - false. Наведу код, який демонструє роботу даного оператора з посилальними типами:
static void Main (string [] args)
{
object o1 = new object ();
object o2 = new object ();
object o3 = o1;
Console.WriteLine (o1 == o2); // false
Console.WriteLine (o1 == o3); // true
}
Тут створюється два об'єкти, посилання на які записуються в змінні o1 і o2. Далі посилання o1 копіюється в змінну o3 (o1 і o3 вказують на один об'єкт). У підсумку маємо false при порівнянні посилань o1 і o2, і true при o1 і o3.
метод Equals
Метод Equals приймає один аргумент - об'єкт, який буде порівнюватися з поточним об'єктом, і визначає, чи рівні між собою ці об'єкти. Тут уже йдеться про рівність полів об'єктів, а не посилань. Цей метод віртуальний, і його базова реалізація це просто перевірка рівності посилань оператором «==». Але коли ми створюємо якийсь клас, і нам необхідно реалізувати можливість перевірки ідентичності об'єктів, слід перевизначити саме даний метод, а не скористатися перевантаженням оператора «==», щоб не плутати базові призначення цих інструментів порівнювання.
Перевантаження методу Equals
При перевизначенні методу Equals слід подбати про те, щоб цей метод повертав false у випадках, коли в метод передано значення NULL, коли переданий об'єкт не можна привести до типу поточного об'єкта, ну і коли поля об'єктів відрізняються.
Візьмемо вже знайомий нам клас Money з попереднього уроку, і перевизначити в ньому метод Equals:
public class Money
{
public decimal Amount {get; set; }
public string Unit {get; set; }
public Money (decimal amount, string unit)
{
Amount = amount;
Unit = unit;
}
public override bool Equals (object obj)
{
if (obj == null)
return false;
Money m = obj as Money; // Повертає null якщо об'єкт не можна привести до типу Money
if (m as Money == null)
return false;
return m.Amount == this.Amount && m.Unit == this.Unit;
}
}
class Program
{
static void Main (string [] args)
{
Money m1 = new Money (100, "UA");
Money m2 = new Money (100, "UA");
Money m3 = new Money (100, "USD");
Money m4 = m1;
Console.WriteLine (m1.Equals (m2)); // true
Console.WriteLine (m1.Equals (m3)); // false
Console.WriteLine (m1 == m2); // false
Console.WriteLine (m1 == m4); // true
Console.ReadLine ();
}
}
Як бачимо, в коді вище метод Equals і оператор «==» працюють відповідно до своїх базових визначень.
Також для підвищення продуктивності при перевизначенні методу Equals рекомендується перевантажувати його реалізацією з типом аргументу відповідного класу, в якому він первизначається:
public bool Equals (Money obj) // аргумент типу Money
{
if (obj == null)
return false;
return obj.Amount == this.Amount && obj.Unit == this.Unit;
}
метод GetHashCode
Даний метод, як випливає з його назви, повертає хеш-код. Хеш-код це число відповідає значенню об'єкта. Це число ми отримуємо в результаті роботи деякого методу, який повинен мати наступні властивості:
- Він повинен повертати однаковий хеш-код кожного разу при виклику для одного і того ж об'єкта.
- Якщо є два рівних (еквівалентних) об'єкта, то хеш-код для них повинен бути однаковим. Тільки це не означає, що якщо об'єкти нерівні, то їх хеш-коди обов'язково будуть різними.
Метод GetHashCode використовується в таких структурах, як хеш-таблиці (Hashtable). Це ми зараз розглядати не будемо, але коректність їх роботи варто забезпечувати. Методи Equals і GetHashCode тісно пов'язані між собою, при перевизначенні одного з них, слід перевизначати і інший. Базова реалізація методу GetHashCode в класі object дуже умовна, і вона не забезпечує друга властивість, коли однакові об'єкти мають однакові хеш-коди.
static void Main (string [] args)
{
Money m1 = new Money (100, "UA");
Money m2 = new Money (100, "UA");
Console.WriteLine (m1.GetHashCode ()); // 456 ...
Console.WriteLine (m2.GetHashCode ()); // 411 ...
Console.ReadLine ();
}
Щоб це виправити, ми переобумовленої метод GetHashCode, і повертаємо хеш-код будь-яким способом, що залежить від поля / полів об'єкта:
public class Money
{
public decimal Amount {get; set; }
public string Unit {get; set; }
public Money (decimal amount, string unit)
{
Amount = amount;
Unit = unit;
}
public override bool Equals (object obj)
{
if (obj == null)
return false;
Money m = obj as Money;
if (m as Money == null)
return false;
return m.Amount == this.Amount && m.Unit == this.Unit;
}
public bool Equals (Money obj) // аргумент типу Money
{
if (obj == null)
return false;
return obj.Amount == this.Amount && obj.Unit == this.Unit;
}
public override int GetHashCode ()
{
int unitCode;
if (Unit == "RUR")
unitCode = 1;
else unitCode = 2;
return (int) Amount + unitCode;
}
}
class Program
{
static void Main (string [] args)
{
Money m1 = new Money (100, "UA");
Money m2 = new Money (100, "UA");
Money m3 = new Money (100, "USD");
Console.WriteLine (m1.GetHashCode ()); // 101
Console.WriteLine (m2.GetHashCode ()); // 101
Console.WriteLine (m3.GetHashCode ()); // 102
Console.ReadLine ();
}
}
Тут в якості хеш-коду повертається кількість грошей (ціла частина) плюс код валюти. В результаті тепер друга умова виконується.
Домашнє завдання
Створіть клас коло з полями координати центру і радіус і перевизначите в ньому коректно методи Equals і GetHashCode. Кола рівні якщо у них однакові координати центру і радіуси.