Dlaczego powinienem używać List <T> zamiast IEnumerable <T>?

24

W mojej aplikacji sieci web ASP.net MVC4 używam IEnumerables, próbując podążać za mantrą, aby programować interfejs, a nie implementację.

Return IEnumerable(Of Student)

vs

Return New List(Of Student)

Ludzie każą mi używać List, a nie IEnumerable, ponieważ listy wymuszają wykonanie zapytania, a IEumerable nie.

Czy to naprawdę najlepsza praktyka? Czy jest jakaś alternatywa? Czuję się dziwnie, używając konkretnych obiektów, w których można zastosować interfejs. Czy moje dziwne uczucie jest uzasadnione?

Rowan Freeman
źródło
2
Po pierwsze, dlaczego warto wymuszać wykonanie zapytania? Po drugie, należy monitorować i profilować wywołania bazy danych, aby ocenić, czy ta uwaga techniczna ma jakąkolwiek wartość.
user16764
2
Mówi się, że wszystkie zapytania powinny zostać wykonane, aby model był gotowy i załadowany gotowy do widoku. To znaczy, widok powinien otrzymać wszystko i nie powinien sprawdzać bazy danych.
Rowan Freeman,
3
To pytanie StackOverflow obejmuje je całkiem dobrze.
Karl Bielefeldt,
To dobra odpowiedź, ale chcę wiedzieć, że ma ona znaczenie dla MVC. Dlaczego nie można podać widoku IEnumerables, aby wszystkie zapytania były uruchamiane dokładnie w czasie?
Rowan Freeman,
2
„Mówi się, że wszystkie zapytania powinny być wykonywane, aby model był gotowy i załadowany gotowy do widoku. Oznacza to, że widok powinien otrzymać wszystko i nie powinien wysyłać zapytań do bazy danych”. To kompletna bzdura. Jeśli zdasz IEnumerable, twój widok nie wie ani nie obchodzi, czy przeszukuje bazę danych. I tak powinno być.
user16764

Odpowiedzi:

21

Są chwile, kiedy wykonywanie ToList()zapytań linq może być ważne, aby zapewnić wykonanie zapytań w czasie i w kolejności, w jakiej się spodziewają. Te scenariusze są jednak rzadkie i nie należy się tym zbytnio przejmować, dopóki nie trafią na nie.

Krótko mówiąc, używaj IEnumerablezawsze, gdy potrzebujesz tylko iteracji, używaj, IListgdy potrzebujesz bezpośrednio indeksować i potrzebujesz tablicy o dynamicznym rozmiarze (jeśli potrzebujesz indeksowania na tablicy o stałym rozmiarze, po prostu użyj tablicy standardowej).

Jeśli chodzi o czas wykonania, zawsze możesz użyć listy jako IEnumerablezmiennej, więc możesz zwrócić wartość IEnumerable, wykonując a .ToList();, lub przekazać parametr jako IEnumerablewykonanie .ToList()w IEnumerablecelu wymuszenia wykonania natychmiast i tam. Uważaj tylko, aby za każdym razem, gdy wymusisz wykonanie z .ToList()tobą, nie trzymaj się IEnumerablezmiennej, którą właśnie to zrobiłeś, i wykonaj ją ponownie, w przeciwnym razie skończy się to niepotrzebnym podwajaniem iteracji w zapytaniu LINQ.

Jeśli chodzi o MVC, naprawdę nie ma tu nic specjalnego do odnotowania. Będzie przestrzegać tych samych reguł czasu wykonania co reszta .NET, myślę, że możesz mieć kogoś, kto był trochę zamieszany spowodowany opóźnioną semantyką wykonania w przeszłości i obwiniał to MVC, mówiąc, że jest to w jakiś sposób powiązane, ale to jest nie. Opóźniona semantyka wykonania na początku wprowadza wszystkich w błąd (a nawet później przez dłuższy czas; mogą być trudne). Znów jednak, nie przejmuj się nim, dopóki naprawdę nie zależy ci na tym, aby zapytanie LINQ nie zostało wykonane dwukrotnie lub nie wymagało wykonania go w określonej kolejności względem innego kodu, w którym to momencie przypisz zmienną do siebie. żeby wymusić egzekucję, a wszystko będzie dobrze.

Jimmy Hoffa
źródło
Czy źle jest wyświetlać IEnumerables? Czy należy podać Listy, aby zapytania zostały wykonane do czasu ich wyświetlenia?
Rowan Freeman,
@RowanFreeman Właśnie dodałem edycję, aby odpowiedzieć na to pytanie. Myślę, że masz kogoś, kto wpadł na coś, czego nie do końca rozumieli (nie można ich winić, opóźnione wykonanie jest poważnie złożone i mylące) i przypisał to złemu joojoo zamiast pracować nad zrozumieniem pełnego zachowania opóźnionego wykonania semantyka.
Jimmy Hoffa
Niezła odpowiedź. Czy kiedykolwiek będę musiał użyć .ToList ()? Jak dotąd moja aplikacja działa dobrze, używając tylko IEnumerables i przekazując je z modelu do widoku. Brak listy lub .ToList (). Moje pytanie nie dotyczy funkcjonalności - wiem, że moja aplikacja działa. Moje pytanie jest jednym z najlepszych praktyk.
Rowan Freeman
4
@ Najlepszą praktyką @RowanFreeman jest używanie minimalnego interfejsu, który nadal spełnia Twoje wymagania. IEnumerable robi to teraz dla ciebie, więc nie martw się o zmianę. To powiedziawszy, nadejdzie dzień, w którym zapytanie będzie wykonywane 3 lub 10 razy i nie zrozumiesz, dlaczego, lub oczekujesz wykonania zapytania przed wstawieniem tylko w celu znalezienia, że ​​zostanie ono wykonane później, są to czasy, które musisz rozpoznać. () wymusi wykonanie, kiedy chcesz, a powtórzenie IEnumerable z zapytania LINQ wiele razy spowoduje wykonanie całego zapytania wiele razy; Napraw swoją egzekucję, gdy zdarzają się takie zdarzenia
Jimmy Hoffa,
1
Nie powinieneś nawet używać - Listomijanie Listsugeruje, że zawartość listy zostanie zmodyfikowana. Jeśli chcesz zwrócić kolekcję, użyj IReadOnlyCollection. Listsłuży do stosowania w ramach metod i do wymiany między metodami modyfikującymi listę. To jest to!
ErikE
7

Istnieją dwa problemy.

IENumerable<Data> query = MyQuery();

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

Do momentu osiągnięcia pętli „Dane procesowe” zapytanie może przestać być prawidłowe. Na przykład, jeśli zapytanie jest uruchamiane w obiekcie DataContext, który został już usunięty, kod wygeneruje wyjątek. Tego rodzaju rzeczy stają się bardzo mylące, gdy przetwarzasz zapytanie w innym kontekście niż miejsce, w którym je utworzyłeś.

Drugi problem polega na tym, że połączenie nie zostanie zwolnione, dopóki pętla „Dane procesowe” się nie zakończy. Jest to problem tylko wtedy, gdy „Dane procesowe” są złożone. Zostało to wspomniane na stronie http://msdn.microsoft.com/en-us/library/bb386929.aspx :

P: Jak długo moje połączenie z bazą danych pozostaje otwarte?

A. Połączenie zazwyczaj pozostaje otwarte, dopóki nie wykorzystasz wyników zapytania. Jeśli oczekujesz czasu na przetworzenie wszystkich wyników i nie sprzeciwiasz się buforowaniu wyników, zastosuj ToList do zapytania. W typowych scenariuszach, w których każdy obiekt jest przetwarzany tylko raz, model przesyłania strumieniowego jest lepszy zarówno w DataReader, jak i LINQ od SQL.

Te problemy sprawiają, że zachęca się Cię do upewnienia się, że zapytanie zostało faktycznie wykonane, np. Przez telefon ToList(). Jednak, jak sugeruje Jimmy, nic nie stoi na przeszkodzie, aby zwrócić listę jako IEnumerable.

Zasadniczo zalecam unikanie iteracji nad IEnumerable więcej niż jeden raz. Zakładając, że konsumenci twojego kodu przestrzegają tej reguły, nie uważam za obawy, że ktoś może trafić dwukrotnie w bazę danych, wykonując zapytanie dwukrotnie.

Brian
źródło
1

Kolejną zaletą wyliczenia IEnumerablewczesnego jest to, że wyjątki zostaną zgłoszone w odpowiednim miejscu. Pomaga to w debugowaniu.

Na przykład, jeśli masz wyjątek zakleszczenia w jednym z widoków Razor, tak naprawdę nie byłoby tak jasne, jak gdyby wyjątek wystąpił podczas jednej z metod dostępu do danych.

Sam
źródło
Każda metoda zwracająca wartość, IEnumerablektóra może rzucić, prawdopodobnie popełni błąd. Odroczone IEnumerablemetody powinny być podzielone na dwie: jedna nieodroczona metoda sprawdza parametry i konfiguruje, w razie potrzeby wyrzucając (powiedzmy, z powodu argumentu zerowego). Następnie zwraca wywołanie do prywatnej implementacji, które jest odroczone. Nie sądzę, że moje komentarze są całkowicie sprzeczne z twoją odpowiedzią, ale myślę, że pominąłeś ważny aspekt w swojej odpowiedzi, który jest semantycznym znaczeniem użycia IEnumerablevs. a List(mutacja) vs. IReadOnlyCollection(brak korzyści dla odroczenie) .
ErikE