Czy ktoś może mi wyjaśnić, dlaczego chciałbym użyć IList zamiast listy w C #?
Powiązane pytanie: Dlaczego ujawnianie jest uważane za złeList<T>
Czy ktoś może mi wyjaśnić, dlaczego chciałbym użyć IList zamiast listy w C #?
Powiązane pytanie: Dlaczego ujawnianie jest uważane za złeList<T>
Odpowiedzi:
Jeśli udostępniasz swoją klasę za pomocą biblioteki, z której będą korzystać inni, zazwyczaj chcesz ją udostępnić za pomocą interfejsów, a nie konkretnych implementacji. Pomoże to, jeśli zdecydujesz się później zmienić implementację swojej klasy, aby użyć innej konkretnej klasy. W takim przypadku użytkownicy Twojej biblioteki nie będą musieli aktualizować swojego kodu, ponieważ interfejs się nie zmienia.
Jeśli używasz go tylko wewnętrznie, możesz nie przejmować się tak bardzo, a używanie
List<T>
może być w porządku.źródło
Mniej popularną odpowiedzią jest to, że programiści lubią udawać, że ich oprogramowanie będzie ponownie używane na całym świecie. W rzeczywistości większość projektów będzie utrzymywana przez niewielką liczbę ludzi, a niezależnie od tego, jak fajne są interfejsy dźwiękowe, to łudzisz siebie.
Astronauci architektury . Szanse, że kiedykolwiek napiszesz własną IListę, która dodaje coś do tych, które są już w środowisku .NET, są tak odległe, że to teoretyczne żelki zarezerwowane dla „najlepszych praktyk”.
Oczywiście, jeśli zostaniesz zapytany o to, którego używasz w wywiadzie, powiesz: IList, uśmiechnij się i oboje wyglądają na zadowolonych z siebie za to, że jesteś tak mądry. Lub dla publicznego API, IList. Mam nadzieję, że rozumiesz.
źródło
IEnumerable<string>
, wewnątrz funkcji mogę użyć aList<string>
dla wewnętrznego magazynu kopii zapasowych do wygenerowania mojej kolekcji, ale chcę tylko, aby osoby dzwoniące wyliczyły jej zawartość, a nie dodawały ani nie usuwały. Zaakceptowanie interfejsu jako parametru przekazuje podobny komunikat „Potrzebuję kolekcji ciągów, nie martw się, nie zmienię tego”.Interfejs to obietnica (lub umowa).
Jak zawsze z obietnicami - im mniejsze, tym lepiej .
źródło
IList
jest obietnica. Która jest tutaj mniejsza i lepsza obietnica? To nie jest logiczne.List<T>
, ponieważAddRange
nie jest zdefiniowany w interfejsie.IList<T>
składa obietnice, których nie może dotrzymać. Na przykład istniejeAdd
metoda, którą można rzucić, jeśliIList<T>
akurat jest tylko do odczytu.List<T>
z drugiej strony jest zawsze do zapisu i dlatego zawsze dotrzymuje obietnic. Ponadto, od .NET 4.6, teraz mamyIReadOnlyCollection<T>
iIReadOnlyList<T>
które prawie zawsze lepiej pasują niżIList<T>
. I są kowariantne. UnikajIList<T>
!IList<T>
. Ktoś mógłby równie dobrze zaimplementowaćIReadOnlyList<T>
i sprawić, by każda metoda rzucała wyjątek. To coś w rodzaju przeciwieństwa pisania na kaczce - nazywasz ją kaczką, ale nie może tak kwakać. Jest to jednak część kontraktu, nawet jeśli kompilator nie może go egzekwować. Zgadzam się jednak w 100% z tym, że powinienemIReadOnlyList<T>
lubIReadOnlyCollection<T>
powinienem z niego korzystać tylko do odczytu.Niektórzy mówią „zawsze używaj
IList<T>
zamiastList<T>
”.Chcą, abyś zmienił sygnatury metod z
void Foo(List<T> input)
navoid Foo(IList<T> input)
.Ci ludzie się mylą.
Jest bardziej dopracowany niż to. Jeśli wracasz
IList<T>
jako część publicznego interfejsu do swojej biblioteki, pozostawiasz sobie ciekawe opcje, aby być może stworzyć własną listę w przyszłości. Być może nigdy nie potrzebujesz tej opcji, ale jest to argument. Myślę, że to cały argument za zwróceniem interfejsu zamiast konkretnego typu. Warto wspomnieć, ale w tym przypadku ma poważną wadę.Jako drobny kontrargument, może się okazać, że każdy dzwoniący potrzebuje i
List<T>
tak, a kod wywołujący jest zaśmiecony.ToList()
Ale co ważniejsze, jeśli akceptujesz IList jako parametr, lepiej bądź ostrożny, ponieważ
IList<T>
iList<T>
nie zachowujesz się w ten sam sposób. Mimo podobieństwa nazwy, a mimo dzielenie interfejs oni nie narazić taką samą umowę .Załóżmy, że masz tę metodę:
Pomocny kolega „refaktoryzuje” metodę akceptacji
IList<int>
.Twój kod jest teraz uszkodzony, ponieważ
int[]
implementujeIList<int>
, ale ma stały rozmiar. UmowaICollection<T>
(podstawaIList<T>
) wymaga kodu, który używa jej do sprawdzeniaIsReadOnly
flagi przed próbą dodania lub usunięcia elementów z kolekcji. UmowaList<T>
nie.Zasada podstawienia Liskowa (uproszczona) stanowi, że typ pochodny powinien być stosowany zamiast typu podstawowego, bez dodatkowych warunków wstępnych i dodatkowych.
Wydaje się, że to łamie zasadę substytucji Liskowa.
Ale tak nie jest. Odpowiedź na to jest taka, że w przykładzie użyto IList <T> / ICollection <T> niepoprawnie. Jeśli używasz ICollection <T>, musisz sprawdzić flagę IsReadOnly.
Jeśli ktoś przekaże Ci tablicę lub listę, Twój kod będzie działał dobrze, jeśli za każdym razem sprawdzisz flagę i cofniesz się ... Ale naprawdę; kto tak robi? Czy nie wiesz z góry, czy twoja metoda potrzebuje listy, która może przyjąć dodatkowych członków; nie podajesz tego w podpisie metody? Co dokładnie zrobiłbyś, gdybyś otrzymał listę tylko do odczytu
int[]
?Możesz zastąpić
List<T>
kod, który używaIList<T>
/ICollection<T>
poprawnie . Nie możesz zagwarantować, że możesz zastąpić używany kodIList<T>
/ .ICollection<T>
List<T>
Istnieje wiele odwołań do zasady pojedynczej odpowiedzialności / zasady segregacji interfejsu w wielu argumentach, aby używać abstrakcji zamiast konkretnych typów - zależą od najwęższego możliwego interfejsu. W większości przypadków, jeśli używasz a
List<T>
i myślisz, że możesz zamiast tego użyć węższego interfejsu - dlaczego nieIEnumerable<T>
? Jest to często lepsze dopasowanie, jeśli nie trzeba dodawać elementów. Jeśli chcesz dodać do kolekcji, użyć typu beton,List<T>
.Dla mnie
IList<T>
(iICollection<T>
) jest najgorszą częścią frameworku .NET.IsReadOnly
narusza zasadę najmniejszego zaskoczenia. Klasa taka jakArray
, która nigdy nie pozwala na dodawanie, wstawianie lub usuwanie elementów, nie powinna implementować interfejsu za pomocą metod dodawania, wstawiania i usuwania. (patrz także /software/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties )Czy
IList<T>
pasuje do twojej organizacji? Jeśli kolega poprosi cię o zmianę podpisu metody, którego chcesz użyćIList<T>
zamiastList<T>
, zapytaj go, jak dodaliby element doIList<T>
. Jeśli nie wiedząIsReadOnly
(a większość ludzi nie wie ), nie używajIList<T>
. Zawsze.Zauważ, że flaga IsReadOnly pochodzi z ICollection <T> i wskazuje, czy elementy można dodać lub usunąć z kolekcji; ale tak naprawdę, aby wprowadzić w błąd rzeczy, nie wskazuje, czy można je zastąpić, co może być w przypadku tablic (które zwracają IsReadOnlys == true).
Aby uzyskać więcej informacji na temat IsReadOnly, zobacz definicję msdn dla ICollection <T> .IsReadOnly
źródło
IsReadOnly
jesttrue
toIList
możliwe, nadal możesz modyfikować jego obecnych członków! Być może tę właściwość należy nazwaćIsFixedSize
?Array
jakoIList
, więc mógłbym zmodyfikować obecnych członków)IReadOnlyCollection<T>
iIReadOnlyList<T>
które prawie zawsze lepiej pasują niż,IList<T>
ale nie mają niebezpiecznej, leniwej semantykiIEnumerable<T>
. I są kowariantne. UnikajIList<T>
!List<T>
jest specyficzną implementacjąIList<T>
, która jest kontenerem, który można adresować w taki sam sposób jak tablicę liniową zT[]
wykorzystaniem indeksu liczb całkowitych. PodającIList<T>
jako typ argumentu metody, określasz tylko, że potrzebujesz pewnych możliwości kontenera.Na przykład specyfikacja interfejsu nie wymusza zastosowania określonej struktury danych. Implementacja
List<T>
dzieje się z tą samą wydajnością w zakresie uzyskiwania dostępu, usuwania i dodawania elementów jako tablicy liniowej. Można jednak wyobrazić sobie implementację, która jest poparta połączoną listą, dla której dodawanie elementów na końcu jest tańsze (czas stały), ale dostęp losowy jest znacznie droższy. (Należy pamiętać, że .NETLinkedList<T>
ma nie wdrożeniaIList<T>
.)W tym przykładzie pokazano również, że mogą wystąpić sytuacje, w których konieczne jest określenie implementacji, a nie interfejsu, na liście argumentów: W tym przykładzie, ilekroć potrzebujesz konkretnej charakterystyki wydajności dostępu. Jest to zwykle gwarantowane dla konkretnej implementacji kontenera (
List<T>
dokumentacja: „ImplementujeIList<T>
ogólny interfejs za pomocą tablicy, której rozmiar jest dynamicznie zwiększany zgodnie z wymaganiami.”).Ponadto możesz rozważyć udostępnienie najmniejszej potrzebnej funkcjonalności. Na przykład. jeśli nie musisz zmieniać zawartości listy, prawdopodobnie powinieneś rozważyć użycie rozszerzenia
IEnumerable<T>
, które sięIList<T>
rozszerza.źródło
LinkedList<T>
vs biorącList<T>
vsIList<T>
wszyscy przekazują coś z gwarancji wydajności wymaganych przez wywoływany kod.Chciałbym nieco odwrócić to pytanie, zamiast uzasadniać, dlaczego powinieneś używać interfejsu zamiast konkretnej implementacji, spróbuj uzasadnić, dlaczego miałbyś użyć konkretnej implementacji zamiast interfejsu. Jeśli nie możesz tego uzasadnić, skorzystaj z interfejsu.
źródło
IList <T> to interfejs, dzięki któremu można odziedziczyć inną klasę i nadal implementować IList <T>, podczas gdy dziedziczenie List <T> nie pozwala na to.
Na przykład, jeśli istnieje klasa A, a twoja klasa B ją dziedziczy, nie możesz użyć Listy <T>
źródło
Zasadą TDD i OOP jest na ogół programowanie interfejsu, a nie implementacja.
W tym konkretnym przypadku, ponieważ zasadniczo mówisz o konstrukcji językowej, a nie niestandardowej, to na ogół nie będzie miało znaczenia, ale powiedz na przykład, że okazało się, że Lista nie obsługuje czegoś, czego potrzebujesz. Jeśli używałeś IList w pozostałej części aplikacji, możesz rozszerzyć Listę o własną klasę niestandardową i nadal być w stanie przekazywać ją bez refaktoryzacji.
Koszt tego jest minimalny, dlaczego nie zaoszczędzić sobie później bólu głowy? Na tym polega zasada interfejsu.
źródło
W takim przypadku można przekazać dowolną klasę, która implementuje interfejs IList <Bar>. Jeśli zamiast tego użyto List <Bar>, można przekazać tylko instancję List <Bar>.
Sposób IList <Bar> jest luźniej sprzężony niż sposób List <Bar>.
źródło
Zaskakujące, że żadne z tych pytań (lub odpowiedzi) nie wspomina o różnicy podpisu. (Dlatego szukałem tego pytania na SO!)
Oto metody zawarte w List, których nie ma w IList, przynajmniej od .NET 4.5 (około 2015)
źródło
Reverse
iToArray
są metodami rozszerzenia interfejsu IEnumerable <T>, z którego wywodzi się IList <T>.List
si, a nie w innych implementacjachIList
.Najważniejszym przypadkiem użycia interfejsów zamiast implementacji są parametry w interfejsie API. Jeśli interfejs API przyjmuje parametr List, każdy, kto go używa, musi użyć List. Jeśli typ parametru to IList, osoba wywołująca ma znacznie większą swobodę i może korzystać z klas, o których nigdy nie słyszałeś, a które nawet nie istniałyby podczas pisania kodu.
źródło
Co jeśli .NET 5.0 zastępuje
System.Collections.Generic.List<T>
sięSystem.Collection.Generics.LinearList<T>
. .NET zawsze jest właścicielem nazwy,List<T>
ale gwarantują toIList<T>
jest to umowa. Więc IMHO my (przynajmniej ja) nie powinniśmy używać czyichś nazwisk (choć w tym przypadku jest to .NET) i wpakować się później.W przypadku użycia
IList<T>
, osoba dzwoniąca zawsze gwarantuje rzeczy do pracy, a osoba wdrażająca może zmienić kolekcję bazową na dowolną alternatywną konkretną implementacjęIList
źródło
Wszystkie koncepcje są w zasadzie zawarte w większości powyższych odpowiedzi dotyczących tego, dlaczego należy używać interfejsu zamiast konkretnych implementacji.
IList<T>
Łącze MSDNList<T>
implementuje te dziewięć metod (nie wliczając metod rozszerzania), ponadto ma około 41 metod publicznych, co waży, biorąc pod uwagę, którą z nich należy zastosować w aplikacji.List<T>
Łącze MSDNźródło
Zrobiłbyś to, ponieważ zdefiniowanie IList lub ICollection otworzyłoby się na inne implementacje twoich interfejsów.
Możesz chcieć mieć IOrderRepository, które definiuje zbiór zamówień w IList lub ICollection. Możesz wtedy zastosować różne rodzaje implementacji, aby uzyskać listę zamówień, o ile są one zgodne z „regułami” zdefiniowanymi przez IList lub ICollection.
źródło
IList <> jest prawie zawsze preferowana zgodnie z zaleceniami drugiego autora, jednak zauważ, że w .NET 3.5 sp 1 występuje błąd podczas uruchamiania IList <> przez więcej niż jeden cykl serializacji / deserializacji z WCF DataContractSerializer.
Dostępny jest teraz dodatek SP do naprawy tego błędu: KB 971030
źródło
Interfejs zapewnia, że uzyskasz przynajmniej oczekiwane metody ; mając świadomość definicji interfejsu, tj. wszystkie metody abstrakcyjne, które mają zostać zaimplementowane przez dowolną klasę dziedziczącą interfejs. więc jeśli ktoś tworzy własną klasę za pomocą kilku metod oprócz metod odziedziczonych po interfejsie dla dodatkowej funkcjonalności, a te nie są dla ciebie przydatne, lepiej użyć odwołania do podklasy (w tym przypadku interfejs) i przypisz mu konkretny obiekt klasy.
dodatkową zaletą jest to, że Twój kod jest bezpieczny przed wszelkimi zmianami w konkretnej klasie, ponieważ subskrybujesz tylko kilka metod konkretnej klasy i te będą tam, dopóki konkretna klasa odziedziczy po interfejsie, którym jesteś za pomocą. więc jego bezpieczeństwo dla ciebie i wolność dla programisty piszącego konkretną implementację, aby zmienić lub dodać więcej funkcjonalności do swojej konkretnej klasy.
źródło
Możesz spojrzeć na ten argument z kilku punktów widzenia, w tym podejścia opartego wyłącznie na OO, które mówi o programowaniu przeciwko interfejsowi, a nie implementacji. Z tą myślą korzystanie z IList odbywa się według tej samej zasady, co przekazywanie i używanie interfejsów zdefiniowanych od zera. Wierzę również w czynniki skalowalności i elastyczności zapewniane przez interfejs w ogóle. Jeśli klasa implementująca IList <T> wymaga rozszerzenia lub zmiany, kod zużywający nie musi się zmieniać; wie, czego przestrzega umowa IList Interface. Jednak użycie konkretnej implementacji i listy <T> w klasie, która się zmienia, może również wymagać zmiany kodu wywołującego. Wynika to z faktu, że klasa przylegająca do IList <T> gwarantuje pewne zachowanie, które nie jest gwarantowane przez konkretny typ używający List <T>.
Również posiadanie uprawnień do robienia czegoś takiego jak modyfikowanie domyślnej implementacji List <T> w klasie Implementing IList <T>, na przykład .Add, .Remove lub dowolna inna metoda IList daje programistom dużą elastyczność i moc, w innym przypadku wstępnie zdefiniowaną według listy <T>
źródło
Zazwyczaj dobrym podejściem jest użycie IList w publicznym interfejsie API (w razie potrzeby i semantyka listy są potrzebne), a następnie Lista wewnętrznie w celu wdrożenia interfejsu API. Umożliwia to zmianę na inną implementację IList bez łamania kodu używającego twojej klasy.
Lista nazw klas może zostać zmieniona w następnym frameworku .net, ale interfejs nigdy się nie zmieni, ponieważ interfejs jest kontraktowy.
Zauważ, że jeśli twój interfejs API będzie używany tylko w pętlach foreach itp., Możesz rozważyć wystawienie IEnumerable.
źródło