Весьма желательно распечатать содержимое коллекций C# (списки, словари, наборы и т. д.) в целях отладки. К сожалению, простой вызов ToString() для типов коллекций печатает нежелательную строку. Вот простая программа для печати списка.

А вот и выход.

System.Collections.Generic.List`1[System.String]

Так мы получаем тип коллекции, но не длину и элементы в ней. Давайте исправим это!

Мы начнем с написания метода расширения для класса List:

Теперь, если мы заменим наш новый метод ToStringExt() в исходной программе, мы получим это:

ToStringExt: [one, two, three]

Гораздо приятнее! Но мы еще не закончили. Что, если мы напечатаем список списков? Запустим следующую программу:

ToString:System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.String]]
ToStringExt: [System.Collections.Generic.List`1[System.String], System.Collections.Generic.List`1[System.String]]

На выходе выводится список верхнего уровня, но каждый подсписок печатается с использованием ToString. Это потому, что наш ToStringExt внутренне вызывает ToString членов списка, чтобы распечатать их. Чтобы исправить это, мы можем создать специализацию нашей функции для типа List следующим образом:

С этой модификацией мы получаем следующий результат, когда вызываем ToStringExt для вложенного списка:

ToStringExt: [[one, two], [three, four]]

Хороший! Это работает для вложенных списков, но что, если бы у нас был список списков списков? Что ж, мы могли бы пройти еще несколько шагов по кроличьей норе и сделать специализированные перегрузки нашего универсального метода для вложенных списков 3-го уровня, вложенных списков 4-го уровня и так далее! На практике достаточно двух-трех уровней.

Другие коллекции

Мы можем использовать ту же технику для других типов коллекций. Проверьте проект github для этого сообщения в блоге.



Там я включил дополнительные методы расширения для других типов коллекций. Например, я поддерживаю словарь, словарь списков и словарь словарей. Вы можете продолжать писать эти методы расширения, чтобы они были такими же сложными, как и структуры данных вашей программы. Есть словарь списка HashSet? Один метод расширения сделает!

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

Недостатки

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

Дополнительно обратите внимание, что вместо ToString() нам нужно вызвать ToStringExt(). Это может показаться не таким уж большим делом, но помните, что C# преобразует любой тип в тип string с помощью метода ToString(). Вот как работают такие выражения, как “name: " + myObjectVariable. Мы теряем эту привлекательность и должны каждый раз явно вызывать наш метод расширения.

Еще одним недостатком использования универсальных методов является то, что привязки вызовов методов происходят во время компиляции. Если мы попытаемся красиво напечатать List<string>, хранящееся в переменной object, это не сработает.

Альтернативные решения

Наследование

Мы могли бы решить проблему с наследованием вместо методов расширения. Мы могли бы создать подклассы для всех типов коллекций (MyList‹T›, MyCollection‹K, V›, MyHashSet‹T› и т. д.), переопределить в них ToString() и использовать эти классы в наших проектах. Это исправление будет работать безупречно для глубоко вложенных коллекций. Но это сработает, только если мы не будем использовать какие-либо внешние библиотеки. Потому что, как только мы получим тип Collection из метода, который мы не создали (т. е. библиотеки, которую мы используем), мы получим обратно к началу и нужно преобразовать их в нашу версию коллекций. К сожалению, коллекции очень распространены, и если мы будем использовать какие-либо внешние библиотеки, то, скорее всего, нам придется иметь с ними дело. Так что у этого альтернативного решения есть свои недостатки…

Отражение

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

Конечная нота

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

Не забудьте проверить сопутствующий проект github здесь, чтобы увидеть полный проект и методы расширения. Вы можете скачать и использовать их в своих программах!

Удачной печати коллекций C#!