Использование общих анонимных типов в одной сборке. Часть 2

В прошлый раз я писал о том, что использование в одной сборке в разных местах «одинакового» анонимного типа приводит к использованию экземпляров одного и того же анонимного типа. Под «одинаковыми» мы подразумевали два анонимных типа, которые содержат одинаковые имена и типы свойств, причем свойства эти идут в одинаковом порядке. Два анонимных типа: new {X = 1, Y = 2 } и new { Y = 2, X = 1 } не приводят к использованию одного и того же анонимного типа.

А почему? Можно подумать, что и в этом случае можно использовать один и тот же тип.

Дело все в том, что такой подход добавит больше проблем, чем их решит. Анонимный тип является удобным местом хранения небольшого набора неизменяемых пар имя/значение, но это не все. Вы также получаете реализацию методов Equals и GetHashCode и, что самое важное для данной дискуссии, реализацию метода ToString. (*)

Давайте, например, представим, что вы написали несколько LINQ-запросов с использованием анонимных типов для получения данных из базы данных. В качестве юнит-теста вы получаете результаты вашего запроса, сохраняете их в файле в виде строкового представления и сравниваете их с некоторым известным результатом. У вас может быть сотня таких тестов. И вот однажды, кто-то в совершенно другой части кода пишет LINQ-запрос, содержащий «такой же» анонимный тип, но со свойствами в другом порядке. В методе ToString нам нужно вывести все свойства в определенном порядке, и нет никакого способа задать, какой из двух вариантов использовать. Кажется весьма странным, что использование анонимных типов в одной части программы приведет к неудачному выполнению тестов в совершенно другой ее части.

Тогда вы скажите, что можно четко специфицировать реализацию метода ToString. Например, можно выводить свойства в алфавитном порядке. Но это не кажется таким уж хорошим решением. Прежде всего, в каком именно алфавитном порядке? Существуют десятки алфавитных порядков. Должны ли мы использовать алфавитный порядок разработчика или текущего пользователя? Или использовать «нейтральный порядок»? Даже если предположить, что мы найдем удовлетворительное решение, мы все равно разочаруем большинство пользователей. Разработчики ожидают, что результат метода ToString будет содержать свойства в том же порядке, в каком они объявлены в исходном коде.

Другим решением является не реализовывать метод ToString вовсе. А это значит, удалить полезную и весьма распространенную возможность (вывод текстовых представления для отладки или тестирования) для более эффективной реализации менее полезной и реже используемой возможности (повторного использования типов).


(*) Реализация анонимными типами методов Equals и GetHashCode необходимо для возможности их использования в LINQ-запросах в качестве ключей, по которым выполняются объединения. Ради эффективности, объединения в LINQ to Objects реализованы на основе хэш-таблиц, так что нам нужна корректная реализация методов Equals и GetHashCode.

Оригинал статьи