Kiedy używać IList, a kiedy List


180

Wiem, że IList to interfejs, a List to konkretny typ, ale nadal nie wiem, kiedy użyć każdego z nich. Teraz robię, jeśli nie potrzebuję metod Sort lub FindAll, których używam w interfejsie. Czy mam rację? Czy istnieje lepszy sposób decydowania, kiedy użyć interfejsu lub konkretnego typu?


1
Jeśli ktoś nadal się zastanawia, najlepsze odpowiedzi znajduję tutaj: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Odpowiedzi:


175

Przestrzegam dwóch zasad:

  • Zaakceptuj najbardziej podstawowy typ, który będzie działał
  • Zwróć najbogatszy typ, jakiego będzie potrzebował twój użytkownik

Dlatego podczas pisania funkcji lub metody, która pobiera kolekcję, napisz ją, aby nie pobierała List, ale IList <T>, ICollection <T> lub IEnumerable <T>. Ogólne interfejsy będą nadal działać nawet w przypadku list heterogenicznych, ponieważ System.Object również może być literą T. Dzięki temu zaoszczędzisz sobie bólu głowy, jeśli zdecydujesz się użyć stosu lub innej struktury danych w dalszej części drogi. Jeśli wszystko, co musisz zrobić w funkcji, to zawsze przez nią, IEnumerable <T> jest naprawdę wszystkim, o co powinieneś prosić.

Z drugiej strony, zwracając obiekt z funkcji, chcesz dać użytkownikowi możliwie najbogatszy zestaw operacji bez konieczności wykonywania rzutów. W takim przypadku, jeśli jest to List <T> wewnętrznie, zwróć kopię jako List <T>.


43
Nie powinieneś inaczej traktować typów wejścia / wyjścia. Typy wejściowe i wyjściowe powinny zarówno być najbardziej podstawowy typ (najlepiej Interface), która będzie obsługiwać klientów potrzeb. Hermetyzacja polega na informowaniu klientów o implementacji Twojej klasy w jak najmniejszym stopniu. Jeśli zwrócisz konkretną listę, nie możesz zmienić na inny lepszy typ bez zmuszania wszystkich klientów do ponownej kompilacji / aktualizacji.
Ash,

11
Nie zgadzam się z dwiema regułami ... Użyłbym najbardziej prymitywnego typu i specjalnego, zwracając w tym przypadku IList (lepiej IEnumarable) i powinieneś pracować z List w swojej funkcji wewnątrz. Następnie, gdy potrzebujesz opcji „dodaj” lub „sortuj”, użyj opcji Kolekcja, jeśli potrzebujesz więcej, niż listy. Tak więc moja twarda zasada brzmi: ZACZNIJ zawsze z IENumarable, a jeśli potrzebujesz więcej, przedłuż ...
ethem

2
Dla Twojej wygody „dwie reguły” mają nazwę: zasada odporności (inaczej prawo Postela) .
easoncxz

Niezależnie od tego, po której stronie debaty ktoś się zastanawia, czy zwrócić najbardziej podstawowy, czy najbogatszy typ, należy wziąć pod uwagę fakt, że zwracając bardzo uproszczony interfejs, konsumujący kod może często - choć nie zawsze - używać if...elsełańcucha ze issłowem kluczowym do obliczenia wydać dla niego znacznie bogatszy typ i w końcu rzucać do niego i używać tego mimo wszystko. Więc niekoniecznie masz gwarancję ukrycia czegokolwiek za pomocą podstawowego interfejsu, zamiast po prostu go zasłaniać. Jednak utrudnienie tego może również zmusić autora używającego kodu do zastanowienia się dwa razy nad tym, jak go używa.
Panzercrisis,

6
Bardzo mocno nie zgadzam się z punktem 2, zwłaszcza jeśli jest to granica usługi / interfejsu API. Zwracanie kolekcji, które można modyfikować, może sprawiać wrażenie, że są one aktywne i wywoływanie metod, takich jak Add()i Remove()może mieć skutki wykraczające poza samą kolekcję. Zwracanie interfejsu tylko do odczytu, takiego jak IEnumerableczęsto metoda pobierania danych. Twój konsument może w razie potrzeby zamienić go na bogatszy typ.
STW

56

Wytyczne firmy Microsoft sprawdzone przez FxCop odradzają używanie List <T> w publicznych interfejsach API - preferuj IList <T>.

Nawiasem mówiąc, teraz prawie zawsze deklaruję jednowymiarowe tablice jako IList <T>, co oznacza, że ​​mogę konsekwentnie używać właściwości IList <T> .Count zamiast Array.Length. Na przykład:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
Najbardziej podoba mi się to wyjaśnienie / przykład!
JonH

28

Jest ważna rzecz, którą ludzie zawsze przeoczają:

Możesz przekazać zwykłą tablicę do czegoś, co akceptuje IList<T>parametr, a następnie możesz wywołać IList.Add()i otrzymać wyjątek czasu wykonania:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Weźmy na przykład pod uwagę następujący kod:

private void test(IList<int> list)
{
    list.Add(1);
}

Jeśli nazwiesz to w następujący sposób, otrzymasz wyjątek czasu wykonywania:

int[] array = new int[0];
test(array);

Dzieje się tak, ponieważ używanie zwykłych tablic z IList<T>narusza zasadę podstawienia Liskova.

Z tego powodu, jeśli dzwonisz, IList<T>.Add()możesz rozważyć wymaganie List<T>zamiast IList<T>.


Jest to trywialne dla każdego interfejsu. Jeśli chcesz kontynuować swoją argumentację, możesz argumentować, że w ogóle nie używasz żadnego interfejsu, ponieważ niektóre jego implementacje mogą zostać odrzucone. Jeśli, z drugiej strony, należy rozważyć propozycję daną przez OP preferują List<T>nad IList<T>powinno być również świadomi powodów IList<T>jest zalecane. (Na przykład blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann

3
@MichaWiedenmann Moja odpowiedź dotyczy konkretnej osoby dzwoniącej IList<T>.Add(). Nie mówię, że nie powinieneś używać IList<T>- po prostu wskazuję na możliwą pułapkę. (Zwykle używam IEnumerable<T>lub IReadOnlyList<T>lub IReadOnlyCollection<T>zamiast, IList<T>jeśli mogę.)
Matthew Watson,

24

Zgodziłbym się z radą Lee dotyczącą przyjmowania parametrów, ale nie zwracania.

Jeśli określisz metody, aby zwrócić interfejs, oznacza to, że możesz później zmienić dokładną implementację bez wiedzy zużywającej metody. Pomyślałem, że nigdy nie będę musiał zmieniać z List <T>, ale później musiałem zmienić, aby użyć niestandardowej biblioteki list dla dodatkowych funkcji, które zapewniała. Ponieważ zwróciłem tylko IList <T>, żadna z osób korzystających z biblioteki nie musiała zmieniać swojego kodu.

Oczywiście wystarczy to dotyczyć tylko metod, które są widoczne na zewnątrz (np. Metody publiczne). Osobiście używam interfejsów nawet w kodzie wewnętrznym, ale ponieważ możesz zmienić cały kod samodzielnie, jeśli wprowadzisz istotne zmiany, nie jest to absolutnie konieczne.


22

IEnumerable
Powinieneś spróbować użyć najmniej konkretnego typu, który odpowiada twojemu celowi.
IEnumerablejest mniej szczegółowy niż IList.
Używasz, IEnumerablegdy chcesz przeglądać elementy w kolekcji.

IList
IList implementuje IEnumerable.
Powinieneś używać, IListgdy potrzebujesz dostępu przez indeks do swojej kolekcji, dodawania i usuwania elementów itp.

Lista
List narzędzi IList.


3
Doskonała, jasna odpowiedź, którą oznaczyłem jako pomocną. Dodam jednak, że dla większości programistów przez większość czasu niewielka różnica w rozmiarze i wydajności programu nie jest warta zmartwień: w razie wątpliwości wystarczy użyć listy.
Graham Laight

9

Zawsze najlepiej jest używać najniższego możliwego typu podstawowego. Daje to osobie wdrażającej Twój interfejs lub konsumentowi Twojej metody możliwość wykorzystania za kulisami tego, co mu się podoba.

W przypadku kolekcji należy dążyć do używania IEnumerable tam, gdzie to możliwe. Daje to największą elastyczność, ale nie zawsze jest odpowiednie.


1
Zawsze najlepiej jest akceptować najniższy możliwy typ podstawowy. Powrót to inna historia. Wybierz opcje, które mogą być przydatne. Więc myślisz, że Twój klient może chcieć skorzystać z dostępu indeksowanego? Nie pozwól im ToList()odesłać swojego zwróconego, IEnumerable<T>który był już listą, i zwróć plikIList<T> zamiast tego. Teraz klienci mogą korzystać z tego, co Ty możesz im zapewnić bez wysiłku.
Timo

5

Jeśli pracujesz w ramach jednej metody (lub nawet w jednej klasie lub zestawie w niektórych przypadkach) i nikt z zewnątrz nie będzie widział, co robisz, użyj pełnej listy. Ale jeśli wchodzisz w interakcję z zewnętrznym kodem, na przykład gdy zwracasz listę z metody, chcesz tylko zadeklarować interfejs bez konieczności wiązania się z konkretną implementacją, zwłaszcza jeśli nie masz kontroli nad tym, kto kompiluje kod później. Jeśli zacząłeś od konkretnego typu i zdecydowałeś się zmienić na inny, nawet jeśli używa tego samego interfejsu, złamiesz kod kogoś innego, chyba że zacząłeś od interfejsu lub abstrakcyjnego typu bazowego.


4

Nie sądzę, żeby istniały sztywne i szybkie zasady dotyczące tego typu rzeczy, ale zwykle kieruję się zasadą używania najlżejszej możliwej drogi, dopóki nie będzie to absolutnie konieczne.

Na przykład, powiedzmy, że masz Personklasę i Groupklasę. GroupInstancja ma wielu ludzi, więc Lista tu miałoby sensu. Kiedy zadeklaruję obiekt listy w Group, IList<Person>użyję go i utworzę go jako plik List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

A jeśli nawet nie potrzebujesz wszystkiego IList, zawsze możesz użyć IEnumerable. W przypadku nowoczesnych kompilatorów i procesorów nie wydaje mi się, aby istniała żadna różnica w szybkości, więc jest to bardziej kwestia stylu.


3
dlaczego nie uczynić z tego tylko listy w pierwszej kolejności? Nadal nie rozumiem, dlaczego otrzymujesz premię za uczynienie go IList, a następnie w konstruktorze zrobisz to w liście <>
chobo2

Zgadzam się, jeśli jawnie tworzysz obiekt List <T>, tracisz przewagę interfejsu?
The_Butcher,

4

Najczęściej lepiej jest używać najbardziej ogólnego użytecznego typu, w tym przypadku IList lub nawet lepiej interfejsu IEnumerable, dzięki czemu można wygodnie przełączać implementację w późniejszym czasie.

Jednak w .NET 2.0 jest irytująca rzecz - IList nie ma metody Sort () . Zamiast tego możesz użyć dostarczonego adaptera:

ArrayList.Adapter(list).Sort()

2

Powinieneś używać interfejsu tylko wtedy, gdy go potrzebujesz, np. Jeśli twoja lista jest rzutowana na implementację IList inną niż List. Dzieje się tak, na przykład, gdy używasz NHibernate, który rzutuje ILists do obiektu NHibernate bag podczas pobierania danych.

Jeśli List jest jedyną implementacją, której kiedykolwiek będziesz używać dla określonej kolekcji, możesz zadeklarować ją jako konkretną implementację List.


1

W sytuacjach, z którymi się zwykle spotykam, rzadko używam bezpośrednio IList.

Zwykle używam go jako argumentu do metody

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Pozwoli mi to na przetwarzanie generyczne na prawie każdej tablicy w środowisku .NET, chyba że używa IEnumerable, a nie IList, co czasami się zdarza.

To naprawdę sprowadza się do rodzaju potrzebnej funkcjonalności. W większości przypadków sugerowałbym użycie klasy List. IList jest najlepszy, gdy trzeba utworzyć niestandardową tablicę, która mogłaby mieć bardzo specyficzne reguły, które chcesz zawrzeć w kolekcji, aby nie powtarzać się, ale nadal chcesz, aby .NET rozpoznał ją jako listę.


1

Obiekt AList pozwala na tworzenie listy, dodawanie rzeczy do niej, usuwanie, aktualizację, indeksowanie do niej itp. Lista jest używana zawsze, gdy chcesz mieć listę ogólną, w której określasz typ obiektu i to wszystko.

Z drugiej strony IList to interfejs. Zasadniczo, jeśli chcesz utworzyć własny typ listy, powiedzmy klasę listy o nazwie BookList, możesz użyć interfejsu, aby zapewnić podstawowe metody i strukturę nowej klasy. IList jest przeznaczony do tworzenia własnej, specjalnej podklasy, która implementuje List.

Inna różnica polega na tym, że IList jest interfejsem i nie można go utworzyć. Lista jest klasą i można ją utworzyć. To znaczy:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
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.