Компилятор C # требует, чтобы всякий раз, когда пользовательский тип определял оператор ==
, он также должен определять !=
(см. Здесь ).
Зачем?
Мне любопытно узнать, почему дизайнеры сочли это необходимым и почему компилятор не может по умолчанию использовать разумную реализацию для одного из операторов, когда присутствует только другой. Например, Lua позволяет вам определять только оператор равенства, а другой вы получаете бесплатно. C # может сделать то же самое, попросив вас определить либо ==, либо оба == и! =, А затем автоматически скомпилировать пропущенный оператор! = Как !(left == right)
.
Я понимаю, что есть странные угловые случаи, когда некоторые объекты не могут быть ни равными, ни неравными (например, IEEE-754 NaN), но они кажутся исключением, а не правилом. Так что это не объясняет, почему разработчики компилятора C # сделали исключение правилом.
Я видел случаи плохого качества изготовления, когда оператор равенства определен, тогда оператор неравенства является копией-вставкой, при котором каждое сравнение инвертируется, а каждый && переключается на || (Вы понимаете, в чем дело ... в основном! (a == b) расширено по правилам Де Моргана). Это плохая практика, которую компилятор может устранить при разработке, как в случае с Lua.
Примечание: то же самое верно для операторов <> <=> =. Я не могу представить случаи, когда вам нужно будет определить их неестественным образом. Lua позволяет вам определять только <и <= и определяет> = и> естественно через отрицание прежних. Почему C # не делает то же самое (по крайней мере «по умолчанию»)?
РЕДАКТИРОВАТЬ
Очевидно, есть веские причины, позволяющие программисту осуществлять проверки на равенство и неравенство, как им нравится. Некоторые ответы указывают на случаи, когда это может быть приятно.
Ядро моего вопроса, однако, почему это принудительно требуется в C #, когда обычно это не логически необходимо?
Кроме того , в разительном контрасте разработать варианты для .NET интерфейсов , как Object.Equals
, IEquatable.Equals
IEqualityComparer.Equals
где отсутствие NotEquals
встречного показывает , что структура рассматривает !Equals()
объекты как неравные и баста. Кроме того, такие классы, как Dictionary
и методы, .Contains()
зависят исключительно от вышеупомянутых интерфейсов и не используют операторы напрямую, даже если они определены. На самом деле, когда ReSharper генерирует элементы равенства, он определяет ==
и !=
в терминах, Equals()
и даже тогда, только если пользователь вообще решает генерировать операторы. Операторы равенства не нужны платформе для понимания равенства объектов.
По сути, .NET Framework не заботится об этих операторах, он заботится только о нескольких Equals
методах. Решение требовать, чтобы операторы == и! = Были определены пользователем в тандеме, связано исключительно с языковым дизайном, а не с семантикой объектов в отношении .NET.
источник
Ответы:
Я не могу говорить за дизайнеров языка, но из того, что я могу рассуждать, кажется, что это было намеренное, правильное дизайнерское решение.
Глядя на этот основной код F #, вы можете скомпилировать его в рабочую библиотеку. Это допустимый код для F #, и он перегружает только оператор равенства, а не неравенство:
Это именно так и выглядит. Он создает только компаратор равенства
==
и проверяет, равны ли внутренние значения класса.Хотя вы не можете создать такой класс в C #, вы можете использовать класс , скомпилированный для .NET. Очевидно, он будет использовать наш перегруженный оператор для
==
Итак, для чего используется среда выполнения!=
?Стандарт C # EMCA содержит целый набор правил (раздел 14.9), объясняющих, как определить, какой оператор использовать при оценке равенства. Говоря слишком упрощенно и, следовательно, не вполне точно, если сравниваемые типы имеют одинаковый тип и присутствует перегруженный оператор равенства, он будет использовать эту перегрузку, а не стандартный оператор равенства ссылок, унаследованный от Object. Поэтому неудивительно, что если присутствует только один из операторов, он будет использовать оператор равенства ссылок по умолчанию, который есть у всех объектов, для него перегрузки не существует. 1
Зная, что это так, реальный вопрос заключается в следующем: почему это было разработано таким образом и почему компилятор не может понять это самостоятельно? Многие люди говорят, что это не было дизайнерским решением, но мне нравится думать, что оно было продумано таким образом, особенно учитывая тот факт, что все объекты имеют оператор равенства по умолчанию.
Итак, почему компилятор не создает
!=
оператор автоматически ? Я не могу знать наверняка, если кто-то из Microsoft не подтвердит это, но это то, что я могу определить, исходя из фактов.Для предотвращения неожиданного поведения
Возможно, я хочу сделать сравнение значений,
==
чтобы проверить равенство. Однако когда дело дошло до!=
меня, мне было все равно, равны ли значения, если ссылка не равна, потому что для моей программы, чтобы считать их равными, меня интересует только совпадение ссылок. В конце концов, это фактически обозначено как поведение C # по умолчанию (если оба оператора не были перегружены, как было бы в случае некоторых библиотек .net, написанных на другом языке). Если компилятор автоматически добавлял код, я больше не мог полагаться на то, что компилятор будет выводить код, который должен быть совместимым. Компилятор не должен писать скрытый код, который меняет ваше поведение, особенно когда код, который вы написали, соответствует стандартам C # и CLI.С точки зрения того, что он заставляет вас перегружать его, вместо того, чтобы переходить к поведению по умолчанию, я могу только твердо сказать, что он соответствует стандарту (EMCA-334 17.9.2) 2 . Стандарт не уточняет почему. Я считаю, что это связано с тем, что C # заимствует много поведения из C ++. Смотрите ниже, чтобы узнать больше об этом.
Когда вы переопределяете
!=
и==
, вам не нужно возвращать bool.Это еще одна вероятная причина. В C # эта функция:
так же, как этот:
Если вы возвращаете что-то отличное от bool, компилятор не может автоматически определить противоположный тип. Кроме того, в том случае , если ваш оператор делает обратный BOOL, он просто не имеет смысла для них создать генерировать код , который будет существовать только в том , что один конкретный случай или, как я уже говорил выше, код , который скрывает поведение по умолчанию CLR.
C # много заимствует из C ++ 3
Когда был представлен C #, в журнале MSDN была статья, в которой говорилось о C #:
Да, цель разработки для C # заключалась в том, чтобы дать почти такое же количество энергии, что и в C ++, и лишь немного пожертвовал такими удобствами, как жесткая безопасность типов и сборка мусора. C # был сильно смоделирован после C ++.
Возможно, вы не удивитесь, узнав, что в C ++ операторы равенства не должны возвращать bool , как показано в этом примере программы.
Теперь C ++ напрямую не требует перегрузки дополнительного оператора. Если вы скомпилировали код в примере программы, вы увидите, что он работает без ошибок. Однако, если вы попытались добавить строку:
ты получишь
Таким образом, хотя сам C ++ не требует от вас перегрузки в парах, он не позволит вам использовать оператор равенства, который вы не перегружали в пользовательском классе. Это действительно в .NET, потому что все объекты имеют объект по умолчанию; C ++ нет.
1. В качестве примечания, стандарт C # по-прежнему требует перегрузки пары операторов, если вы хотите перегрузить один из них. Это часть стандарта, а не просто компилятор . Однако те же правила, что и при определении того, какой оператор вызывать, применяются при доступе к библиотеке .net, написанной на другом языке, который не имеет тех же требований.
2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )
3. И Java, но на самом деле это не главное
источник
==
возвращать что-либо, кроме какbool
? Если вы возвращаетеint
, вы больше не можете использовать его как условный оператор, например.if(a == b)
больше не будет компилироваться. В чем смысл?Вероятно, если кому-то нужно реализовать трехзначную логику (т.е.
null
). В таких случаях, например, в стандарте ANSI SQL, операторы нельзя просто отменить, в зависимости от ввода.Вы можете иметь случай, когда:
И
a == true
возвращается,false
аa == false
также возвращаетсяfalse
.источник
a
это не логическое значение, вы должны сравнивать его с перечислением из трех состояний, а не с реальнымtrue
илиfalse
.a == true
это так,false
то я всегда ожидалa != true
бы бытьtrue
, несмотря на то,a == false
чтоfalse
==
, а не почему он должен быть вынужден переопределить оба.!=
будет правильной. В крайне необычном случае , когда мы хотимx == y
иx != y
как к ложному (я не могу вспомнить ни одного примера , который имеет смысл) , они еще могут переопределить реализацию по умолчанию и предоставить свои собственные. Всегда делайте общий случай по умолчанию!Помимо того, что C # во многих областях относится к C ++, лучшее объяснение, которое я могу придумать, заключается в том, что в некоторых случаях вам может понадобиться немного иной подход к доказательству «не равенства», чем к «равенству».
Очевидно, что при сравнении строк, например, вы можете просто проверить на равенство и
return
выйти из цикла, когда вы видите несопоставимые символы. Тем не менее, это может быть не так чисто с более сложными проблемами. Цветения фильтр приходит на ум; это очень легко и быстро сказать , если элемент не в наборе, но трудно сказать , если элемент находится в наборе. Хотяreturn
может применяться та же техника, код может быть не таким красивым.источник
!
запирает вас в едином определении равенства, где информированный кодер может оптимизировать и то,==
и другое!=
.Если вы посмотрите на реализации перегрузок == и! = В источнике .net, они часто не реализуют! = As! (Left == right). Они реализуют это полностью (как ==) с отрицательной логикой. Например, DateTime реализует == как
и! = как
Если вы (или компилятор, если он сделал это неявно) должны были реализовать! = Как
тогда вы делаете предположение о внутренней реализации == и! = в вещах, на которые ссылается ваш класс. Избежание этого предположения может быть философией их решения.
источник
Чтобы ответить на ваши изменения, относительно того, почему вы вынуждены переопределить оба, если вы переопределите один, все это в наследстве.
Если вы переопределите ==, скорее всего, обеспечите какое-то семантическое или структурное равенство (например, DateTimes равны, если их свойства InternalTicks равны, даже если они могут быть разными экземплярами), тогда вы меняете поведение оператора по умолчанию с Объект, который является родителем всех объектов .NET. Оператор == в C # - это метод, базовая реализация которого Object.operator (==) выполняет ссылочное сравнение. Object.operator (! =) - это другой, другой метод, который также выполняет ссылочное сравнение.
Практически в любом другом случае переопределения методов было бы нелогично предполагать, что переопределение одного метода также приведет к изменению поведения антонимического метода. Если вы создали класс с методами Increment () и Decrement () и переопределили Increment () в дочернем классе, ожидаете ли вы, что Decrement () также будет переопределен с противоположностью вашего переопределенного поведения? Компилятор не может быть достаточно умным, чтобы генерировать обратную функцию для любой реализации оператора во всех возможных случаях.
Однако операторы, хотя и реализованные очень похоже на методы, концептуально работают парами; == и! =, <и>, и <= и> =. В этом случае было бы нелогично с точки зрения потребителя думать, что! = Работает иначе, чем ==. Таким образом, компилятор нельзя заставить предполагать, что a! = B ==! (A == b) во всех случаях, но обычно ожидается, что == и! = Должны работать аналогичным образом, поэтому компилятор заставляет Вы должны реализовать в парах, однако на самом деле вы делаете это. Если для вашего класса a! = B ==! (A == b), то просто реализуйте оператор! =, Используя! (==), но если это правило не выполняется во всех случаях для вашего объекта (например, (если сравнение с определенным значением, равным или неравным, недопустимо), то вы должны быть умнее, чем в среде IDE.
НАСТОЯЩИЙ вопрос, который следует задать, заключается в том, почему <и> и <= и> = являются парами для сравнительных операторов, которые должны быть реализованы одновременно, когда они выражены в числовом выражении! (A <b) == a> = b и! (A> б) == а <= б. Вы должны быть обязаны реализовать все четыре, если вы переопределяете одно, и вам, вероятно, также необходимо переопределить == (и! =), Потому что (a <= b) == (a == b), если a семантически равно б.
источник
Если вы перегрузите == для своего пользовательского типа, а не! =, Тогда он будет обработан оператором! = Для объекта! = Объект, поскольку все происходит от объекта, и это будет сильно отличаться от CustomType! = CustomType.
Также создатели языка, вероятно, хотели, чтобы это было максимально возможным для программистов, а также чтобы они не делали предположений относительно того, что вы намереваетесь делать.
источник
Это то, что приходит мне в голову первым:
false
и для==
и!=
(то есть, если они не могут быть сопоставлены по какой-то причине)источник
Dictionary
иList
если вы используете свой собственный тип с ними.Dictionary
используетGetHashCode
иEquals
, а не операторы.List
используетEquals
.Ну, это, наверное, просто выбор дизайна, но, как вы говорите,
x!= y
не обязательно должен быть таким же!(x == y)
. Не добавляя реализацию по умолчанию, вы уверены, что не сможете забыть реализовать конкретную реализацию. И если это действительно так тривиально, как вы говорите, вы можете просто реализовать одно, используя другое. Я не понимаю, как это «плохая практика».Могут быть и другие различия между C # и Lua тоже ...
источник
Ключевыми словами в вашем вопросе являются « почему » и « должны ».
В следствии:
Отвечать так, потому что они разработали так, правда ... но не отвечать на вопрос «почему» на ваш вопрос.
Ответ на то, что иногда может быть полезно переопределить оба этих элемента независимо, верно ... но не отвечает на обязательную часть вашего вопроса.
Я думаю, что простой ответ заключается в том, что нет никаких убедительных причин, почему C # требует от вас переопределения обоих.
Язык должен позволять переопределить только
==
, и предоставить вам реализацию по умолчанию!=
это!
что. Если вы хотите переопределить!=
, имейте это в виду.Это не было хорошим решением. Люди проектируют языки, люди не совершенны, C # не совершенен. Пожав плечами и QED
источник
Просто чтобы добавить к отличным ответам здесь:
Подумайте, что произойдет в отладчике , когда вы попытаетесь войти в
!=
оператор и==
вместо этого оказаться в операторе! Разговор о путанице!Имеет смысл, что CLR предоставит вам свободу исключать одного или другого оператора - так как он должен работать со многими языками. Но есть много примеров C # не подвергая функции CLR (
ref
возвращается и местные жители, к примеру), и множество примеров реализации возможностей , не в самой CLR (например:using
,lock
,foreach
и т.д.).источник
Языки программирования представляют собой синтаксические перестановки исключительно сложных логических выражений. Имея это в виду, можете ли вы определить случай равенства без определения случая неравенства? Ответ - нет. Чтобы объект a был равен объекту b, обратное значение объекта a, отличного от b, также должно быть истинным. Еще один способ показать это
if a == b then !(a != b)
это обеспечивает определенную способность языка определять равенство объектов. Например, сравнение NULL! = NULL может бросить рывок в определение системы равенства, которая не реализует оператор неравенства.
Теперь, что касается идеи! = Просто заменяемого определения, как в
if !(a==b) then a!=b
Я не могу спорить с этим. Тем не менее, наиболее вероятно, что группа спецификаций языка C # приняла решение, что программист будет вынужден явно определять равенство и неравенство объекта вместе
источник
Null
будет только проблемой, потому чтоnull
это допустимый вход==
, чего не должно быть, хотя, очевидно, это очень удобно. Лично я просто думаю, что это допускает огромное количество расплывчатого, неаккуратного программирования.Короче говоря, принудительная согласованность.
'==' и '! =' всегда являются истинными противоположностями, независимо от того, как вы их определяете, как таковые определяются их словесным определением «равно» и «не равно». Определяя только одно из них, вы открываете себя для несогласованности операторов равенства, когда оба «==» и «! =» Могут быть оба истинными или оба ложными для двух заданных значений. Вы должны определить и то и другое, поскольку, когда вы решите определить одно, вы должны также определить другое соответствующим образом, чтобы было совершенно ясно, каково ваше определение «равенства». Другое решение для компилятора - позволить вам только переопределить '==' OR '! =' И оставить другое как неотъемлемо отрицательное для другого. Очевидно, что это не относится к компилятору C #, и я уверен, что
Вопрос, который вы должны задать: «зачем мне переопределять операторы?» Это сильное решение, которое требует серьезных аргументов. Для объектов '==' и '! =' Сравните по ссылке. Если вы хотите переопределить их, чтобы НЕ сравнивать по ссылке, вы создаете общую несогласованность операторов, которая не очевидна для любого другого разработчика, который будет просматривать этот код. Если вы пытаетесь задать вопрос «является ли состояние этих двух экземпляров эквивалентным?», То вам следует реализовать IEquatible, определить Equals () и использовать этот вызов метода.
Наконец, IEquatable () не определяет NotEquals () по той же причине: потенциальная возможность раскрыть несоответствия операторов равенства. NotEquals () должен ВСЕГДА возвращать! Equals (). Открыв определение NotEquals () для класса, реализующего Equals (), вы вновь решаете проблему согласованности в определении равенства.
Изменить: это просто мои рассуждения.
источник
operator ==
и компилятор сделал бы выводoperator !=
. В конце концов, это то , что он делает дляoperator +
иoperator +=
.a != b
как!(a == b)
... (это не то , что делает C #).Возможно, что-то, о чем они не подумали, не успело сделать.
Я всегда использую твой метод, когда перегружаю ==. Тогда я просто использую это в другом.
Вы правы, с небольшим количеством работы, компилятор может дать нам это бесплатно.
источник