tgoop.com/csharp_gepard/118
Last Update:
Танцы вокруг Enumerable.ToArray #скорость #память
Если мы пишем код по гайдлайнам, то наши методы часто возвращают IEnumerable<T>
, IReadonlyCollection<T>
и прочие интерфейсы коллекций. Это необходимо для того, чтобы сигнатура метода не изменялась при изменении логики метода, что, в свою очередь, является фундаментом для создания устойчивого к изменениям кода. В принципе, очень полезная и весьма здравая мысль.
При получении IEnumerable
из какого-либо метода, мы часто делаем ToList
или ToArray
. Например, чтобы получить возможность пробежаться по перечислению более чем один раз (см. multiple enumerations в случае IEnumerable). Или, например, мы не хотим аллоцировать Enumerator
при пробегании по IReadonlyCollection
. Короче говоря, по каким-то перформансным соображениям, нам интерфейсы не подходят.
В этой ситуации некоторые программисты допускают первую логическую ошибку. Они подсматривают код метода, который возвращает интерфейс коллекции, видят, что за ним на самом деле скрывается массив, и делают предположение о том, что им тоже нужно использовать исходную коллекцию, скрытую за интерфейсом.
Далее действия могут быть различными и зависеть от знаний глубин .NET'a.
Например, некоторые коллеги верят, что если они вызовут метод ToArray
, то произойдёт магия: мол, dotnet знает настоящий тип коллекции, которая возвращается из метода, а значит просто его и вернёт. Увы, это не так. Если мы посмотрим код, то можно заметить, что при вызове Enumerable.ToArray
создаётся новый массив с копией данных исходного, который и возвращается потребителю.
Другие коллеги будут более упорными в желании добраться до исходной коллекции, и создадут метод AsArray
. Он, я уверен, есть во многих проектах. Этот метод прост, он проверяет тип, и, если это действительно массив, просто возвращает его. Если же это другой тип коллекции, то будет использоваться стандартный Enumerable.ToArray
.
Скорость сильно выше, никаких дополнительных аллокаций сделано не будет (см. бенчмарк), а значит будет сделан вывод, что это идеальное решение для ситуаций работы с приходящим IEnumerable
. Код, думаю, будет примерно таким:
public static T[] AsArray<T>(this IEnumerable<T> collection)
{
return collection as T[] ?? collection.ToArray();
}
Казалось бы win-win. Но нет. И это вторая логическая ошибка.
Если мы ожидаем массив, исходный метод делает массив и мы придумали костыль, чтоб всё-таки получить массив... не лучше ли просто возвращать массив? Зачем эти танцы с бубном?
Если это наш код и нам ну очень нужен конкретный тип коллекции - давайте просто его использовать. Да, если до этого был IEnumerable, а теперь стал массив - это ломающее изменение и оно не подойдёт для библиотеки с тыщами потребителей... но уж внутри нашего приложения медиаторный хэндлер мы поправить в состоянии.
Ещё одним неприятным моментом будет изменение исходной коллекции. Например, мы получили перечисление, скастили его к массиву и изменили... А оно, например, служит неким кэшем. Как результат, возможно очень странное поведение в других местах кода, которые полагаются на неизменяемость исходной коллекции.
Короче говоря, мне кажется, что иногда не нужно изобретать велосипед. В данном случае, я в этом почти уверен.
P.S.: Александр, спасибо!
P.P.S.: Для серьёзных ребят Денис сделал собственный перечислитель для разных случаев. Кажется, что это очень хорошее решение, которое ликвидирует минусы подобного подхода.
BY C# Heppard

Share with your friend now:
tgoop.com/csharp_gepard/118