Весьма желательно распечатать содержимое коллекций 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#!