Почему я должен использовать List <T> поверх IEnumerable <T>?

24

В моем веб-приложении ASP.net MVC4 я использую IEnumerables, пытаясь следовать мантре для программирования интерфейса, а не реализации.

Return IEnumerable(Of Student)

против

Return New List(Of Student)

Люди говорят мне использовать List, а не IEnumerable, потому что списки заставляют выполнять запрос, а IEumerable - нет.

Это действительно лучшая практика? Есть ли альтернатива? Я чувствую себя странно, используя конкретные объекты, где может быть использован интерфейс. Оправдано ли мое странное чувство?

Роуэн Фриман
источник
2
Во-первых, почему хорошо заставить запрос выполняться? Во-вторых, вы должны отслеживать и профилировать обращения к базе данных, чтобы оценить, имеет ли это техническое соображение какое-либо значение.
user16764
2
Говорят, что все запросы должны быть выполнены так, чтобы модель была сделана и загружена готова для просмотра. Т.е. представление должно получать все, а не запрашивать базу данных.
Роуэн Фриман
3
Этот вопрос StackOverflow достаточно хорошо освещает этот вопрос .
Карл Билефельдт
Это хороший ответ, но я хочу знать, насколько он актуален для MVC. Почему нельзя предоставить представление IEnumerables, чтобы все запросы выполнялись точно в срок?
Роуэн Фриман
2
«Говорят, что все запросы должны быть выполнены так, чтобы модель была выполнена и загружена в готовом виде для представления. Т.е. представление должно получить все, а не запрашивать базу данных». Это чепуха. Если вы передаете IEnumerable, то ваше представление не знает или не заботится, запрашивает ли оно базу данных. И так и должно быть.
user16764

Ответы:

21

Бывают случаи, когда выполнение ToList()запросов в linq может быть важным для обеспечения выполнения ваших запросов в то время и в том порядке, в котором вы ожидаете их выполнить. Эти сценарии, однако, редки, и никто не должен слишком беспокоиться о них, пока они действительно не столкнутся с ними.

Короче говоря, используйте в IEnumerableлюбое время, IListкогда вам нужна только итерация, используйте, когда вам нужно напрямую индексировать и вам нужен массив с динамическим размером (если вам нужна индексация для массива с фиксированным размером, просто используйте стандартный массив).

Что же касается времени выполнения вещь, вы всегда можете использовать список в качестве IEnumerableпеременной, так что не стесняйтесь возвращать IEnumerableпосредством производства .ToList();, или передать в качестве параметра как IEnumerableпутем выполнения .ToList()на IEnumerableв силу исполнения прямо тогда и там. Просто будьте осторожны, чтобы каждый раз, когда вы принудительно .ToList()выполняли выполнение, не зависали от IEnumerableпеременной, которую вы только что сделали, и выполняли ее снова, иначе вы в итоге удвоите количество итераций в своем запросе LINQ.

Что касается MVC, то здесь особо отмечать нечего. Он будет следовать тем же правилам времени выполнения, что и остальная часть .NET, я думаю, что у вас может быть кто-то, кто был немного смущен, вызванным семантикой отложенного выполнения в прошлом, и обвинил его в том, что MVC говорит, что это как-то связано, но это не. Семантика отложенного выполнения сначала смущает всех (и даже на некоторое время потом; они могут быть довольно сложными). Опять же, просто не беспокойтесь об этом, пока вы действительно не позаботитесь о том, чтобы запрос LINQ не выполнялся дважды или не требовал, чтобы он выполнялся в определенном порядке относительно другого кода, после чего назначьте вашу переменную себе. ToList () форсировать исполнение, и все будет в порядке.

Джимми Хоффа
источник
Это плохо, чтобы дать представление IEnumerables? Должны ли вы дать ему списки, чтобы запросы были выполнены к тому времени, когда представление их получит?
Роуэн Фриман
@RowanFreeman Я только что добавил правку, чтобы ответить на это. Я думаю, что у вас есть кто-то, кто столкнулся с чем-то, что он не совсем понял (не могу винить его, отложенное выполнение серьезно усложняет и запутывает) и приписал это плохому джиу, вместо того, чтобы разобраться в полном поведении отложенного выполнения семантика.
Джимми Хоффа
Хороший ответ. Так нужно ли мне когда-либо использовать .ToList ()? Пока что мое приложение работает нормально, используя только IEnumerables и передавая их из модели в представление. Нет списка или .ToList (). Мой вопрос не касается функциональности - я знаю, что мое приложение работает. Мой вопрос один из лучших.
Роуэн Фриман,
4
Лучшая практика @RowanFreeman - использовать минимальный интерфейс, который по-прежнему соответствует вашим требованиям. IEnumerable делает это прямо сейчас для вас, поэтому не беспокойтесь об изменении. Тем не менее, придет день, когда у вас будет запрос, выполняемый 3 или 10 раз, и вы не поймете, почему, или ожидаете, что запрос будет выполнен перед вставкой, а затем обнаружит, что он выполняется, в эти моменты вам нужно распознать .ToList () принудительно выполнит выполнение, когда вы захотите, и повторение IEnumerable из запроса LINQ несколько раз выполнит весь запрос несколько раз; Исправьте свое исполнение, когда эти события произойдут
Джимми Хоффа,
1
Вы даже не должны использовать - Listобход вокруг Listозначает, что содержимое списка будет изменено. Если вы хотите вернуть коллекцию, используйте IReadOnlyCollection. Listпредназначен для использования внутри методов и для обмена между методами, которые изменяют список. Это оно!
ErikE
7

Есть две проблемы.

IENumerable<Data> query = MyQuery();

//Later
foreach (Data item in query) {
  //Process data
}

К тому времени, когда цикл «Данные процесса» будет достигнут, запрос может перестать быть действительным. Например, если запрос выполняется для DataContext, который уже был удален, ваш код выдаст исключение. Подобные вещи становятся очень запутанными, когда вы обрабатываете запрос в другом контексте, чем тот, где вы его создали.

Вторая проблема заключается в том, что ваше соединение не будет освобождено, пока не завершится цикл обработки данных. Это проблема, только если «Данные процесса» сложны. Это упоминается по адресу http://msdn.microsoft.com/en-us/library/bb386929.aspx :

В. Как долго мое соединение с базой данных остается открытым?

О. Соединение обычно остается открытым, пока вы не используете результаты запроса. Если вы ожидаете, что для обработки всех результатов потребуется время, и вы не против кэширования результатов, примените ToList к запросу. В распространенных сценариях, когда каждый объект обрабатывается только один раз, потоковая модель превосходит как DataReader, так и LINQ to SQL.

Таким образом, именно эти проблемы побуждают вас убедиться, что запрос действительно выполняется, например, с помощью вызова ToList(). Тем не менее, как полагает Джимми , ничто не мешает вам вернуть свой список в виде IEnumerable.

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

Брайан
источник
1

Другое преимущество перечисления IEnumerableранних исключений будет выброшено в соответствующем месте. Это помогает отладке.

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

Сэм
источник
Любой метод, возвращающий, IEnumerableкоторый может бросить, вероятно, делает ошибку. Отложенные IEnumerableметоды должны быть разделены на два: один не отложенный метод проверяет параметры и устанавливает их, выбрасывая при необходимости (скажем, из-за пустого аргумента). Затем он возвращает вызов к частной реализации , которая является отложенной. Я не думаю, что мои комментарии полностью расходятся с вашим ответом, но я думаю, что вы упустили важный аспект в своем ответе, а именно семантическое значение использования IEnumerableпротив List(мутация) против IReadOnlyCollection(без пользы для отсрочка) .
ErikE