Чи повинні обгортки порівнювати рівними за допомогою оператора ==, коли вони обертають один і той же об'єкт?


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я ніколи не перестарався ==(але ніколи навпаки). Чи лінивий ідіоматик? Якщо я отримую іншу поведінку без явного виступу, що порушує найменше здивування.
radarbob

Відповідь на це залежить від того, що робить NameAttribute - змінює базовий елемент? Чи є додатковий фрагмент даних? Значення прикладу коду (і чи варто його вважати рівним) змінюється залежно від цього, тому я думаю, вам потрібно заповнити його.
Errorsatz

@Errorsatz Я вибачаюсь, але хотів зберегти приклад лаконічним, і я припустив, що було зрозуміло, що він змінить обернутий елемент (зокрема, змінивши атрибут XML під назвою "ім'я"), але специфіка ледь має значення - справа в тому що обгортка дозволяє читати / записувати доступ до загорнутого елемента, але не містить власного стану.
Джон Ву

4
Що ж, у цьому випадку вони мають значення - це означає, що завдання "Привіт" та "Світ" вводять в оману, оскільки остання замінить перше. Що означає, що я згоден з відповіддю Мартіна, що обидва можна вважати рівними. Якби NameAttribute насправді відрізнялися між собою, я б не вважав їх рівними.
Errorsatz

2
"Як я розумію ідіоматичний c #, оператор == призначений для довідкової рівності, тоді як метод рівний () - для рівності величин." Це все-таки? Більшість випадків, які я бачив == перевантажено, - це значення рівності. Найважливіший приклад - System.String.
Артуро Торрес Санчес

Відповіді:


17

Оскільки посилання на загорнуту XElementє непорушною, різниці між двома примірникамиXmlWrapper цього обгортання одного і того ж елемента, тому є сенс перевантажувати, ==щоб відобразити цей факт.

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


9

Якщо ви думаєте, це має найбільше сенс

Питання та відповідь - це питання очікування розробника , це не технічна вимога.

Якщо ви вважаєте, що обгортка не має ідентичності та має бути визначена суто її змістом, то відповідь на ваше запитання - так.

Але це повторювана проблема. Чи повинні два обгортки виявляти рівність, коли вони обертають різні предмети, але обидва об'єкти мають однаковий вміст?

Відповідь повторюється. ЯКЩО об’єкти вмісту не мають особистої ідентичності і натомість є чітко визначеними їх змістом, то об'єкти контенту є ефективно обгортками, які виявлятимуть рівність. Якщо потім загортати об’єкти вмісту в іншу обгортку, ця (додаткова) обгортка також повинна демонструвати рівність.

Це черепахи аж донизу .


Загальна порада

Щоразу, коли ви відхиляєтесь від поведінки за замовчуванням, вона повинна бути чітко задокументована. Як розробник, я очікую, що два типи посилань не виявлятимуть рівності, навіть якщо їх зміст буде рівним. Якщо ви зміните таку поведінку, я б запропонував вам чітко її задокументувати, щоб усі розробники знали про цю нетипову поведінку.


Як я розумію, ідіоматичний c #, ==оператор призначений для еталонної рівності, тоді як Equals()метод - для рівності величин.

Це його поведінка за замовчуванням, але це не нерухоме правило. Це питання конвенції, але конвенції можна змінювати там, де це виправдано .

stringтут є чудовим прикладом, як ==це також перевірка рівності значень (навіть коли немає строкового інтернування!). Чому? Простіше кажучи: адже те, що рядки поводяться як об'єкти цінності, більшість розробників відчуває себе більш інтуїтивно.

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

Якщо ви ніколи не вимагаєте перевірки рівності еталонних рівнів (або вони не використовуються для вашої ділової сфери), немає жодного сенсу тримати перевірку рівності еталонних рівнів. Тоді краще замінити його на перевірку рівності значення, щоб уникнути помилки розробника .
Однак усвідомлюйте, що якщо вам знадобиться перевірка рівності еталону пізніше за лінією, повторне їх виконання може зайняти помітні зусилля.


Мені цікаво, чому ви очікуєте, що типи посилань не визначатимуть рівність вмісту. Більшість типів не визначають рівність просто тому, що це не суттєво для їхньої області, а не тому, що вони хочуть еталонної рівності.
casablanca

3
@casablanca: Я думаю, ви інтерпретуєте інше визначення "очікувати" (тобто вимога проти припущення). Без документації я сподіваюсь (тобто припускаю), що ==перевіряється рівність еталону, оскільки це поведінка за замовчуванням. Однак, якщо ==насправді перевіряється ціннісна рівність, я очікую (тобто вимагаю), щоб це було чітко зафіксовано. I'm curious why you expect that reference types won't define content equality.Вони не визначають це за замовчуванням , але це не означає, що це неможливо зробити. Я ніколи не казав, що цього не можна (або не слід) робити, я просто не чекаю (тобто припускаю) це за замовчуванням.
Флатер

Я бачу, що ви маєте на увазі, дякую за уточнення.
casablanca

2

Ви в основному порівнюєте рядки, тому я був би здивований, якщо дві обгортки, що містять однаковий вміст XML, не вважатимуться рівними, перевіряючи їх за допомогою рівняння = ==.

Ідіоматичне правило може мати сенс для об'єктів опорного типу взагалі, але рядки є особливими в ідіоматичному сенсі, ви повинні вважати їх і вважати значеннями, хоча технічно вони є еталонними типами.

Ваш поштовий індекс Wrapper додає плутанини. В основному це говорить "не XML-елемент". Тож чи маю я ставитися до цього як довідкового типу? Семантично це не мало б сенсу. Я був би менш розгублений, якби клас назвали XmlContent. Це означатиме, що ми дбаємо про вміст, а не про деталі технічної реалізації.


Де ви поставили межу між "об’єктом, побудованим з рядка" та "в основному рядком"? Це здається досить неоднозначним визначенням із зовнішньої точки зору API. Чи дійсно користувачеві API належить відгадати внутрішніх, щоб вгадати поведінку?
Артур Гавлічек

@ Артур Семантика класу / об'єкта має дати зрозуміння. І іноді це не все так очевидно, звідси і це питання. Для реальних типів цінності це стане очевидним для кожного. Для справжніх струн також. Тип повинен в кінцевому підсумку вказати, чи має порівняння включати вміст чи ідентичність об'єкта.
Мартін Мейт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.