Jak więc być może wiesz, tablice w implementacji C # IList<T>
, wśród innych interfejsów. Jednak w jakiś sposób robią to bez publicznego wdrażania właściwości Count IList<T>
! Tablice mają tylko właściwość Length.
Czy to jest rażący przykład C # / .NET łamiącego własne zasady dotyczące implementacji interfejsu, czy czegoś mi brakuje?
Array
zajęcia muszą być napisane w C #!Array
to "magiczna" klasa, której nie można zaimplementować w C # ani w żadnym innym języku docelowym .net. Ale ta konkretna funkcja jest dostępna w C #.Odpowiedzi:
Nowa odpowiedź w świetle odpowiedzi Hansa
Dzięki odpowiedzi udzielonej przez Hansa widzimy, że implementacja jest nieco bardziej skomplikowana, niż mogłoby się wydawać. Zarówno kompilator, jak i środowisko CLR bardzo się starają sprawiać wrażenie, że typ tablicy implementuje
IList<T>
- ale wariancja tablicy sprawia, że jest to trudniejsze. W przeciwieństwie do odpowiedzi Hansa, typy tablic (jednowymiarowe, w każdym razie oparte na zerach) implementują kolekcje ogólne bezpośrednio, ponieważ typ konkretnej tablicy nie jestSystem.Array
- to tylko typ podstawowy tablicy. Jeśli zapytasz typ tablicy, jakie interfejsy obsługuje, zawiera typy ogólne:Wynik:
W przypadku tablic jednowymiarowych, opartych na zerach, jeśli chodzi o język , tablica naprawdę również implementuje
IList<T>
. Sekcja 12.1.2 specyfikacji C # tak mówi. Zatem cokolwiek robi podstawowa implementacja, język musi zachowywać się tak, jakby typT[]
implementacji był taki,IList<T>
jak w przypadku każdego innego interfejsu. Z tej perspektywy interfejs jest implementowany, a niektóre elementy członkowskie są jawnie implementowane (na przykładCount
). To najlepsze wyjaśnienie na poziomie językowym tego, co się dzieje.Zauważ, że dotyczy to tylko tablic jednowymiarowych (i tablic opartych na zerach, a nie że C # jako język mówi cokolwiek o tablicach niezerowych).
T[,]
nie implementujeIList<T>
.Z perspektywy CLR dzieje się coś fajniejszego. Nie można uzyskać mapowania interfejsu dla ogólnych typów interfejsów. Na przykład:
Daje wyjątek:
Skąd więc ta dziwność? Uważam, że tak naprawdę jest to spowodowane kowariancją tablic, która jest brodawką w systemie typów IMO. Mimo że nie
IList<T>
jest kowariantna (i nie może być bezpieczna), kowariancja tablicowa pozwala na to:... co sprawia, że wygląda jak
typeof(string[])
narzędziaIList<object>
, podczas gdy tak naprawdę nie jest.Specyfikacja interfejsu CLI (ECMA-335) partycji 1, sekcja 8.7.1, ma następującą treść:
...
(Właściwie nie wspomina o
ICollection<W>
lubIEnumerable<W>
które uważam, że jest to błąd w specyfikacji).W przypadku braku wariancji specyfikacja CLI idzie w parze ze specyfikacją języka. Z sekcji 8.9.1 partycji 1:
( Wektor to jednowymiarowa tablica z zerową podstawą).
Jeśli chodzi o szczegóły implementacji , najwyraźniej CLR wykonuje pewne funky mapowania, aby zachować tutaj zgodność przypisania: gdy
string[]
zostanie poproszony o implementacjęICollection<object>.Count
, nie może sobie z tym poradzić w całkiem normalny sposób. Czy liczy się to jako jawna implementacja interfejsu? Myślę, że rozsądne jest traktowanie tego w ten sposób, ponieważ jeśli nie poprosisz bezpośrednio o mapowanie interfejsu, zawsze się zachowuje ten sposób z perspektywy języka.O co chodzi
ICollection.Count
?Do tej pory mówiłem o interfejsach ogólnych, ale jest też nieogólny
ICollection
z jegoCount
właściwością. Tym razem można uzyskać mapowanie interfejsu, aw rzeczywistości interfejs jest realizowane bezpośrednioSystem.Array
. Dokumentacja dotyczącaICollection.Count
implementacji właściwościArray
stwierdza, że została zaimplementowana z jawną implementacją interfejsu.Jeśli ktokolwiek może wymyślić sposób, w jaki tego rodzaju jawna implementacja interfejsu różni się od „normalnej” jawnej implementacji interfejsu, z przyjemnością przyjrzę się temu dokładniej.
Stara odpowiedź dotycząca jawnej implementacji interfejsu
Pomimo powyższego, co jest bardziej skomplikowane ze względu na znajomość tablic, nadal możesz zrobić coś z tymi samymi widocznymi efektami poprzez jawną implementację interfejsu .
Oto prosty samodzielny przykład:
źródło
Count
jest w porządku - aleAdd
zawsze będzie rzucać, ponieważ tablice mają stały rozmiar.Cóż, tak, erm nie, nie do końca. Oto deklaracja klasy Array w środowisku .NET 4:
Implementuje System.Collections.IList, a nie System.Collections.Generic.IList <>. Nie może, tablica nie jest ogólna. To samo dotyczy ogólnych interfejsów IEnumerable <> i ICollection <>.
Ale środowisko CLR tworzy konkretne typy tablic w locie, więc może technicznie utworzyć taki, który implementuje te interfejsy. Tak jednak nie jest. Wypróbuj ten kod na przykład:
Wywołanie GetInterfaceMap () kończy się niepowodzeniem dla konkretnego typu tablicy z komunikatem „Nie znaleziono interfejsu”. Jednak rzutowanie na IEnumerable <> działa bez problemu.
To jest pisanie jak kaczuszka. Jest to ten sam rodzaj pisania, który tworzy iluzję, że każdy typ wartości pochodzi od ValueType, który pochodzi od Object. Zarówno kompilator, jak i środowisko CLR mają specjalną wiedzę na temat typów tablic, podobnie jak w przypadku typów wartości. Kompilator widzi twoją próbę przesłania do IList <> i mówi "okej, wiem jak to zrobić!". I emituje instrukcję castclass IL. Środowisko CLR nie ma z tym żadnych problemów, wie, jak zapewnić implementację IList <>, która działa na bazowym obiekcie tablicy. Ma wbudowaną wiedzę o skądinąd ukrytej klasie System.SZArrayHelper, opakowaniu, które faktycznie implementuje te interfejsy.
Co nie jest jednoznaczne z twierdzeniami wszystkich, właściwość Count, o którą pytałeś, wygląda następująco:
Tak, z pewnością możesz nazwać ten komentarz „łamaniem zasad” :) Poza tym jest cholernie przydatny. I wyjątkowo dobrze ukryty, możesz to sprawdzić w SSCLI20, współdzielonej dystrybucji źródeł CLR. Wyszukaj „IList”, aby zobaczyć, gdzie ma miejsce podstawianie typów. Najlepszym miejscem do zobaczenia tego w akcji jest metoda clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod ().
Ten rodzaj substytucji w środowisku CLR jest dość łagodny w porównaniu z tym, co dzieje się w projekcji języka w środowisku CLR, który umożliwia pisanie kodu zarządzanego dla WinRT (aka Metro). Prawie każdy podstawowy typ platformy .NET zostanie tam zastąpiony. IList <> mapuje na przykład IVector <>, całkowicie niezarządzany typ. Sam w sobie podstawienie, COM nie obsługuje typów ogólnych.
Cóż, to było spojrzenie na to, co dzieje się za zasłoną. Mogą to być bardzo niewygodne, dziwne i nieznane morza ze smokami żyjącymi na końcu mapy. Bardzo przydatne może być spłaszczenie Ziemi i modelowanie innego obrazu tego, co naprawdę dzieje się w kodzie zarządzanym. Odwzorowanie go na ulubioną odpowiedź wszystkich jest w ten sposób wygodne. Co nie działa tak dobrze w przypadku typów wartości (nie modyfikuj struktury!), Ale ta jest bardzo dobrze ukryta. Błąd metody GetInterfaceMap () to jedyny przeciek w abstrakcji, o jakim przychodzi mi do głowy.
źródło
Array
klasy, która nie jest typem tablicy. Jest to podstawowy typ tablicy. Pojedyncza wymiarowe tablicy C # sposób realizacjiIList<T>
. A typ nieogólny z pewnością może i tak zaimplementować interfejs ogólny ... który działa, ponieważ istnieje wiele różnych typów -typeof(int[])
! = Typeof (string []), so
typeof (int []) `implementujeIList<int>
itypeof(string[])
implementujeIList<string>
.Array
(które, jak pokazujesz, jest klasą abstrakcyjną, więc prawdopodobnie nie może być rzeczywistym typem obiektu tablicy), jak i wniosek (że nie implementujeIList<T>
) są nieprawidłowe IMO. Sposób , w którym realizujeIList<T>
się niezwykła i ciekawa, będę zgodzić - ale to czysto realizacja szczegół. Twierdzenie, żeT[]
to nie implementuje,IList<T>
jest mylące dla IMO. Jest to sprzeczne ze specyfikacją i wszelkim obserwowanym zachowaniem.IList<T>
ponieważArray
tak nie jest? Ta logika jest dużą częścią tego, z czym się nie zgadzam. Poza tym myślę, że musielibyśmy uzgodnić definicję tego, co oznacza implementacja interfejsu przez typ: moim zdaniem typy tablicowe wyświetlają wszystkie obserwowalne cechy typów, które implementująIList<T>
, inne niżGetInterfaceMapping
. Ponownie, sposób, w jaki to zostanie osiągnięte, ma dla mnie mniejsze znaczenie, tak jak mogę powiedzieć, żeSystem.String
jest to niezmienne, mimo że szczegóły implementacji są różne.IList<T>
, aby działała.IList<T>.Count
jest implementowana jawnie :Odbywa się to w taki sposób, że gdy masz prostą zmienną tablicową, nie masz obu
Count
iLength
bezpośrednio dostępnych.Ogólnie rzecz biorąc, jawna implementacja interfejsu jest używana, gdy chcesz mieć pewność, że typ może być używany w określony sposób, bez zmuszania wszystkich konsumentów tego typu do myślenia o tym w ten sposób.
Edycja : Ups, źle pamiętam.
ICollection.Count
jest implementowana jawnie. OgólnyIList<T>
jest obsługiwane tak, jak Hans opisuje poniżej .źródło
string
).ICollection
deklarujeCount
, a byłoby jeszcze bardziej zagmatwane, gdyby typ ze słowem „kolekcja” nie był używanyCount
:). Podejmowanie takich decyzji zawsze wymaga kompromisów.IList<T>
, mimo że specyfikacje języka i interfejsu CLI wydają się być przeciwne. Ośmielę się powiedzieć, że sposób, w jaki implementacja interfejsu działa pod osłonami, może być skomplikowana, ale tak jest w wielu sytuacjach. Czy zgodziłbyś się również na kogoś, kto mówi, żeSystem.String
jest to niezmienne, tylko dlatego, że wewnętrzne działanie jest zmienne? Ze wszystkich praktycznych powodów - iz pewnością w przypadku języka C # - jest to jawne impl.Jawna implementacja interfejsu . Krótko mówiąc, deklarujesz to jako
void IControl.Paint() { }
lubint IList<T>.Count { get { return 0; } }
.źródło
Nie różni się od jawnej implementacji interfejsu IList. Tylko dlatego, że implementujesz interfejs, nie oznacza, że jego członkowie muszą pojawiać się jako członkowie klasy. To robi wdrożyć właściwości Count, to po prostu nie narażać go na X [].
źródło
Przy dostępnych źródłach referencyjnych:
W szczególności ta część:
(Podkreślenie moje)
Źródło (przewiń w górę).
źródło