Що потрібно замінити у структурі, щоб забезпечити належну роботу рівності?


74

Як сказано в назві: чи потрібно перевизначати ==оператора? як щодо .Equals()методу? Щось мені не вистачає?


Також слідкуйте за stackoverflow.com/questions/1972262/… - якщо ви не обережні, тоді порівняння вашої структури (типу значення) з нулем буде скомпільовано чудово, але не робитиме те, що очікуєте.
йойо

Відповіді:


90

Приклад із msdn

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
        return obj is Complex c && this == c;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}

Цікаво, чи не краще буде використовувати продуктивність, Complex other = obj as Complexа потім перевірити, чи не other == nullзамість використання, isа потім акторський склад
Клеман

5
@Clement: Ви не можете зробити це для структури; результат не може бути нульовим. Ви отримаєте помилку компіляції.
Метью Ватсон,

@MatthewWatson: Я думаю, що можна було б використовувати Complex? other = obj as Complex?, але типи, що допускають обнулення , часто не піддаються ефективності.
supercat

1
@HaraldCoppoolse - типи значень у природно запечатаних, тому неможливо отримати значення, MyComplexяк ви пропонуєте.
M.Babcock

1
Чому б не obj - це SaveOptions op && this == op; ?
Біллі Джейк О'Коннор

45

Вам також слід реалізувати IEquatable <T>. Ось уривок із керівних принципів проектування:

ВБЕРІТИ IEquatable на типи значень. Метод Object.Equals для типів значень спричиняє бокс, і його реалізація за замовчуванням не дуже ефективна, оскільки використовує посилання. IEquatable.Equals може запропонувати набагато кращу продуктивність і може бути реалізований так, щоб це не спричиняло боксу.

public struct Int32 : IEquatable<Int32> {
    public bool Equals(Int32 other){ ... }
}

ВИКОРИСТОВУЙТЕ ті самі вказівки, що й для перевизначення Object.Equals при впровадженні IEquatable.Equals. Див. Розділ 8.7.1 для детальних вказівок щодо заміни Object.Equals


Отже, це використовується лише для типів значень? (не посилання?)
UpTheCreek

1
Оскільки типи посилань не потрібно вставляти в поле, коли передаються як об'єкт, ergo, IEquatable <T> не забезпечить жодної вигоди. Типи значень, як правило, повністю копіюються у стек (або у зовнішній макет типів), тому, щоб отримати посилання на об’єкт і правильно обробляти час життя об’єкта, його потрібно упакувати (обернути спеціальним типом) і скопіювати до купи; лише тоді посилання на об'єкт купи може бути передано такій функції, як Object.Equals.
gimpf

15

На жаль, у мене недостатньо репутації, щоб коментувати інші записи. Тож я публікую тут можливе вдосконалення найкращого рішення.

Виправте мене, якщо я помиляюся, але реалізація згадана вище

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
      return obj is Complex && this == (Complex)obj;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}

Має великий недолік. Я посилаюся на

  public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }

XORing є симетричним, тому Complex (2,1) і Complex (1,2) дадуть однаковий хеш-код.

Напевно, нам слід зробити щось подібне:

  public override int GetHashCode() 
   {
      return re.GetHashCode() * 17 ^ im.GetHashCode();
   }

7
Зіткнення хеш-кодів не обов'язково є проблемою. Насправді у вас завжди буде шанс зіткнення (читайте про пігіонові отвори / парадокс дня народження). У вашому випадку зіткнення комплексу (1,4) та комплексу (4,1) (правда, зіткнень було менше) це залежить від ваших даних . Хеш-код використовується для швидкого відсіювання 99,999% небажаних об’єктів (наприклад, у словнику). Останнє слово мають оператори рівності.
DarcyThomas

Сказано, чим більше у вас властивостей на структурі, тим більше шансів зіткнення. Це може бути кращий алгоритм хешування: stackoverflow.com/a/263416/309634
DarcyThomas

9

Здебільшого ви можете уникати реалізації Equals і GetHashcode у структурах, оскільки компілятор автоматично реалізує типи типів, використовуючи побітове вміст + відображення для посилальних членів.

Погляньте на цю публікацію: Що найкраще підходить для структури / класів сховища даних?

Отже, для зручності використання ви все ще можете застосувати == та! =.

Але більшу частину часу ви можете уникати впровадження рівних та GetHashcode.
Випадок, коли вам довелося б застосувати Equals і GetHashCode, - це поле, яке ви не хочете брати до уваги.
Наприклад, поле, яке змінюється з часом, наприклад Вік людини або миттєва швидкість автомобіля (ідентичність об’єкта не повинна змінюватися, якщо ви хочете знайти його там же в словнику)

З повагою, найкращий код


Рефлексія відбувається набагато повільніше, ніж ручна реалізація. Якщо ви дбаєте про продуктивність, НЕ пишіть їх вручну.
Károly Ozsvárt

3

Основна різниця між ними полягає в тому, що ==оператор є статичним, тобто відповідний метод, який потрібно викликати, визначається під час компіляції, тоді як Equalsметод викликається динамічно на екземплярі.
Визначення обох - це, мабуть, найкраще, навіть якщо це менш важливо у випадку структур, оскільки структури не можуть бути розширені (структура не може успадкувати від іншої).


1

Для повноти я б порадив також Equalsметод перевантаження :

public bool Equals(Complex other) 
{
   return other.re == re && other.im == im;
}

це справжнє вдосконалення роздумів, оскільки не відбувається боксу вхідного аргументу Equals(Object obj) методу

Деякі найкращі практики використання типів цінностей:

  • зробити їх незмінними
  • перевизначити рівне (те, що приймає об'єкт як аргумент);
  • перевантажити Equals, щоб взяти інший екземпляр того ж типу значення (наприклад, * Equals (Складне інше));
  • оператори перевантаження == та! =;
  • замінити GetHashCode

Це випливає з цієї публікації: http://theburningmonk.com/2015/07/beware-of-implicit-boxing-of-value-types/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.