Pull to refresh

Comments 7

«При этом, каждый из способов сравнения является коммуникативным:
x.Equals(y) возвращает тот же результат, что и y.Equals(x), и т.д.»

Коммутативным только, наверное.
Все очень интересно и замечательно, но хочется понять необходимость сравнения объектов по значению в реальных проектах. IMHO для того чтобы, что-то сравнивать по значению — это что-то должно иметь семантику значения. Как правило, для этого используют value-типы, в .net — это скалярные значения, строка и типы унаследованные от System.ValueType (struct). В этом случае проблем с наследованием не возникает. Reference-типы сравнивать по значению если и приходится, то только по причине плохого дизайна. Но даже если гипотетически предположить, что это необходимо, то логику сравнения «по значению» я бы реализовал в виде отдельного класса.
Что-то типа:
internal sealed PersonMembersComparer: IEqualityComparer<Person> {
    public bool Equals(Person p1, Person p2) {
...
    }
}

Верно, решение о необходимости встраивания в класс сравнения объектов по значению для каждой сущности нужно принимать отдельно.
И в идеале, для типов, отличных от примитивных (или условно примитивных — типа TimeSpan или DateTime), при хорошем дизайне, должно быть достаточно сравнения по ссылке.

А для особых случаев — использовать компаратор, тем более случаи для объектов одного и того же типа могут быть разные:
может потребоваться сравнение как по некому ID, так и по ФИО — для того же класса Person.
Значит, нужны разные компараторы, и, возможно, нет смысла реализовывать в самом классе «предустановленное» сравнение по значению.

Тем не менее, механизм сравнения объектов по значению существует и, как минимум, нужно понимать, как именно он работает в стандартных типах (и всегда ли правильно работает — см. пример с Uri), и как именно правильно его реализовывать в своих типах.
А с учетом, как видим, сложностей в реализации, принимать решение, встраивать такое сравнение в класс, или нет.

Эта тему лучше обсудить в отдельной публикации — сформулировать, для каких случаев реализация сравнения по значению имеет смысла, и, возможно, с учетом ограниченности круга этих случаев, предложить более простую реализацию value equality, которая будет корректно работать в заданных условиях.

P.S. И еще один момент.
Вероятно, вы говорили о слое клиентского кода, где создаются программные сущности для той или иной предметной области, и для которых лучше использовать компараторы.
Но если речь вести о разработке какого-либо фреймворка, API, то там будут создаваться достаточно универсальные сущности, наподобие примитивных типов или стандартных классов платформы.
И скорее всего, для этих сущностей потребуется в т.ч. и сравнение по значению.
В вашей статье используется class Person, уж очень распространенная бизнес-сущность. А вот Uri — хороший пример или IPAddress и т.п., эти типы как раз имеют семантику значения.
Я не отрицаю, что это кому-то может понадобиться, но даже для .net framework переопределение Equals является очень уж нетипичной задачей. В mscorlib, System, System.Core и System.Data метод Equals переопределен лишь у каждого 100-го класса ~1%, а всего их более 7000, т.е. где-то 70-80.

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

Диссонанс возник именно из-за класса Person, который мне показался слишком «бизнесовым». IMHO реализация сравнения ссылочных типов по значению в бизнес системах — вещь вредная и опасная, т.к. не всем будет понятна логика реализации сравнения таких объектов, кроме этого не так часто можно встретить immutable класс.
А вот Uri — хороший пример или IPAddress и т.п., эти типы как раз имеют семантику значения.

Повторюсь, и даже Uri.Equals в стандартной библиотеке реализован, скорее всего, некорректно (разбор случая Uri — двумя статьями ранее).
Поэтому тема важная.

Насчет Person и остального в целом вы правы.
кроме этого не так часто можно встретить immutable класс.

На мой взгляд, immutable-классы в бизнес-логике могут применяться чаще, что позволило бы обеспечить более предсказуемую работу логики, но это уже другой разговор.
Reference-типы сравнивать по значению если и приходится, то только по причине плохого дизайна.

Или тот случай, когда для некоего типа сравнение объектов по значению объективно необходимо, но само появление этого типа является плохим дизайном.

Например, когда по надуманным соображениям создается обертка вокруг DateTime (причем не Struct, а Reference-обертка), чтобы приводить размерность к меньшей размерности, используемой в БД.

(Наверное, не стоит пояснять почему — потом этот велосипед тащится в коде из версии в версию, даже когда в БД уже 10 лет как поддерживаются нормальные DateTime, что изначальная проблема должна была решаться не созданием велосипеда, и т.д.)
Sign up to leave a comment.

Articles