Dlaczego tablica implementuje IList?

141

Zobacz definicję klasy System.Array

public abstract class Array : IList, ...

Teoretycznie powinienem być w stanie napisać ten fragment i być szczęśliwy

int[] list = new int[] {};
IList iList = (IList)list;

Powinienem również móc wywołać dowolną metodę z iList

 ilist.Add(1); //exception here

Moje pytanie nie brzmi: dlaczego otrzymuję wyjątek, ale raczej dlaczego Array implementuje IList ?

oleksii
źródło
22
Dobre pytanie. Nigdy nie podobał mi się pomysł grubych interfejsów (to techniczny termin na tego rodzaju projekty).
Konrad Rudolph
2
Czy komuś naprawdę zależy na LSP? Wydaje mi się to dość akademickie.
Gabe
13
@ Gabe, musisz pracować z większymi bazami kodu. Zaimplementowanie zachowania (dziedziczenie z interfejsu), a następnie po prostu ignorowanie rzeczy, których nie lubisz / których nie możesz obsługiwać, prowadzi do śmierdzącego, zaciemnionego, rzutowania i wreszcie: błędnego kodu.
Marius
3
@ Gabe to kolekcja, która implikuje zmienność, a nie zawarte w niej encje. Możesz uczynić swoją klasę składową typu, który implementuje zarówno IRWList <>, jak i IReadList <>, użyj if jako IRWList <> wewnętrznie w swojej klasie i ujawnij ją jako IReadList. Tak, musisz gdzieś umieścić złożoność, ale po prostu nie widzę, jak to się ma do lekceważenia LSP jako bardzo dobrej zasady projektowej (nie wiedziałem o właściwości IsReadOnly, co sprawia, że ​​IList jest bardziej złożony z punktu widzenia konsumenta)
Marius

Odpowiedzi:

94

Ponieważ tablica umożliwia szybki dostęp przez indeks, a IList/ IList<T>to jedyne interfejsy kolekcji, które to obsługują. Więc może Twoje prawdziwe pytanie brzmi: „Dlaczego nie ma interfejsu dla stałych kolekcji z indeksatorami?” I na to nie mam odpowiedzi.

Nie ma też interfejsów tylko do odczytu dla kolekcji. I brakuje mi tych nawet bardziej niż stałego rozmiaru z interfejsem indeksatorów.

IMO powinno być kilka dodatkowych (ogólnych) interfejsów do zbierania danych, w zależności od cech zbioru. Nazwy też powinny być inne, Listbo coś z indeksatorem to naprawdę głupie IMO.

  • Tylko wyliczenie IEnumerable<T>
  • Tylko do odczytu, ale bez indeksatora (.Count, .Contains, ...)
  • Możliwość zmiany rozmiaru, ale bez indeksatora, tj. Ustawiona jako (Dodaj, Usuń, ...) bieżąca ICollection<T>
  • Tylko do odczytu z indeksatorem (indeksator, indeksowanie, ...)
  • Stały rozmiar z indeksatorem (indeksator z ustawiaczem)
  • Zmienny rozmiar z prądem indeksatora (Wstaw, ...) IList<T>

Myślę, że obecne interfejsy kolekcji są źle zaprojektowane. Ale ponieważ mają właściwości, które mówią ci, które metody są prawidłowe (i jest to część kontraktu tych metod), nie łamie zasady substytucji.

CodesInChaos
źródło
14
Dziękuję za odpowiedź. Ale wolę pozostawić pytanie tak, jak jest. Powód jest prosty. Interfejs jest zamówieniem publicznym. Jeśli ktoś go wdroży, to trzeba w pełni zaimplementować wszystkie elementy, w przeciwnym razie zepsuje LSP i ogólnie brzydko pachnie, prawda?
oleksii
16
To łamie LSP. Jeśli nie, Dodaj (element) powinien dodać pozycję do listy niezależnie od konkretnego typu. Z wyjątkiem wyjątkowych przypadków. W implementacji tablicy w rzuca wyjątek w przypadku nietypowym, co samo w sobie jest złą praktyką
Rune FS
2
@smelch Przepraszam, ale źle zrozumiałeś LSP. Tablica nie implementuje addi dlatego nie może być zastąpiona czymś, co robi, gdy ta umiejętność jest wymagana.
Rune FS
7
Przyznaję, że technicznie nie narusza to LSP tylko dlatego, że dokumentacja stwierdza, że ​​należy sprawdzić właściwości IsFixedSizei IsReadOnly, zdecydowanie narusza zasadę Tell, Don't Ask i Zasadę najmniejszego zaskoczenia . Po co implementować interfejs, skoro zamierzasz tylko zgłosić wyjątki dla 4 z 9 metod?
Matthew,
11
Minęło trochę czasu od pierwotnego pytania. Ale teraz z .Net 4.5 są dodatkowe interfejsy IReadOnlyList i IReadOnlyCollection .
Tobias
43

Uwag sekcję dokumentacji dla IListmówi

IList jest potomkiem interfejsu ICollection i jest interfejsem podstawowym wszystkich list nieogólnych. Implementacje IList dzielą się na trzy kategorie: tylko do odczytu, o stałym rozmiarze i o zmiennym rozmiarze . Nie można modyfikować IList tylko do odczytu. IList o stałym rozmiarze nie pozwala na dodawanie ani usuwanie elementów, ale umożliwia modyfikację istniejących elementów. IList o zmiennej wielkości umożliwia dodawanie, usuwanie i modyfikację elementów.

Oczywiście tablice należą do kategorii o stałym rozmiarze, więc ze względu na definicję interfejsu ma to sens.

Brian Rasmussen
źródło
4
Myślę, że skończyłoby się na wielu interfejsach. IListFixedSize, IListReadOnly ...
Magnus
9
to właściwie dobra odpowiedź z punktu widzenia dokumentacji. Ale dla mnie wygląda to raczej na włamanie. Interfejsy muszą być cienkie i proste, aby klasa mogła zaimplementować wszystkie składowe.
oleksii
1
@oleksii: Zgadzam się. Interfejsy i wyjątki czasu wykonywania nie są najbardziej elegancką kombinacją. W obronie Arraytego Addwyraźnie implementuje metodę, co zmniejsza ryzyko przypadkowego wywołania jej.
Brian Rasmussen
Dopóki nie stworzymy implementacji, IListktóra zabrania zarówno modyfikacji, jak i dodawania / usuwania. Wtedy dokumentacja nie jest już poprawna. : P
Timo
1
@Magnus - W .Net 4.5 są dodatkowe interfejsy IReadOnlyList i IReadOnlyCollection .
RBT
17

Ponieważ nie wszystkie ILists są zmienne (zobacz IList.IsFixedSizei IList.IsReadOnly), a tablice z pewnością zachowują się jak listy o stałym rozmiarze.

Jeśli twoje pytanie naprawdę brzmi „dlaczego implementuje nieogólny interfejs”, to odpowiedź jest taka, że ​​były one w pobliżu, zanim pojawiły się generyczne.

user541686
źródło
10
@oleksii: Nie, to nie psuje LSP, ponieważ IList sam interfejs mówi ci, że może nie być zmienny. Gdyby faktycznie gwarantowano, że będzie zmienny, a tablica mówi inaczej, złamałoby to regułę.
user541686
W rzeczywistości Array psuje LSP w przypadku generycznego IList<T>i nie psuje go w przypadku nieogólnego IList: enterprisecraftsmanship.com/2014/11/22/…
Vladimir
5

To dziedzictwo, które mamy z czasów, gdy nie było jasne, jak radzić sobie z kolekcjami tylko do odczytu i czy Array jest tylko do odczytu. W interfejsie IList istnieją flagi IsFixedSize i IsReadOnly. Flaga IsReadOnly oznacza, że ​​nie można w ogóle zmienić kolekcji, a IsFixedSize oznacza, że ​​kolekcja umożliwia modyfikację, ale nie pozwala na dodawanie ani usuwanie elementów.

W czasie .Net 4.5 było jasne, że niektóre „pośrednie” interfejsy są wymagane do pracy przy zbiorach tylko do odczytu, więc IReadOnlyCollection<T>i IReadOnlyList<T>zostały wprowadzone.

Oto świetny wpis na blogu opisujący szczegóły: Kolekcje tylko do odczytu w .NET

Vladimir
źródło
0

Definicja interfejsu IList to „Reprezentuje nieogólną kolekcję obiektów, do których można uzyskać dostęp za pomocą indeksu”. Tablica całkowicie spełnia tę definicję, więc musi implementować interfejs. Wyjątkiem podczas wywoływania metody Add () jest „System.NotSupportedException: kolekcja miała stały rozmiar” i wystąpił, ponieważ tablica nie może dynamicznie zwiększać swojej pojemności. Jego pojemność jest określana podczas tworzenia obiektu tablicy.

meir
źródło
0

Posiadanie tablicy implementującej IList (i przejściowo ICollection) uprościło aparat Linq2Objects, ponieważ rzutowanie IEnumerable na IList / ICollection działałoby również dla tablic.

Na przykład Count () kończy wywołanie Array.Length pod maską, ponieważ jest rzutowana na ICollection, a implementacja tablicy zwraca Length.

Bez tego silnik Linq2Objects nie byłby specjalnie traktowany dla tablic i działałby strasznie, albo musieliby podwoić kod, dodając specjalne traktowanie tablic (tak jak w przypadku IList). Musieli zamiast tego zdecydować się na implementację IList w tablicach.

To moje podejście do „Dlaczego”.

Herman Schoenfeld
źródło
0

Również szczegóły implementacji LINQ Last sprawdza dla IList, jeśli nie zaimplementował listy, potrzebowałby albo 2 kontroli spowalniających wszystkie ostatnie wywołania, albo mieć Last on an Array biorąc O (N)

user1496062
źródło