Czy należy zawsze zwracać IEnumerable <T> zamiast IList <T>?


98

Kiedy piszę mój kod DAL lub inny kod, który zwraca zestaw elementów, czy powinienem zawsze składać instrukcję zwrotu:

public IEnumerable<FooBar> GetRecentItems()

lub

public IList<FooBar> GetRecentItems()

Obecnie w moim kodzie próbuję używać IEnumerable tak często, jak to możliwe, ale nie jestem pewien, czy jest to najlepsza praktyka? Wydawało się słuszne, ponieważ zwracałem najbardziej ogólny typ danych, jednocześnie opisując, co robi, ale być może nie jest to właściwe.


1
Czy pytasz o List <T> czy IList <T>? Tytuł i pytanie mówią co innego ...
Fredrik Mörk

Kiedykolwiek jest to możliwe, interfejs użytkownika IEnumerable lub Ilist instaead typu constrete.
Usman Masood

2
Kolekcje zwracam jako List <T>. Nie widzę potrzeby zwracania IEnumberable <T>, ponieważ można to wyodrębnić z List <T>
Chuck Conway,


Odpowiedzi:


45

To naprawdę zależy od tego, dlaczego używasz tego konkretnego interfejsu.

Na przykład IList<T>ma kilka metod, których nie ma w IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

i właściwości:

  • T this[int index] { get; set; }

Jeśli potrzebujesz tych metod w jakikolwiek sposób, na pewno wróć IList<T>.

Ponadto, jeśli metoda, która zużywa twój IEnumerable<T>wynik, oczekuje an IList<T>, zapisze CLR przed rozważeniem wszelkich wymaganych konwersji, optymalizując w ten sposób skompilowany kod.


2
@Jon FDG zaleca używanie Collection <T> lub ReadOnlyCollection <T> jako wartości zwracanej dla typów kolekcji. Zobacz moją odpowiedź.
Sam Saffron

Nie jesteś pewien, co masz na myśli w swoim ostatnim zdaniu, kiedy mówisz „to uratuje CLR”. Co go uratuje, używając IEnumerable w porównaniu z IList? Czy możesz to wyjaśnić?
PositiveGuy

1
@CoffeeAddict Trzy lata po tej odpowiedzi myślę, że masz rację - ta ostatnia część jest niejasna. Jeśli metoda, która oczekuje IList <T> jako parametru, pobiera IEnumerable <T>, IEnumerable musi zostać zawinięta ręcznie w nowym implementatorze List <T> lub innym IList <T>, a ta praca nie zostanie wykonana przez CLR dla Ciebie. Wręcz przeciwnie - metoda, która oczekuje, że IEnumerable <T> pobiera IList <T>, może wymagać rozpakowania, ale z perspektywy czasu może nie być konieczne, ponieważ IList <T> implementuje IEnumerable <T>.
Jon Limjap,

69

Wytyczne dotyczące projektowania frameworka zalecają używanie Collection Collection, gdy trzeba zwrócić kolekcję, którą można modyfikować przez obiekt wywołujący lub ReadOnlyCollection dla kolekcji tylko do odczytu.

Powodem, dla którego jest to preferowane od prostego, IListjest to, że IListnie informuje dzwoniącego, czy jest tylko do odczytu, czy nie.

Jeśli IEnumerable<T>zamiast tego zwrócisz an , niektóre operacje mogą być nieco trudniejsze do wykonania przez dzwoniącego. Ponadto nie będziesz już dawać dzwoniącemu elastyczności w modyfikowaniu kolekcji, czego możesz chcieć lub nie.

Pamiętaj, że LINQ zawiera kilka sztuczek w rękawie i zoptymalizuje niektóre wywołania na podstawie typu, na którym są wykonywane. Na przykład, jeśli wykonasz a, Counta podstawową kolekcją jest List, NIE przejdzie ona przez wszystkie elementy.

Osobiście w przypadku ORM prawdopodobnie pozostałbym Collection<T>jako wartość zwrotu.


14
Te wytyczne dla Kolekcje zawierają bardziej szczegółową listę nakazów i zakazów.
Chaquotay,

27

Ogólnie rzecz biorąc, powinieneś wymagać najbardziej ogólnej i zwrócić najbardziej szczegółową rzecz, jaką możesz. Więc jeśli masz metodę, która przyjmuje parametr i naprawdę potrzebujesz tylko tego, co jest dostępne w IEnumerable, to powinien być twój typ parametru. Jeśli Twoja metoda może zwrócić IList lub IEnumerable, preferuj zwracanie IList. Gwarantuje to, że jest on użyteczny przez najszerszą grupę konsumentów.

Nie przejmuj się tym, czego potrzebujesz, i jasno przedstaw to, co zapewniasz.


1
Doszedłem do przeciwnego wniosku: należy akceptować określone typy, a zwracać typy ogólne. Możesz mieć jednak rację, że szerzej stosowane metody są lepsze niż metody bardziej ograniczone. Będę musiał o tym więcej pomyśleć.
Dave Cousineau

3
Uzasadnieniem akceptacji typów ogólnych jako danych wejściowych jest to, że umożliwia to pracę z możliwie najszerszym zakresem danych wejściowych w celu uzyskania jak największego ponownego wykorzystania komponentu. Z drugiej strony, ponieważ wiesz już dokładnie, jakiego rodzaju przedmiot masz pod ręką, nie ma sensu go maskować.
Mel,

1
Myślę, że zgadzam się z bardziej ogólnymi parametrami, ale jakie jest twoje uzasadnienie dla zwrócenia czegoś mniej ogólnego?
Dave Cousineau

7
Okej, spróbuję tego w inny sposób. Dlaczego miałbyś wyrzucić informacje? Jeśli zależy Ci tylko na wyniku będącym IEnumerable <T>, czy w jakikolwiek sposób boli Cię wiedza, że ​​jest to IList <T>? Nie, to nieprawda. W niektórych przypadkach może to być zbędna informacja, ale nie szkodzi. Teraz na korzyść. Jeśli zwrócisz List lub IList, mogę od razu stwierdzić, że kolekcja została już pobrana, coś, czego nie mogę wiedzieć o IEnumerable. Może to być przydatna informacja lub nie, ale jeszcze raz, dlaczego miałbyś wyrzucić informacje? Jeśli znasz jakieś dodatkowe informacje, przekaż je dalej.
Mel

Z perspektywy czasu jestem zaskoczony, że nikt nigdy do mnie nie dzwonił. IEnumerable BYŁBY już pobrane. IQueryable może jednak zostać zwrócony w stanie niewymienionym. Być może więc lepszym przykładem byłoby zwrócenie IQueryable vs IEnumerable. Zwrócenie bardziej szczegółowego IQueryable pozwoliłoby na dalszą kompozycję, podczas gdy zwrócenie IEnumerable wymusiłoby natychmiastowe wyliczenie i zmniejszyłoby kompozycję metody.
Mel

23

To zależy...

Zwracanie najmniejszego typu pochodnego (IEnumerable ) pozostawi największą swobodę w zmianie podstawowej implementacji w dół ścieżki.

Zwracanie bardziej pochodnego typu (IList ) zapewnia użytkownikom interfejsu API więcej operacji na wyniku.

Zawsze sugerowałbym zwrócenie najmniej pochodnego typu, który zawiera wszystkie operacje, których będą potrzebować Twoi użytkownicy ... więc w zasadzie najpierw musisz zastanowić się, jakie operacje na wyniku mają sens w kontekście definiowanego API.


ładna ogólna odpowiedź, ponieważ dotyczy również innych metod.
user420667

1
Wiem, że ta odpowiedź jest bardzo stara ... ale wydaje się być sprzeczna z dokumentacją: „Jeśli ani interfejs IDictionary <TKey, TValue>, ani interfejs IList <T> nie spełniają wymagań wymaganej kolekcji, wyprowadź nową klasę kolekcji z zamiast tego interfejs ICollection <T> zapewnia większą elastyczność ”Wydaje się to sugerować, że preferowany powinien być typ bardziej pochodny. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK

11

Jedną rzeczą do rozważenia jest to, że jeśli używasz instrukcji LINQ z odroczonym wykonaniem, aby wygenerować swoją IEnumerable<T>, wywołanie .ToList()przed powrotem z metody oznacza, że ​​elementy mogą być iterowane dwukrotnie - raz w celu utworzenia listy i raz, gdy obiekt wywołujący przechodzi przez pętlę , filtruje lub przekształca zwracaną wartość. Jeśli jest to praktyczne, wolę unikać konwertowania wyników LINQ-to-Objects na konkretną listę lub słownik, dopóki nie będę musiał. Jeśli mój wywołujący potrzebuje listy, jest to jedna prosta metoda wywołania - nie muszę podejmować tej decyzji za niego, a to sprawia, że ​​mój kod jest nieco bardziej wydajny w przypadkach, gdy wywołujący wykonuje tylko foreach.


@Joel Mueller, i tak zwykle dzwoniłbym do ToList (). Generalnie nie lubię wystawiać IQueryable na resztę moich projektów.
KingNestor

4
Bardziej odnosiłem się do LINQ-to-Objects, gdzie IQueryable na ogół nie wchodzi w grę. Gdy w grę wchodzi baza danych, ToList () staje się bardziej konieczne, ponieważ w przeciwnym razie ryzykujesz zamknięcie połączenia przed iteracją, co nie działa zbyt dobrze. Jednak jeśli nie stanowi to problemu, dość łatwo jest ujawnić IQueryable jako IEnumerable bez wymuszania dodatkowej iteracji, gdy chcesz ukryć IQueryable.
Joel Mueller

9

List<T>oferuje kod wywołujący o wiele więcej funkcji, takich jak modyfikowanie zwracanego obiektu i dostęp przez indeks. Tak więc pytanie sprowadza się do tego: czy w konkretnym przypadku Twojej aplikacji CHCESZ wspierać takie zastosowania (prawdopodobnie zwracając świeżo skonstruowaną kolekcję!), Dla wygody dzwoniącego - czy też chcesz szybkości w prostym przypadku, gdy wszystkie potrzebą osoby dzwoniącej jest zapętlenie kolekcji i można bezpiecznie zwrócić odwołanie do prawdziwej kolekcji bazowej bez obawy, że spowoduje to błędną zmianę itp.?

Tylko Ty możesz odpowiedzieć na to pytanie i tylko wtedy, gdy dobrze rozumiesz, co Twoi rozmówcy będą chcieli zrobić z wartością zwracaną i jak ważna jest tutaj wydajność (jak duże są kolekcje, które chcesz kopiować, jakie jest prawdopodobieństwo, że będzie to wąskie gardło, itp).


„bezpiecznie zwróć odwołanie do prawdziwej kolekcji bazowej bez obawy, że spowoduje to błędną zmianę” - Nawet jeśli zwrócisz IEnumerable <T>, czy nie mogliby po prostu przesłać go z powrotem do List <T> i zmienić?
Kobi

Nie każdy element IEnumarable <T> jest również List <T>. Jeśli zwrócony obiekt nie jest typu, który dziedziczy po List <T> lub implementuje IList <T>, spowoduje to InvalidCastException.
lowglider

2
List <T> ma problem polegający na tym, że blokuje Cię w konkretnej implementacji Collection <T> lub ReadOnlyCollection <T> są preferowane
Sam Saffron

4

Myślę, że możesz użyć jednego z nich, ale każdy ma zastosowanie. Zasadniczo Listjest, IEnumerableale masz funkcjonalność liczenia, dodawania elementu, usuwania elementu

IEnumerable nie jest wydajne do liczenia elementów

Jeśli kolekcja ma być przeznaczona tylko do odczytu lub modyfikacja kolekcji jest kontrolowana przez, Parentto zwrócenie „ IListjust for” Countnie jest dobrym pomysłem.

W Linq istnieje Count()metoda rozszerzenia, do IEnumerable<T>której wewnątrz środowiska CLR zostanie skrócony, .Countjeśli typ bazowy to IList, więc różnica wydajności jest pomijalna.

Generalnie uważam (opinia), że lepszą praktyką jest zwrócenie IEnumerable tam, gdzie to możliwe, jeśli potrzebujesz dodać te metody, dodaj te metody do klasy nadrzędnej, w przeciwnym razie konsument zarządza kolekcją w ramach Modelu, co narusza zasady, np. manufacturer.Models.Add(model)Narusza prawo demeter. Oczywiście są to tylko wskazówki, a nie twarde i szybkie zasady, ale dopóki nie zrozumiesz w pełni możliwości ich zastosowania, podążanie na ślepo jest lepsze niż w ogóle.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Uwaga: jeśli używasz nNHibernate, może być konieczne zmapowanie do prywatnego IList przy użyciu różnych akcesorów).


2

Nie jest to takie proste, gdy mówisz o zwracanych wartościach zamiast o parametrach wejściowych. Kiedy jest to parametr wejściowy, wiesz dokładnie, co musisz zrobić. Tak więc, jeśli chcesz mieć możliwość iteracji kolekcji, weź IEnumberable, podczas gdy jeśli chcesz dodać lub usunąć, bierzesz IList.

W przypadku wartości zwracanej jest trudniej. Czego oczekuje Twój rozmówca? Jeśli zwrócisz IEnumerable, nie będzie on wiedział a priori, że może zrobić z tego IListę. Ale jeśli zwrócisz ILista, będzie wiedział, że może to powtórzyć. Musisz więc wziąć pod uwagę, co twój rozmówca zrobi z danymi. Funkcjonalność, której Twój rozmówca potrzebuje / oczekuje, jest tym, co powinno rządzić przy podejmowaniu decyzji o tym, co zwrócić.


0

jak wszyscy powiedzieli, to zależy, jeśli nie chcesz dodawać / usuwać funkcjonalności przy wywoływaniu warstwy, zagłosuję na IEnumerable, ponieważ zapewnia on tylko iterację i podstawową funkcjonalność, która w perspektywie projektowej mi się podoba. Wracając do IList, moje głosy zawsze się powtarzają, ale chodzi głównie o to, co lubisz, a co nie. pod względem wydajności myślę, że są bardziej takie same.


0

Jeśli nie liczysz w kodzie zewnętrznym, zawsze lepiej jest zwrócić IEnumerable, ponieważ później możesz zmienić swoją implementację (bez wpływu na kod zewnętrzny), na przykład dla iteratora yield logiki i oszczędzania zasobów pamięci (przy okazji bardzo dobra funkcja języka ).

Jeśli jednak potrzebujesz liczby elementów, nie zapominaj, że między IEnumerable a IList istnieje inna warstwa - ICollection .


0

Mogę być tutaj trochę zdziwiony, widząc, że nikt inny tego nie zasugerował, ale dlaczego nie zwrócisz (I)Collection<T> ?

Z tego, co pamiętam, Collection<T>był preferowanym typem zwracanym, List<T>ponieważ odciąga implementację. Wszystkie realizują IEnumerable, ale wydaje mi się to trochę za niskie do tej pracy.


0

Myślę, że możesz użyć jednego z nich, ale każdy ma zastosowanie. Zasadniczo Listjest, IEnumerableale masz funkcjonalność liczenia, dodaj element, usuń element

IEnumerable nie jest wydajne do liczenia elementów lub pobierania określonego elementu w kolekcji.

List to zbiór, który idealnie nadaje się do wyszukiwania konkretnych elementów, łatwego dodawania elementów lub ich usuwania.

Generalnie staram się używać Listtam, gdzie to możliwe, ponieważ daje mi to większą elastyczność.

Użyj List<FooBar> getRecentItems() zamiast IList<FooBar> GetRecentItems()


0

Myślę, że ogólną zasadą jest użycie bardziej szczegółowej klasy do powrotu, aby uniknąć wykonywania niepotrzebnej pracy i dać dzwoniącemu więcej opcji.

To powiedziawszy, myślę, że ważniejsze jest rozważenie kodu przed sobą, który piszesz, niż kodu, który napisze następny facet (w granicach rozsądku). Dzieje się tak, ponieważ możesz przyjąć założenia dotyczące kodu, który już istnieje.

Pamiętaj, że przejście w górę do kolekcji z IEnumerable w interfejsie będzie działać, przejście w dół do IEnumerable z kolekcji spowoduje uszkodzenie istniejącego kodu.

Jeśli wszystkie te opinie wydają się sprzeczne, to dlatego, że decyzja jest subiektywna.


0

TL; DR; - Podsumowanie

  • Jeśli tworzysz własne oprogramowanie, używaj określonego typu (Like List) dla wartości zwracanych i najbardziej ogólnego typu dla parametrów wejściowych, nawet w przypadku kolekcji.
  • Jeśli metoda jest częścią publicznego interfejsu API biblioteki redystrybucyjnej, użyj interfejsów zamiast konkretnych typów kolekcji, aby wprowadzić zarówno wartości zwracane, jak i parametry wejściowe.
  • Jeśli metoda zwraca kolekcję tylko do odczytu, pokaż to, używając IReadOnlyListlub IReadOnlyCollectionjako typu wartości zwracanej.

Więcej

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.