Zwracanie „IList” vs „ICollection” vs „Collection”

119

Nie wiem, który typ kolekcji powinienem zwrócić z moich publicznych metod i właściwości interfejsu API.

Zbiory, które mam na myśli to IList, ICollectioni Collection.

Czy zwrot jednego z tych typów jest zawsze preferowany względem innych, czy też zależy to od konkretnej sytuacji?

Rocky Singh
źródło

Odpowiedzi:

71

Ogólnie powinieneś zwrócić typ, który jest jak najbardziej ogólny, tj. Taki, który zna wystarczająco dużo zwróconych danych, których konsument musi użyć. W ten sposób masz większą swobodę zmiany implementacji API bez przerywania kodu, który go używa.

Rozważ również IEnumerable<T>interfejs jako typ zwracany. Jeśli wynik ma być tylko iterowany, konsument nie potrzebuje więcej.

Guffa
źródło
25
Ale należy również rozważyć ICollection <T>, który rozszerza IEnumerable <T>, aby zapewnić dodatkowe narzędzia, w tym właściwość Count. Może to być pomocne, ponieważ można określić długość sekwencji bez wielokrotnego przechodzenia przez sekwencję.
retrodrone
11
@retrodrone: W rzeczywistości implementacja Countmetody sprawdza rzeczywisty typ kolekcji, więc użyje właściwości Lengthlub Countdla niektórych znanych typów, takich jak tablice, a ICollectionnawet jeśli podasz ją jako plik IEnumerable.
Guffa,
8
@Guffa: Warto zauważyć, że jeśli klasa Thing<T>implementuje, IList<T>ale nie jest nieogólna ICollection, to wywołanie IEnumerable<Cat>.Count()a Thing<Cat>byłoby szybkie, ale wywołanie IEnumerable<Animal>.Count()byłoby wolne (ponieważ metoda rozszerzenia szukałaby implementacji ICollection<Cat>). Jeśli jednak klasa implementuje nieogólną ICollection, IEnumerable<Animal>.Countto znajdzie i użyje tego.
supercat
8
Nie zgadzam się. Najlepiej jest zwrócić najbogatszy typ, jaki masz, aby klient mógł go bezpośrednio użyć, jeśli tego chce. Jeśli masz List <>, po co zwracać IEnuerable <> tylko po to, aby zmusić obiekt wywołujący do niepotrzebnego wywołania ToList ()?
zumalifeguard
140

ICollection<T>jest interfejs, który ujawnia semantykę kolekcji, takich jak Add(), Remove()i Count.

Collection<T>jest konkretną implementacją ICollection<T>interfejsu.

IList<T>jest zasadniczo ICollection<T>z dostępem opartym na kolejności losowej.

W takim przypadku powinieneś zdecydować, czy Twoje wyniki wymagają semantyki listy, takiej jak indeksowanie na podstawie kolejności (następnie użyj IList<T>), czy też po prostu musisz zwrócić nieuporządkowany „pakiet” wyników (następnie użyj ICollection<T>).

cordialgerm
źródło
5
Collection<T>narzędzia IList<T>i nie tylko ICollection<T>.
CodesInChaos
56
Wszystkie kolekcje w .Net są uporządkowane, ponieważ IEnumerable<T>są uporządkowane. Co odróżnia IList<T>od ICollection<T>jest to, że oferuje członków do pracy z indeksami. Na przykład list[5]działa, ale collection[5]się nie kompiluje.
Svick
5
Nie zgadzam się też, że zamówione kolekcje powinny być realizowane IList<T>. Istnieje wiele uporządkowanych kolekcji, które tego nie robią. IList<T>chodzi o szybki dostęp indeksowany.
CodesInChaos
4
@yoyo Klasy kolekcji i interfejsy .net są po prostu źle zaprojektowane i źle nazwane. Nie zastanawiałbym się zbytnio, dlaczego tak ich nazwali.
CodesInChaos
6
@svick: Czy wszystkie kolekcje są uporządkowane, ponieważ IEnumerable<T>są uporządkowane? Weź HashSet<T>- wdraża, IEnumerable<T>ale wyraźnie nie jest zamówiony. Oznacza to, że nie masz wpływu na kolejność elementów i ta kolejność może się zmienić w dowolnym momencie, jeśli wewnętrzna tabela skrótów zostanie ponownie zorganizowana.
Olivier Jacot-Descombes
61

Główna różnica między IList<T>i ICollection<T>polega na tym, że IList<T>umożliwia dostęp do elementów za pośrednictwem indeksu. IList<T>opisuje typy tablicowe. ICollection<T>Dostęp do elementów w an można uzyskać tylko poprzez wyliczenie. Oba pozwalają na wstawianie i usuwanie elementów.

Jeśli potrzebujesz tylko wyliczyć kolekcję, IEnumerable<T>to należy preferować. Ma dwie zalety w stosunku do innych:

  1. Nie zezwala na zmiany w kolekcji (ale nie w elementach, jeśli są typu referencyjnego).

  2. Pozwala na największą możliwą różnorodność źródeł, w tym wyliczenia, które są generowane algorytmicznie i wcale nie są kolekcjami.

Collection<T>jest klasą bazową, która jest przydatna głównie dla implementatorów kolekcji. Jeśli ujawnisz go w interfejsach (API), wiele przydatnych kolekcji, które nie pochodzą z niego, zostanie wykluczonych.


Jedną z wad IList<T>jest to, że tablice go implementują, ale nie pozwalają na dodawanie ani usuwanie elementów (tj. Nie można zmienić długości tablicy). W przypadku wywołania IList<T>.Add(item)tablicy zostanie zgłoszony wyjątek . Sytuacja jest nieco rozwiązana, ponieważ IList<T>ma właściwość logiczną IsReadOnly, którą można sprawdzić przed podjęciem próby. Ale moim zdaniem to wciąż wada projektowa w bibliotece . Dlatego korzystam List<T>bezpośrednio, gdy wymagana jest możliwość dodania lub usunięcia pozycji.

Olivier Jacot-Descombes
źródło
Analiza kodu (i FxCop) informuje, że po powrocie konkretny zbiór rodzajowe, powinien zostać zwrócony jeden z poniższych: Collection, ReadOnlyCollectionlub KeyedCollection. I Listnie należy tego zwracać.
DavidRR
@DavidRR: Często przydatne jest przekazanie listy do metody, która ma dodawać lub usuwać elementy. Jeśli wpiszesz parametr as IList<T>, nie ma możliwości zapewnienia, że ​​metoda nie zostanie wywołana z tablicą jako parametrem.
Olivier Jacot-Descombes
7

IList<T>jest podstawowym interfejsem dla wszystkich list ogólnych. Ponieważ jest to zbiór uporządkowany, implementacja może decydować o kolejności, od sortowania po zamówienie reklamowe. Ponadto Ilistposiada właściwość Item, która umożliwia metodom odczytywanie i edycję wpisów na liście na podstawie ich indeksu. Umożliwia to wstawianie, usuwanie wartości do / z listy w indeksie pozycji.

Ponieważ IList<T> : ICollection<T>wszystkie metody z ICollection<T>są również dostępne tutaj do implementacji.

ICollection<T>jest podstawowym interfejsem dla wszystkich kolekcji ogólnych. Definiuje rozmiar, moduły wyliczające i metody synchronizacji. Możesz dodać lub usunąć element do kolekcji, ale nie możesz wybrać, w którym miejscu ma to nastąpić z powodu braku właściwości index.

Collection<T>stanowi implementację IList<T>, IListi IReadOnlyList<T>.

Jeśli używasz węższego typu interfejsu, takiego jak ICollection<T>zamiast IList<T>, chronisz kod przed wprowadzaniem zmian. Jeśli używasz szerszego typu interfejsu, takiego jak IList<T>, istnieje większe ryzyko zerwania zmian w kodzie.

Cytowanie ze źródła ,

ICollection, ICollection<T>: Chcesz zmodyfikować kolekcję lub zależy Ci na jej wielkości. IList, IList<T>: Chcesz zmodyfikować kolekcję i zależy Ci na kolejności i / lub pozycjonowaniu elementów w kolekcji.

NullReference
źródło
5

Zwracanie typu interfejsu jest bardziej ogólne, więc (brak dalszych informacji na temat twojego konkretnego przypadku użycia) skłaniałbym się ku temu. Jeśli chcesz udostępnić obsługę indeksowania, wybierz IList<T>, w przeciwnym razie ICollection<T>wystarczy. Na koniec, jeśli chcesz wskazać, że zwracane typy są tylko do odczytu, wybierz IEnumerable<T>.

A jeśli wcześniej tego nie czytaliście, Brad Abrams i Krzysztof Cwalina napisali świetną książkę zatytułowaną „Wytyczne dotyczące projektowania ram: konwencje, idiomy i wzorce dla bibliotek .NET wielokrotnego użytku” (podsumowanie można pobrać stąd ).

Alan
źródło
IEnumerable<T>jest tylko do odczytu? Jasne, ale po ponownym uruchomieniu go w kontekście repozytorium, nawet po wykonaniu ToList nie jest bezpieczny wątkowo. Problem polega na tym, że oryginalne źródło danych pobiera GC, a IEnumarble.ToList () nie działa w kontekście wielowątkowym. Działa na liniowym, ponieważ GC jest wywoływane po wykonaniu pracy, ale używanie asyncteraz dni w 4.5+ IEnumarbale staje się bólem piłki.
Piotr Kula
-3

Jest kilka tematów, które wynikają z tego pytania:

  • interfejsy a klasy
  • która konkretna klasa, z kilku podobnych klas, kolekcji, listy, tablicy?
  • Typowe klasy a kolekcje podelementów („typy ogólne”)

Możesz chcieć podkreślić, że jest to API zorientowane obiektowo

interfejsy a klasy

Jeśli nie masz dużego doświadczenia z interfejsami, polecam pozostać przy zajęciach. Widzę wiele razy deweloperów skaczących do interfejsów, nawet jeśli nie jest to konieczne.

I koniec z kiepskim projektem interfejsu, zamiast dobrym projektem klasowym, który, nawiasem mówiąc, może ostatecznie zostać przeniesiony do dobrego projektu interfejsu ...

Zobaczysz wiele interfejsów w API, ale nie spiesz się, jeśli tego nie potrzebujesz.

W końcu nauczysz się, jak stosować interfejsy w swoim kodzie.

która konkretna klasa, z kilku podobnych klas, kolekcji, listy, tablicy?

Istnieje kilka klas w języku C # (dotnet), które można wymieniać. Jak już wspomniano, jeśli potrzebujesz czegoś z bardziej specyficznej klasy, takiej jak „CanBeSortedClass”, wyraź to w swoim API.

Czy twój użytkownik API naprawdę musi wiedzieć, że twoją klasę można posortować lub zastosować jakiś format do elementów? Następnie użyj „CanBeSortedClass” lub „ElementsCanBePaintedClass”, w przeciwnym razie użyj „GenericBrandClass”.

W przeciwnym razie użyj bardziej ogólnej klasy.

Typowe klasy kolekcji a kolekcje podelementów („ogólne”)

Przekonasz się, że istnieją klasy zawierające inne elementy i możesz określić, że wszystkie elementy powinny być określonego typu.

Kolekcje ogólne to te klasy, których można używać tej samej kolekcji dla kilku aplikacji kodujących, bez konieczności tworzenia nowej kolekcji, dla każdego nowego typu elementu podrzędnego, na przykład: Collection .

Czy Twój użytkownik API będzie potrzebował bardzo konkretnego typu, takiego samego dla wszystkich elementów?

Użyj czegoś takiego jak List<WashingtonApple>.

Czy Twój użytkownik API będzie potrzebował kilku powiązanych typów?

Wystawiać List<Fruit>do API i użyć List<Orange> List<Banana>, List<Strawberry>wewnętrznie, gdzie Orange, Bananai Strawberrysą potomkami Fruit.

Czy Twój użytkownik interfejsu API będzie potrzebował kolekcji typów ogólnych?

Użyj Listtam, gdzie znajdują się wszystkie elementy object.

Twoje zdrowie.

umlcat
źródło
7
„Jeśli nie masz dużego doświadczenia z interfejsami, polecam trzymać się zajęć” To nie jest dobra rada. To tak, jakby powiedzieć, że jeśli jest coś, czego nie wiesz, po prostu trzymaj się od tego z daleka. Interfejsy są niezwykle potężne i wspierają koncepcję „Programowanie do abstrakcji kontra konkrecje”. Są również dość potężne do przeprowadzania testów jednostkowych ze względu na możliwość wymiany typów za pomocą makiet. Poza tymi komentarzami istnieje wiele innych wspaniałych powodów, dla których warto korzystać z interfejsów, więc prosimy o zapoznanie się z nimi.
atconway,
@atconway Regularnie używam interfejsów, ale podobnie jak wiele koncepcji, jest to potężne narzędzie, które może być łatwe w użyciu. Czasami programista może tego nie potrzebować. Moja sugestia nie była „nigdy nie używaj interfejsów”, moja sugestia brzmiała: „w tym przypadku możesz pominąć interfejsy. Dowiedz się o tym, a możesz uzyskać najlepsze z nich później”. Twoje zdrowie.
umlcat
1
Zalecam zawsze programowanie do interfejsu. Pomaga zachować luźne powiązanie kodu.
mac10688
@ mac10688 Moja poprzednia odpowiedź (atconway) dotyczy również Twojego komentarza.
umlcat