Это часть серии статей:
- Перечисление в .NET
- Перечисление в .NET II - Count ()
- Перечисление в .NET III - Enumerable.Empty ‹T› ()
- Перечисление в .NET IV - Поиск элемента
- Перечисление в .NET V - ToList () или нет ToList ()
К списку()
Я очень часто нахожу использование ToList()
в конце каждого запроса LINQ. В большинстве случаев в этом нет необходимости, и это может сильно повлиять на производительность.
Разберем небольшой пример:
Этот код записывает в консоль четные числа от 0 до 10. Вы можете увидеть в SharpLab.io, что это действительно работает.
Давайте расширим ToList()
до эквивалентного кода:
ПРИМЕЧАНИЕ. В реализациях
ToList()
иToArray()
есть отличные оптимизации, но вы не узнаете, что их запускает, пока не заглянете в его код.
Проверьте в SharpLab.io, что результат такой же, но действительно ли это то, что вы хотели? ToList()
скрывает дополнительное List<T>
выделение, один foreach
цикл и копию каждого элемента в списке.
Собственно, ToList()
в этом случае не нужен ...
Проверьте в SharpLab.io, что код без ToList()
выводит точно так же.
Представление
Выполнение кода в BenchmarkDotNet для диапазонов из 0, 500 и 1000 элементов дает следующие результаты.
ПРИМЕЧАНИЕ. Для теста я вычисляю общую сумму элементов последовательности вместо вывода на консоль.
ПРИМЕЧАНИЕ.
ToArray()
похож наToList()
, но возвращает массив вместо списка, и я включил его в тесты.
Неудивительно, что все реализации имеют O (n), что означает, что время обработки увеличивается с увеличением количества элементов в последовательности.
Удивительно, но ToArray()
быстрее, чем без преобразования. Должно быть инициирована одна из оптимизаций.
Что касается памяти, то именно здесь отказ от преобразования имеет огромное значение, и дальнейшая оптимизация преобразования невозможна, поскольку данные должны находиться в памяти. Вот для чего они нужны ...
Преобразование требует выделения памяти в куче, при этом требуемый объем увеличивается непосредственно с количеством элементов в последовательности.
Выделение кучи вызовет сборку мусора. Если вы выделяете небольшие суммы и храните их в течение короткого периода времени, они будут обрабатываться коллекцией Gen 0, которая работает быстро, но не бесплатно. Если вы выделяете большой объем (›85 000 байт), они попадут непосредственно в LOH (кучу больших объектов), вызывая ее фрагментацию и замедляя ее.
Заключение
ToList()
и ToArray()
будут выделять динамическую память, чаще вызывая GC и рискуя получить OutOfMemoryException
при масштабировании проекта.
Используйте их только в случае крайней необходимости!