Подтвердить что ты не робот

Должен ли IEquatable <T>, IComparable <T> реализоваться на незапечатанных классах?

Есть ли у кого-нибудь мнения о том, требуется ли вообще IEquatable<T> или IComparable<T>, чтобы T был sealed (если он a class)?

Этот вопрос возник со мной, так как я пишу набор базовых классов, предназначенных для помощи в реализации неизменяемых классов. Часть функциональности, которую должен обеспечить базовый класс, - автоматическая реализация сравнений равенства (с использованием полей класса вместе с атрибутами, которые могут применяться к полям для контроля сравнений равенства). Это должно быть довольно хорошо, когда я закончен - я использую деревья выражений для динамического создания скомпилированной функции сравнения для каждого T, поэтому функция сравнения должна быть очень близка к производительности регулярной функции сравнения равенства. (Я использую неизменяемый словарь с ключом System.Type и дважды проверяю блокировку, чтобы хранить сгенерированные функции сравнения так, чтобы это было разумно выполнено)

Однако одна вещь, которая возникла, - это то, какие функции использовать для проверки равенства полей-членов. Мое первоначальное намерение состояло в том, чтобы проверить, реализует ли тип поля каждого члена (который я назовем X) IEquatable<X>. Однако, подумав, я не думаю, что это безопасно использовать, если X не является sealed. Причина в том, что если X не sealed, я не уверен, что если X надлежащим образом делегирует проверки равенства виртуальному методу на X, тем самым позволяя подтипу переопределить сравнение равенства.

В этом случае возникает более общий вопрос: если тип не запечатан, должен ли он действительно реализовать эти интерфейсы НА ВСЕ? Я бы не подумал, так как я бы сказал, что контракт с интерфейсом заключается в сравнении двух типов X, а не двух типов, которые могут быть или не быть X (хотя они должны быть, конечно, X или подтипом).

Что вы, ребята, думаете? Следует ли избегать IEquatable<T> и IComparable<T> для незапечатанных классов? (Также заставляет задуматься, есть ли для этого правило fxcop)

Моя текущая мысль заключается в том, чтобы моя сгенерированная функция сравнения использовала только IEquatable<T> в полях-членах, T - sealed, а вместо этого использовать виртуальный Object.Equals(Object obj), если T не распечатано, даже если T реализует IEquatable<T>, так как поле может потенциально хранить подтипы T, и я сомневаюсь, что большинство реализаций IEquatable<T> спроектированы надлежащим образом для наследования.

4b9b3361

Ответ 1

Я немного подумал об этом вопросе, и после небольшого рассмотрения я согласен, что реализация IEquatable<T> и IComparable<T> должна выполняться только по закрытым типам.

Я немного поехал туда-сюда, но потом я подумал о следующем тесте. При каких обстоятельствах следующее должно быть ложным? ИМХО, 2 объекта либо равны, либо нет.

public void EqualitySanityCheck<T>(T left, T right) where T : IEquatable<T> {
  var equals1 = left.Equals(right);
  var equals2 = ((IEqutable<T>)left).Equals(right);
  Assert.AreEqual(equals1,equals2);
}

Результат IEquatable<T> для данного объекта должен иметь то же поведение, что и Object.Equals, предполагая, что компаратор имеет эквивалентный тип. Реализация IEquatable<T> дважды в иерархии объектов допускает и подразумевает, что существуют два разных способа выражения равенства в вашей системе. Легко придумать любое количество сценариев, где IEquatable<T> и Object.Equals будут отличаться, поскольку существует несколько реализаций IEquatable<T>, но только один Object.Equals. Следовательно, вышеизложенное не сработает и создаст путаницу в вашем коде.

Некоторые люди могут утверждать, что реализация IEquatable<T> в более высокой точке иерархии объектов действительна, потому что вы хотите сравнить подмножество свойств объектов. В этом случае вы должны одобрить IEqualityComparer<T>, который специально предназначен для сравнения этих свойств.

Ответ 2

Я бы рекомендовал не применять IEquatable <T> на любом непечатаемом классе или на самом деле несовместимом с IComparable, но то же самое нельзя сказать о IComparable <T> . Две причины:

  • Уже существует средство сравнения объектов, которые могут или не могут быть одного и того же типа: Object.Equals. Поскольку IEquatable <T> не включает GetHashCode, его поведение по существу должно соответствовать значению Object.Equals. Единственная причина для реализации IEquatable <T> в дополнение к Object.Equals - производительность. IEquatable & л, T > предлагает небольшое улучшение производительности по сравнению с Object.Equals при применении к закрытым типам классов и большое улучшение при применении к типам структуры. Единственный способ реализации незапечатанного типа IEquatable <T> .Equals может гарантировать, что его поведение соответствует тому, что возможно переопределенный Object.Equals, однако, вызывает вызов Object.Equals. Если IEquatable <T> .Equals должен вызывать Object.Equals, любое возможное преимущество в производительности исчезает.
  • Иногда возможно, осмысленно и полезно, чтобы базовый класс имел определенное естественное упорядочение, включающее только свойства базового класса, которое будет согласовано во всех подклассах. При проверке двух объектов на равенство результат не должен зависеть от того, рассматривают ли объекты как базовый тип или производный тип. Однако при ранжировании объектов результат часто зависит от типа, используемого в качестве основы для сравнения. Объекты с производными классами должны реализовывать IComparable <TheyOwnType> но не следует переопределять метод сравнения базового типа. Весьма разумно, чтобы два объекта производного класса сравнивались как "без рейтинга" при сравнении с родительским типом, но для сравнения друг с другом по сравнению с производным типом.

Реализация неэквивалентного IComparable в наследуемых классах, возможно, более сомнительна, чем реализация IComparable <T> . Вероятно, самое лучшее, что можно сделать, это позволить базовому классу реализовать его, если не ожидается, что для какого-либо дочернего класса потребуется какой-то другой порядок, но для дочерних классов не переопределять и не переопределять реализации родительского класса.

Ответ 3

Большинство реализаций Equals, которые я видел, проверяют типы сравниваемых объектов, если они не совпадают, тогда метод возвращает false.

Это аккуратно избегает проблемы сравнения подтипа с родительским типом, тем самым отрицая необходимость герметизации класса.

Очевидным примером этого будет попытка сравнить двумерную точку (A) с 3D-точкой (B): для 2D значения x и y трехмерной точки могут быть равны, но для трехмерной точки z, скорее всего, будет отличаться.

Это означает, что A == B будет true, но B == A будет ложным. В этом случае большая часть людей, таких как операторы Equals, которые должны быть коммутативными, для проверки типов, является хорошей идеей.

Но что, если вы подкласс и не добавляете никаких новых свойств? Ну, это немного сложнее ответить и, возможно, зависит от вашей ситуации.