Pull to refresh

Comments 13

Type-specific сравнение объектов по значению позволяет достичь [...] Более высокой производительности.

Я надеюсь, у вас есть конкретные бенчмарки в пользу этого утверждения?


Кроме того, реализация Type-specific сравнения по значению необходима по причинам:

Стандартные Generic-коллекции (List(Ot T), LinkedList(Of T), Dictionary(Of TKey, TValue) и др.) требуют реализацию IEquatable(Of T) для всех объектов, хранимых в коллекциях.

Нет.



Стандартный компаратор-по-значению EqualityComparer(Of T) по умолчанию использует реализацию IEquatable(Of T) у операндов.

Вы, наверное, имеете в виду EqualityComparer<T>.Default. В таком случае, ваше утверждение верно только частично. Действительно, если T в EqualityComparer<T>.Default реализует IEquatable<T>, то компаратор будет использовать эту реализацию (через GenericEqualityComparer<T>). Однако, T не обязан реализовывать IEquatable<T> — в этом случае будет использован ObjectEqualityComparer<T>.


Таким образом, хотя реализация IEquatable<T> и имеет некоторые преимущества, для указанных вами сценариев она не обязательна.

List(Of T): T неограничен
LinkedList(Of T): T неограничен
Dictionary(Of TKey, TValue): ни TKey, ни TValue не ограничены

Верно, не требуют обязательного IEquatable(Of T), т.к. «It should be implemented for any object that might be stored in a generic collection.»,
а не «It must be implemented».

Стоит уточнить формулировку в статье.
It should be implemented for any object that might be stored in a generic collection.

… зачем мне это, если я использую List<T>, на котором никогда не вызываю ни одного метода, работающего со сравнениями объектов?

Вы, наверное, имеете в виду EqualityComparer(Of T).Default. В таком случае, ваше утверждение верно только частично. Действительно, если T в EqualityComparer(Of T).Default реализует IEquatable(Of T), то компаратор будет использовать эту реализацию (через GenericEqualityComparer(Of T)). Однако, T не обязан реализовывать IEquatable(Of T) — в этом случае будет использован ObjectEqualityComparer(Of T).

«по умолчанию» относится к «по умолчанию использует реализацию», т.е. когда есть Generic-реализация.
а не «компаратор по умолчанию».
Требуется уточнение формулировки в статье.
Я надеюсь, у вас есть конкретные бенчмарки в пользу этого утверждения?
Давайте определимся — ранее, сейчас и далее, мы говорим о контрактах/спецификациях, а не конкретных реализациях, которые могут быть оптимизированы, в т.ч. на уровне JIT и т.д.

Из двух методов Equals, где заведомо все строки совпадают, но в одном есть дополнительное приведение через as, один будет заведомо быстрее другого. Насколько — другой вопрос.

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

В контракте и/или спецификации есть какое-либо конкретное утверждение, подтверждающее ваш тезис?


Из двух методов Equals, где заведомо все строки совпадают, но в одном есть дополнительное приведение через as, один будет заведомо быстрее другого.

… если не учитывать накладные расходы на остальные операции. Именно поэтому важны бенчмарки, а не теоретические рассуждения о "будет заведомо быстрее".


хеш-контейнер для входящего объекта проверяет вначале наличие Generic-метода Equals, и только затем Object-метода. [...] можно предполагать, что стандартные контейнеры вначале будут проверять Generic-версию.

Нет. Стандартные контейнеры достают все тот же EqualityComparer<T>.Default, который и используют впоследствии. Так что там есть однократная потенциальная потеря в момент создания компарера, но в операциях работы с элементами разницы нет.

Статический protected метод-хелпер EqualsHelper(Person, Person) сравнивает два объекта по полям, сочетание значений которых образует уникальность значения конкретного объекта.

Совершенно не понятно, зачем он такой нужен, когда можно просто использовать Equals(Person).


Я бы понял, если бы он проверки на null делал, но он же ожидает не-null объекты с обеих стороны. И вызываете вы его ровно в одном месте.


Аналогично, я бы понял, если бы вы его вызывали отовсюду, откуда можно, вместо a.Equals(b), чтобы получить call вместо callvirt (но тут возникают сложности с полиморфизмом, но они у вас и так возникают пока).

Совершенно не понятно, зачем он такой нужен, когда можно просто использовать Equals(Person).
И вызываете вы его ровно в одном месте.

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

То есть вы опять дали нам неполный пример. Спасибо, но нет.

реализован статический метод Person.Equals(Person, Person).

Отдельный задорный вопрос. Как должен себя вести этот метод, когда у нас есть PersonEx: Person, имеющий свой собственный PersonEx.Equals(PersonEx, PersonEx)? Должна ли для пользователя быть разница между PersonEx.Equals(pex1, pex2) и Person.Equals(pex1, pex2)?

Ответ на этот вопрос уже есть в статье/коде.
Подробный разбор этого вопроса с классом-наследником и примером клиентского кода будет отдельно.

Неа, нету. Если вы считаете, что есть, то вам не составит труда его процитировать.

Почему-то возникает стойкое ощущение спама статьями качества ниже среднего ради высокого рейтинга здесь и сейчас.

Sign up to leave a comment.

Articles