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 jest System.Array - to tylko typ podstawowy tablicy. Jeśli zapytasz typ tablicy, jakie interfejsy obsługuje, zawiera typy ogólne:
foreach (var type in typeof(int[]).GetInterfaces())
{
Console.WriteLine(type);
}
Wynik:
System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]
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 typ T[]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ład Count). 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 implementuje IList<T>.
Z perspektywy CLR dzieje się coś fajniejszego. Nie można uzyskać mapowania interfejsu dla ogólnych typów interfejsów. Na przykład:
typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))
Daje wyjątek:
Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.
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 nieIList<T> jest kowariantna (i nie może być bezpieczna), kowariancja tablicowa pozwala na to:
string[] strings = { "a", "b", "c" };
IList<object> objects = strings;
... 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ść:
Podpis typu T jest zgodny z podpisem typu U wtedy i tylko wtedy, gdy przynajmniej jedna z poniższych jest zachowana
...
T jest tablicą pozycji 1 od zera V[]i Ujest IList<W>, a V jest zgodna z elementem tablicy z W.
(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:
Dodatkowo utworzony wektor z elementem typu T implementuje interfejs System.Collections.Generic.IList<U>, gdzie U: = T. (§8.7)
( 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 ICollectionz jego Countwłaściwością. Tym razem można uzyskać mapowanie interfejsu, aw rzeczywistości interfejs jest realizowane bezpośrednio System.Array. Dokumentacja dotycząca ICollection.Countimplementacji właściwości Arraystwierdza, ż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:
public interface IFoo
{
void M1();
void M2();
}
public class Foo : IFoo
{
// Explicit interface implementation
void IFoo.M1() {}
// Implicit interface implementation
public void M2() {}
}
class Test
{
static void Main()
{
Foo foo = new Foo();
foo.M1(); // Compile-time failure
foo.M2(); // Fine
IFoo ifoo = foo;
ifoo.M1(); // Fine
ifoo.M2(); // Fine
}
}
Arrayzajęcia muszą być napisane w C #!