Wie Sie vielleicht wissen, implementieren Arrays in C # IList<T>
unter anderem Schnittstellen. Irgendwie tun sie dies jedoch, ohne die Count-Eigenschaft von IList<T>
! Arrays haben nur eine Length-Eigenschaft.
Ist dies ein offensichtliches Beispiel dafür, dass C # /. NET seine eigenen Regeln für die Schnittstellenimplementierung verletzt, oder fehlt mir etwas?
Array
Klasse müsse in C # geschrieben werden!Array
ist eine "magische" Klasse, die nicht in C # oder einer anderen Sprache implementiert werden kann, die auf .net abzielt. Diese spezielle Funktion ist jedoch in C # verfügbar.Antworten:
Neue Antwort im Lichte von Hans 'Antwort
Dank der Antwort von Hans können wir sehen, dass die Implementierung etwas komplizierter ist, als wir vielleicht denken. Sowohl der Compiler als auch die CLR sind sehr bemüht , den Eindruck zu erwecken, den ein Array-Typ implementiert
IList<T>
- aber die Array-Varianz macht dies schwieriger. Im Gegensatz zu der Antwort von Hans implementieren die Array-Typen (eindimensional, ohnehin nullbasiert) die generischen Sammlungen direkt, da der Typ eines bestimmten Arrays nichtSystem.Array
- das ist nur der Basistyp des Arrays. Wenn Sie einen Array-Typ fragen, welche Schnittstellen er unterstützt, enthält er die generischen Typen:Ausgabe:
Bei eindimensionalen, auf Null basierenden Arrays wird das Array in Bezug auf die Sprache auch wirklich implementiert
IList<T>
. Abschnitt 12.1.2 der C # -Spezifikation sagt dies aus. Unabhängig von der zugrunde liegenden Implementierung muss sich die Sprache so verhalten, als ob die Art derT[]
ImplementierungIList<T>
wie bei jeder anderen Schnittstelle. Aus dieser Perspektive ist die Schnittstelle wird mit einigen der Mitglieder umgesetzt explizit umgesetzt (wieCount
). Das ist die beste Erklärung auf Sprachebene für das, was vor sich geht.Beachten Sie, dass dies nur für eindimensionale Arrays gilt (und nullbasierte Arrays, nicht dass C # als Sprache etwas über nicht nullbasierte Arrays aussagt).
T[,]
nicht implementiertIList<T>
.Aus CLR-Sicht ist etwas Funkigeres los. Sie können die Schnittstellenzuordnung für die generischen Schnittstellentypen nicht abrufen. Beispielsweise:
Gibt eine Ausnahme von:
Warum also die Verrücktheit? Nun, ich glaube, es liegt wirklich an der Array-Kovarianz, die eine Warze im Typsystem IMO ist. Obwohl
IList<T>
es nicht kovariant ist (und nicht sicher sein kann), ermöglicht die Array-Kovarianz Folgendes:... was es wie Geräte aussehen lässt , wenn es nicht wirklich ist.
typeof(string[])
IList<object>
Die CLI-Spezifikation (ECMA-335) Partition 1, Abschnitt 8.7.1, enthält Folgendes:
...
(Es wird nicht wirklich erwähnt
ICollection<W>
oderIEnumerable<W>
was ich für einen Fehler in der Spezifikation halte.)Bei Nichtvarianz geht die CLI-Spezifikation direkt mit der Sprachspezifikation einher. Aus Abschnitt 8.9.1 von Partition 1:
(Ein Vektor ist ein eindimensionales Array mit einer Nullbasis.)
In Bezug auf die Implementierungsdetails führt die CLR eindeutig eine funky Zuordnung durch, um die Zuweisungskompatibilität hier zu gewährleisten: Wenn a
string[]
nach der Implementierung von gefragt wirdICollection<object>.Count
, kann dies nicht ganz normal gehandhabt werden. Zählt dies als explizite Schnittstellenimplementierung? Ich denke, es ist vernünftig, es so zu behandeln, denn wenn Sie nicht direkt nach der Schnittstellenzuordnung fragen, verhält es sich aus sprachlicher Sicht immer so.Was ist mit
ICollection.Count
?Bisher habe ich über die generischen Schnittstellen gesprochen, aber dann gibt es die nicht generischen
ICollection
mit ihrerCount
Eigenschaft. Dieses Mal sind wir können das Interface - Mapping erhalten, und in der Tat ist die Schnittstelle implementiert direkt durchSystem.Array
. Die Dokumentation für dieICollection.Count
Eigenschaftsimplementierung inArray
besagt, dass sie mit expliziter Schnittstellenimplementierung implementiert ist.Wenn sich jemand vorstellen kann, wie sich diese Art der expliziten Schnittstellenimplementierung von der "normalen" expliziten Schnittstellenimplementierung unterscheidet, würde ich mich gerne weiter damit befassen.
Alte Antwort zur expliziten Schnittstellenimplementierung
Trotz des oben Gesagten, das aufgrund der Kenntnis von Arrays komplizierter ist, können Sie durch explizite Schnittstellenimplementierung immer noch etwas mit denselben sichtbaren Effekten tun .
Hier ist ein einfaches eigenständiges Beispiel:
quelle
Count
in Ordnung ist - wird aberAdd
immer geworfen, da Arrays eine feste Größe haben.Nun ja, ähm nein, nicht wirklich. Dies ist die Deklaration für die Array-Klasse im .NET 4-Framework:
Es implementiert System.Collections.IList, nicht System.Collections.Generic.IList <>. Es kann nicht, Array ist nicht generisch. Gleiches gilt für die generischen Schnittstellen IEnumerable <> und ICollection <>.
Die CLR erstellt jedoch im Handumdrehen konkrete Array-Typen, sodass sie technisch einen erstellen kann, der diese Schnittstellen implementiert. Dies ist jedoch nicht der Fall. Versuchen Sie diesen Code zum Beispiel:
Der Aufruf von GetInterfaceMap () schlägt für einen konkreten Array-Typ mit "Schnittstelle nicht gefunden" fehl. Eine Umwandlung in IEnumerable <> funktioniert jedoch problemlos.
Dies ist Quacksalber-wie-eine-Ente-Eingabe. Es ist dieselbe Art der Eingabe, die die Illusion erzeugt, dass jeder Werttyp von ValueType abgeleitet ist, der von Object abgeleitet ist. Sowohl der Compiler als auch die CLR verfügen über spezielle Kenntnisse über Array-Typen, ebenso wie über Werttypen. Der Compiler sieht Ihren Versuch, IList <> zu übertragen, und sagt "Okay, ich weiß, wie das geht!". Und gibt die IL-Anweisung der Castklasse aus. Die CLR hat keine Probleme damit, sie weiß, wie eine Implementierung von IList <> bereitgestellt wird, die auf dem zugrunde liegenden Array-Objekt funktioniert. Es verfügt über integrierte Kenntnisse der ansonsten verborgenen System.SZArrayHelper-Klasse, einem Wrapper, der diese Schnittstellen tatsächlich implementiert.
Die Eigenschaft Count, nach der Sie gefragt haben, sieht folgendermaßen aus:
Ja, diesen Kommentar kann man sicherlich als "Verstoß gegen die Regeln" bezeichnen :) Ansonsten ist er verdammt praktisch. Und sehr gut versteckt, können Sie dies in SSCLI20, der gemeinsam genutzten Quelldistribution für die CLR, überprüfen. Suchen Sie nach "IList", um zu sehen, wo die Typersetzung stattfindet. Der beste Ort, um es in Aktion zu sehen, ist die Methode clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod ().
Diese Art der Ersetzung in der CLR ist im Vergleich zu der Sprachprojektion in der CLR, mit der verwalteter Code für WinRT (auch bekannt als Metro) geschrieben werden kann, recht mild. Nahezu jeder .NET-Kerntyp wird dort ersetzt. IList <> wird beispielsweise IVector <> zugeordnet, einem völlig nicht verwalteten Typ. COM ist selbst eine Substitution und unterstützt keine generischen Typen.
Nun, das war ein Blick darauf, was hinter dem Vorhang passiert. Es kann sehr unangenehm sein, seltsame und unbekannte Meere mit Drachen, die am Ende der Karte leben. Es kann sehr nützlich sein, die Erde flach zu machen und ein anderes Bild davon zu modellieren, was wirklich in verwaltetem Code vor sich geht. Auf diese Weise ist es bequem, es jeder Lieblingsantwort zuzuordnen. Was für Werttypen nicht so gut funktioniert (mutiere keine Struktur!), Aber diese ist sehr gut versteckt. Der GetInterfaceMap () -Methodenfehler ist das einzige Leck in der Abstraktion, an das ich denken kann.
quelle
Array
Klasse, die nicht der Typ eines Arrays ist. Es ist der Basistyp für ein Array. Ein eindimensionales Array in C # wird implementiertIList<T>
. Und ein nicht generischer Typ kann sicherlich sowieso eine generische Schnittstelle implementieren ... was funktioniert, weil es viele verschiedene Typen gibt -typeof(int[])
! = Typeof (string []), so
typeof (int []) `implementiertIList<int>
undtypeof(string[])
implementiertIList<string>
.Array
(die, wie Sie zeigen, eine abstrakte Klasse ist, also möglicherweise nicht der tatsächliche Typ eines Array-Objekts sein kann) als auch die Schlussfolgerung (die sie nicht implementiertIList<T>
) eine falsche IMO sind. Die ArtIList<T>
und Weise, wie es implementiert wird, ist ungewöhnlich und interessant, da stimme ich zu - aber das ist nur ein Implementierungsdetail . Zu behaupten, dassT[]
dies nicht implementiert wird,IList<T>
ist eine irreführende IMO. Es widerspricht der Spezifikation und allen beobachteten Verhaltensweisen.IList<T>
weilArray
dies nicht der Fall ist? Diese Logik ist ein großer Teil dessen, womit ich nicht einverstanden bin. Darüber hinaus müssten wir uns auf eine Definition einigen, was es für einen Typ bedeutet, eine Schnittstelle zu implementieren: Meiner Meinung nach zeigen Array-Typen alle beobachtbaren Merkmale von Typen an, dieIList<T>
außer implementierenGetInterfaceMapping
. Auch hier ist es für mich weniger wichtig, wie dies erreicht wird, genauso wie ich sagen kann, dass diesSystem.String
unveränderlich ist, obwohl die Implementierungsdetails unterschiedlich sind.IList<T>
um zu arbeiten.IList<T>.Count
wird explizit implementiert :Dies geschieht so, dass Sie bei einer einfachen Array-Variablen nicht beide
Count
und habenLength
direkt verfügbar haben.Im Allgemeinen wird die explizite Schnittstellenimplementierung verwendet, wenn Sie sicherstellen möchten, dass ein Typ auf eine bestimmte Art und Weise verwendet werden kann, ohne dass alle Benutzer des Typs gezwungen sind, auf diese Weise darüber nachzudenken.
Edit : Hoppla, schlechter Rückruf da.
ICollection.Count
wird explizit implementiert. Das GenerikumIList<T>
wird wie unten beschrieben behandelt .quelle
string
).ICollection
erklärtCount
, und es wäre auch seine mehr verwirrend , wenn ein Typ mit dem Wort „Sammlung“ in es nicht verwendenCount
:). Es gibt immer Kompromisse bei diesen Entscheidungen.IList<T>
, obwohl sowohl Sprach- als auch CLI-Spezifikationen im Gegenteil zu sein scheinen. Ich wage zu sagen, dass die Art und Weise, wie die Schnittstellenimplementierung unter dem Deckmantel funktioniert, kompliziert sein kann, aber das ist in vielen Situationen der Fall. Würden Sie auch jemanden ablehnen, der sagt, dass diesSystem.String
unveränderlich ist, nur weil die internen Abläufe veränderlich sind? Für alle praktischen Zwecke - und sicherlich für die C # -Sprache - ist dies explizit implizit.Explizite Schnittstellenimplementierung . Kurz gesagt, Sie erklären es wie
void IControl.Paint() { }
oderint IList<T>.Count { get { return 0; } }
.quelle
Es ist nicht anders als eine explizite Schnittstellenimplementierung von IList. Nur weil Sie die Schnittstelle implementieren, müssen ihre Mitglieder nicht als Klassenmitglieder angezeigt werden. Es tut der Count - Eigenschaft implementieren, es aussetzt einfach nicht auf X [].
quelle
Mit verfügbaren Referenzquellen:
Speziell dieser Teil:
(Hervorhebung von mir)
Quelle (nach oben scrollen).
quelle