проверка на равенство С#

Каков ваш подход к написанию проверок на равенство для создаваемых вами structs и classes?

1) Требуется ли для «полной» проверки на равенство столько шаблонного кода (например, override Equals, override GetHashCode, общие Equals, operator==, operator!=)?

2) Указываете ли вы явно, что ваши классы моделируют интерфейс IEquatable<T>?

3) Правильно ли я понимаю, что нет реального способа автоматически применять переопределения Equals, когда я вызываю что-то вроде a == b и мне всегда приходится реализовывать оба элемента Equals и operator==?


person Yippie-Ki-Yay    schedule 12.12.2010    source источник


Ответы (4)


Вы правы, это много шаблонного кода, и вам нужно реализовать все отдельно.

Я бы порекомендовал:

  • Если вы вообще собираетесь реализовать равенство значений, переопределите GetHashCode и Equals(object) — создание перегрузок для == и реализация IEquatable<T> без этого могут привести к очень неожиданному поведению.
  • Я бы всегда реализовывал IEquatable<T>, если вы переопределяете Equals(object) и GetHashCode
  • Я только реже перегружаю оператор ==
  • Правильная реализация равенства для незапечатанных классов сложна и может привести к неожиданным/нежелательным результатам. Если вам нужно равенство типов в иерархии, реализуйте IEqualityComparer<T>, выражающее интересующее вас сравнение.
  • Равенство для изменяемых типов, как правило, плохая идея, так как два объекта могут быть равными, а затем неравными позже... если объект мутирует (влияющим на равенство) после того, как он использовался в качестве ключа в хеш-таблице, вы выиграли не найти его снова.
  • Некоторые шаблоны для структур немного отличаются... но, как и Марк, я очень редко пишу свои собственные структуры.

Вот пример реализации:

using System;

public sealed class Foo : IEquatable<Foo>
{
    private readonly string name;
    public string Name { get { return name; } }

    private readonly int value;
    public int Value { get { return value; } }

    public Foo(string name, int value)
    {
        this.name = name;
        this.value = value;
    }

    public override bool Equals(object other)
    {
        return Equals(other as Foo);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        hash = hash * 31 + (name == null ? 0 : name.GetHashCode());
        hash = hash * 31 + value;
        return hash;
    }

    public bool Equals(Foo other)
    {
        if ((object) other == null)
        {
            return false;
        }
        return name == other.name && value == other.value;
    }

    public static bool operator ==(Foo left, Foo right)
    {
        return object.Equals(left, right);
    }

    public static bool operator !=(Foo left, Foo right)
    {
        return !(left == right);
    }
}

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

Реализация == немного менее эффективна, чем могла бы быть, так как она будет вызывать Equals(object), которая должна выполнять проверку динамического типа... но альтернатива еще более стандартна, как эта :

public static bool operator ==(Foo left, Foo right)
{
    if ((object) left == (object) right)
    {
        return true;
    }

    // "right" being null is covered in left.Equals(right)
    if ((object) left == null)
    {
        return false;
    }
    return left.Equals(right);
}
person Jon Skeet    schedule 12.12.2010
comment
2 незначительных предложения для второго блока кода: 1) Не следует ли переместить (object) left == (object) right из == в общий Equals? Итак, что дает скорость (конечно, это зависит, но в худшем случае) проверки равенства ссылок даже для универсального метода Equals? 2) вам не нужна вторая нулевая проверка (object) right == null в ==, поскольку вы, по сути, делаете это в общем Equals. Смотрите мой пост.. - person nawfal; 17.12.2012
comment
@nawfal: я не думаю, что есть большой смысл делать это в общем случае Equals - в любом случае это будет быстро в случаях, когда это верно верно, и в случаях, когда это неверно, это добавление дополнительной проверки без какой-либо выгоды. Что касается нулевой части - это потребует повторной проверки динамического типа. Да, можно спорить и за то, и за другое - но я достаточно доволен тем, что написал два года назад... - person Jon Skeet; 17.12.2012
comment
@nawfal: Гм, вы правы - там не будет динамической проверки. Я удалю правую часть здесь... - person Jon Skeet; 17.12.2012
comment
@JonSkeet так неприятно писать этот шаблонный код снова и снова, когда в таких вещах так легко ошибиться. К счастью, фрагменты могут помочь в этом отношении! Я бы попытался уйти, написав общий абстрактный базовый класс, который выполняет эту работу (я знаю о потенциальных опасностях, но если я могу справиться, тогда все в порядке), который будет производным от базовых классов, но это ограничивает производные классы от наследования из другого базового класса.. Просто говорю.. - person nawfal; 17.12.2012

Я редко делаю что-то особенное для занятий; для большинства обычных объектов ссылочное равенство работает нормально.

Я еще реже пишу struct; но поскольку структуры представляют значения, обычно уместно обеспечить равенство и т. д. Обычно это касается всего; Равно, ==, != и IEquatable<T> (поскольку это позволяет избежать упаковки в сценариях, использующих EqualityComparer<T>.Default.

Шаблон обычно не слишком проблематичен, но здесь могут помочь инструменты IIRC, такие как resharper.

Да, рекомендуется синхронизировать Equals и ==, и это нужно делать явно.

person Marc Gravell    schedule 12.12.2010

Вам просто нужно реализовать operator== для a==b .
Поскольку мне нравятся мои данные в словарях, иногда я переопределяю GetHashCode.
Затем я реализую Equals (как не упомянутый стандарт... это потому, что нет ограничений на равенство при использовании дженериков) и определяю реализацию IEquatable. Поскольку я собираюсь это сделать, я мог бы указать свои реализации == и != на Equals. :)

person basarat    schedule 12.12.2010

См. Что лучше всего подходит для сравнения двух Экземпляры ссылочного типа?

Вы можете избежать шаблонного кода (надеемся, что команда C#/VS предложит что-то простое для разработчиков в своей следующей итерации) с помощью фрагмента, здесь один из таких..

person nawfal    schedule 16.12.2012