Должны ли обертки сравниваться как равные с использованием оператора ==, когда они переносят один и тот же объект?

19

Я пишу оболочку для элементов XML, которая позволяет разработчику легко анализировать атрибуты из XML. Оболочка не имеет никакого состояния, кроме объекта, который оборачивается.

Я рассматриваю следующую реализацию (упрощенную для этого примера), которая включает перегрузку для ==оператора.

class XmlWrapper
{
    protected readonly XElement _element;

    public XmlWrapper(XElement element)
    {
        _element = element;
    }

    public string NameAttribute
    {
        get
        {
            //Get the value of the name attribute
        }
        set
        {
            //Set the value of the name attribute
        }
    }

    public override bool Equals(object other)
    {
        var o = other as XmlWrapper;
        if (o == null) return false;
        return _element.Equals(o._element);
    }

    public override int GetHashCode()
    {
        return _element.GetHashCode();
    }

    static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
    {
        if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) return true;
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false;

        return lhs._element == rhs._element;
    }

    static public bool operator != (XmlWrapper lhs, XmlWrapper rhs)
    {
        return !(lhs == rhs);
    }
}

Как я понимаю, идиоматический C #, ==оператор для равенства ссылок, а Equals()метод для равенства значений. Но в этом случае «значение» - это просто ссылка на обертываемый объект. Так что мне не ясно, что является обычным или идиоматическим для C #.

Например, в этом коде ...

var underlyingElement = new XElement("Foo");
var a = new XmlWrapper(underlyingElement);
var b = new XmlWrapper(underlyingElement);

a.NameAttribute = "Hello";
b.NameAttribute = "World";

if (a == b)
{
    Console.WriteLine("The wrappers a and b are the same.");
}

.... должна ли программа выводить "Обертки a и b одинаковы"? Или это будет странно, то есть нарушит принцип наименьшего удивления ?

Джон Ву
источник
За все время, что я пережил, Equalsя никогда не переживал ==(но никогда не наоборот). Ленивый идиоматик? Если я получаю другое поведение без явного приведения, который нарушает наименьшее удивление.
Радар Боб
Ответ на это зависит от того, что делает NameAttribute - изменяет базовый элемент? Является ли дополнительная часть данных? Значение примера кода (и следует ли его считать равным) меняется в зависимости от этого, поэтому я думаю, что вам нужно его заполнить.
Errorsatz
@Errorsatz Я прошу прощения, но я хотел, чтобы пример был кратким, и я предполагал, что было ясно, что он изменит обернутый элемент (в частности, изменив атрибут XML с именем «name»). Но специфика почти не имеет значения - дело в том, что оболочка предоставляет доступ на чтение / запись к обернутому элементу, но не содержит своего собственного состояния.
Джон Ву
4
Ну, в данном случае они имеют значение - это означает, что назначение «Hello» и «World» вводит в заблуждение, потому что последнее перезапишет первое. Это означает, что я согласен с ответом Мартина, что оба могут считаться равными. Если бы атрибут NameAttribute действительно отличался между ними, я бы не стал считать их равными.
Errorsatz
2
«Как я понимаю, идиоматический c #, оператор == предназначен для ссылочного равенства, а метод Equals () для равенства значений». Это правда? В большинстве случаев, когда я видел == перегруженный, это значение равнозначно. Самый важный пример - System.String.
Артуро Торрес Санчес

Ответы:

17

Поскольку ссылка на завернутый XElementявляется неизменной, внешне заметных различий между двумя экземплярами одного и XmlWrapperтого же элемента нет, поэтому имеет смысл перегрузить, ==чтобы отразить этот факт.

Код клиента почти всегда заботится о логическом равенстве (которое по умолчанию реализуется с использованием равенства ссылок для ссылочных типов). Тот факт, что в куче есть два экземпляра, является деталью реализации, о которой не должны заботиться клиенты (и те, которые будут использовать Object.ReferenceEqualsнапрямую).

Касабланка
источник
9

Если вы думаете, это имеет смысл

Вопрос и ответ - вопрос ожидания разработчика , это не техническое требование.

Если вы считаете, что обертка не имеет идентификатора и ее можно определить исключительно по ее содержимому, тогда ответ на ваш вопрос - да.

Но это постоянная проблема. Должны ли два обертки демонстрировать равенство, когда они обертывают разные объекты, но оба объекта имеют одинаковое содержимое?

Ответ повторяется. Если объекты контента не имеют персональной идентичности и вместо этого определяются исключительно своим контентом, то объекты контента фактически являются обертками, которые будут демонстрировать равенство. Если затем вы оберните объекты содержимого в другую оболочку, эта (дополнительная) оболочка должна также продемонстрировать равенство.

Это черепахи все время вниз .


Общий совет

Всякий раз, когда вы отклоняетесь от поведения по умолчанию, это должно быть явно задокументировано. Как разработчик, я ожидаю, что два ссылочных типа не будут демонстрировать равенство, даже если их содержание одинаково. Если вы измените это поведение, я бы предложил вам четко документировать его, чтобы все разработчики знали об этом нетипичном поведении.


Как я понимаю, идиоматический C #, ==оператор для равенства ссылок, а Equals()метод для равенства значений.

Это поведение по умолчанию, но это не является неизменным правилом. Это вопрос соглашения, но условные обозначения могут быть изменены там, где это оправдано .

stringотличный пример здесь, также как ==и проверка на равенство значений (даже если нет интернирования строк!). Почему? Проще говоря: потому что строки ведут себя как объекты значений, они кажутся более интуитивными для большинства разработчиков.

Если ваша кодовая база (или жизнь ваших разработчиков) может быть значительно упрощена, если ваши обертки демонстрируют равенство значений по всем направлениям, сделайте это (но задокументируйте это ).

Если вам никогда не требуются проверки на равенство ссылок (или они становятся бесполезными в вашей бизнес-сфере), тогда нет смысла хранить проверку на равенство ссылок. Лучше заменить его проверкой на равенство значений, чтобы предотвратить ошибку разработчика .
Тем не менее, имейте в виду, что если вам понадобятся проверки на равенство ссылок позже, повторная реализация может потребовать значительных усилий.

Flater
источник
Мне любопытно, почему вы ожидаете, что ссылочные типы не будут определять равенство содержимого. Большинство типов не определяют равенство просто потому, что это не важно для их области, а не потому, что они хотят ссылочного равенства.
Касабланка
3
@casablanca: Я думаю, что вы интерпретируете другое определение «ожидание» (то есть требование против предположения). Без документации я ожидаю (т.е. предполагаю), что ==проверяет равенство ссылок, так как это поведение по умолчанию. Однако, если на ==самом деле проверяется равенство значений, я ожидаю (то есть требую), что это задокументировано явно. I'm curious why you expect that reference types won't define content equality.Они не определяют его по умолчанию , но это не значит, что это невозможно сделать. Я никогда не говорил, что это не может (или не должно) быть сделано, я просто не ожидаю (то есть предполагаю) это по умолчанию.
Флэтер
Я понимаю, что вы имеете в виду, спасибо за разъяснения.
Касабланка
2

Вы в основном сравниваете строки, поэтому я был бы удивлен, если бы две обертки, содержащие один и тот же контент XML, не считались равными, будь то проверка с использованием Equals или ==.

Идиоматическое правило может иметь смысл для объектов ссылочного типа в целом, но строки являются особенными в идиоматическом смысле, вы должны рассматривать и рассматривать их как значения, хотя технически они являются ссылочными типами.

Ваш постфикс Wrapper добавляет путаницу. В основном это говорит "не элемент XML". Так должен ли я рассматривать его как ссылочный тип в конце концов? Семантически это не имеет смысла. Я был бы менее смущен, если бы класс назывался XmlContent. Это будет означать, что мы заботимся о контенте, а не о технических деталях реализации.

Мартин Маат
источник
Где бы вы поместили предел между «объектом, построенным из строки» и «в основном строкой»? Это выглядит довольно двусмысленным с точки зрения внешнего API. Должен ли пользователь API на самом деле угадывать внутренности, чтобы угадать поведение?
Артур Гавличек
@ Артур Семантика класса / объекта должна предоставить ключ. И иногда это не так уж очевидно, отсюда и этот вопрос. Для реальных типов значений это будет очевидно для всех. Для реальных струн также. Тип должен в конечном итоге сказать, должно ли сравнение включать содержание или идентичность объекта.
Мартин Маат