2015-08-05 15 views
6

Ich lese ein Buch "CLR über C# Fourth Edition". Und ich kann nicht verstehen, eine Aussage:Auto implementiert Schnittstellen in Arrays

So zum Beispiel, wenn Sie die folgende Codezeile haben:

FileStream[] fsArray; 

dann, wenn die CLR erstellt der FileStream[] Typ, wird es verursacht diese Art zu automatisch implementieren die IEnumerable<FileStream>, ICollection<FileStream> und IList<FileStream> Schnittstellen. Darüber hinaus implementieren die FileStream[] Typ werden auch die Schnittstellen für die Basistypen: IEnumerable<Stream>, IEnumerable<Object>, ICollection<Stream>, ICollection<Object>, IList<Stream> und IList<Object>.

Getestet habe ich diese Aussage mit diesem Code:

FileStream[] fsArray = new FileStream[0]; 

string s = null; 
foreach (var m in fsArray.GetType().GetInterfaces()) 
    s += m.ToString() + Environment.NewLine; 

und als Ergebnis, ich habe dies:

System.ICloneable 
System.Collections.IList 
System.Collections.ICollection 
System.Collections.IEnumerable 
System.Collections.IStructuralComparable 
System.Collections.IStructuralEquatable 
System.Collections.Generic.IList`1[System.IO.FileStream] 
System.Collections.Generic.ICollection`1[System.IO.FileStream] 
System.Collections.Generic.IEnumerable`1[System.IO.FileStream] 
System.Collections.Generic.IReadOnlyList`1[System.IO.FileStream] 
System.Collections.Generic.IReadOnlyCollection`1[System.IO.FileStream] 

Es gibt keine Implementierung von IEnumerable<Stream> und andere! Habe ich irgendwo einen Fehler gemacht? Oder hat Jeffrey Richter einen Fehler gemacht?

Außerdem denke ich, dass es gemein ist. Weil Arrays Kovarianz unterstützen.

+0

Sie haben 'System.Collections.Generic.IList'1 [System.IO.FileStream]' –

+0

@NikhilAgrawal 'IList ' ist im Buchtext; der Buchtext fährt fort zu behaupten, dass "Schnittstellen für die Basistypen ..."'IEnumerable ' "sollte auch vorhanden sein, die sie anscheinend nicht sind – AakashM

+0

Riecht inkorrekt. Senden Errata, Whydoncha https://www.microsoftpressstore.com/contact-us/errata – Will

Antwort

3

Es gibt keine Implementierung von IEnumerable und anderen!

Nein. Und doch, IList<Stream> streamList = fsArray; wird funktionieren. Und Sie können streamList wie erwartet verwenden, allerdings mit Laufzeitausnahmen, wenn Sie versuchen, etwas zu tun, das für ein Array nicht gültig ist (solange das Array nullbasiert ist und eine einzige Dimension hat - "SZ-Arrays" im Microsoft-Sprachgebrauch) sonst ist es nicht erlaubt).

Möchten Sie etwas Schlimmeres sehen?

var listMap = typeof(List<FileStream>).GetInterfaceMap(typeof(IList<FileStream>)); // This works fine. 
var arrMap = typeof(typeof(FileStream[]).GetInterfaceMap(typeof(IList<FileStream>)); // This throws `ArgumentException` 

So in dieser Hinsicht nicht FileStream[] nicht einmal implementieren IList<FileStream>; Wenn ja, sollte die obige Zeile funktionieren.

Wir erhalten einen interessanten Hinweis ab .NET 4.0. Zuvor hätte die ArgumentException eine Nachricht von "Interface not found", die gleiche wie wenn wir versucht hätten, diese Schnittstelle auf int oder string[] zu bekommen. Nun ist es "Interface maps for generic interfaces on arrays cannot be retrived." [sic]

Und es uns auch diese geben, wenn wir versuchen, die Schnittstelle Karte für IList<Stream> zu bekommen, aber nicht für eine völlig nicht unterstützte Schnittstelle wie IList<bool>.

Etwas Ungewöhnliches passiert hier.

Und was es ist, ist, dass FileStream[] keine generische Schnittstelle überhaupt unterstützt, in der gleichen Weise, die eine class oder struct würde.

Stattdessen gibt es eine Stub-Klasse namens SZArrayHelper, die diese Schnittstellen zur Laufzeit für nullbasierte Eindimensions-Arrays bereitstellt. Der Kommentar auf dem .NET Core Version ist informativ:

//---------------------------------------------------------------------------------------- 
// ! READ THIS BEFORE YOU WORK ON THIS CLASS. 
// 
// The methods on this class must be written VERY carefully to avoid introducing security holes. 
// That's because they are invoked with special "this"! The "this" object 
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[] 
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will 
// see a lot of expressions that cast "this" "T[]". 
// 
// This class is needed to allow an SZ array of type T[] to expose IList<T>, 
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is 
// made: 
// 
// ((IList<T>) (new U[n])).SomeIListMethod() 
// 
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper, 
// finds the corresponding generic method (matched simply by method name), instantiates 
// it for type <T> and executes it. 
// 
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be 
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly 
// "T[]" - for orefs, it may be a "U[]" where U derives from T.) 
//---------------------------------------------------------------------------------------- 

Und das ist, was passiert. Wenn Sie versuchen, fsArray zu IList<Stream> zu übertragen, erhalten Sie diese Klasse, die die Aufrufe für Sie ausführt. Wenn Sie GetInterfaces() aufrufen, erhalten Sie einen ähnlichen Stub-Code, der nur die für den Array-Typ relevanten Informationen enthält. In jedem Fall fsArraytut implementieren alle Schnittstellen in dem Buch genannten Zitat, aber es tut es nicht in der gleichen Weise wie eine class oder struct kann.

(für Analogie prüfen, wie ein int beide vier Bytes des Wertes 32-Bit sein kann, und auch ein „full“ Objekt mit Interface-Implementierungen, die Methode überschreibt, etc.)

Also das Buch ist richtig, aber Sie verpassen auch nichts, weil einige der Dinge, die wir erwarten würden, wenn ein Typ eine Schnittstelle implementiert, dies nicht tun.

Darüber hinaus denke ich, dass es gemein ist. Weil Arrays Kovarianz unterstützen.

Unterstützung Co-Varianz bedeutet jedoch nicht, dass sie eine bestimmte Schnittstelle implementieren werden, oder umgekehrt. Vor allem, da sich die Kovarianz der Arrays (wohl gebrochen) sehr von der in Interfaces unterscheidet und älter ist, und Arrays mit implizierten generischen Interfaces auch vor der Interface-Kovarianz existieren.

jedoch, dass es wurde beschlossen, dass FileStream[] tatsächlich Stream[] implementieren sollten bezieht covariant (die Entscheidung nur bizarr falsch gewesen sonst hätte) auf Arrays zu sein, aber es muss die zusätzliche Hilfe, die SZArrayHelper bietet, anstatt automatisch zur Folge wird es.

1

Da Arrays Kovarianz unterstützen.

Es ist weil Arrays covariant sind, dass sie auch die generischen Schnittstellen der Elementbasisklassen implementieren müssen. Mit anderen Worten, erwarten alle arbeiten diese:

var a = new FileStream[] { new FileStream("a", FileMode.Create) }; 
    Stream[] b = a;     // Fine, covariant 
    var iterb = (IList<Stream>)b; // Fine of course, actually iterates FileStreams 

Die Zuordnung zu dem Strom [] Objektverweis nicht in irgendeiner Weise das Objekt nicht ändern, ist es noch ein Filestream [] unter der Haube ist. So harte Anforderung hier ist, dass FileStream [] auch IList<Stream> implementiert. Und IList<Object>. Und IEnumerable<Stream>, und so weiter.

Also was Sie wirklich entdeckt, ist, dass Reflection Array-Kovarianz nicht perfekt emuliert. Für die es vergeben werden kann. Arrays nicht eigentlich implementieren diese Schnittstellen, die CLR weiß nur, wie ein Ersatzobjekt, das das gewünschte Verhalten hat. Quacksalberei wie eine Ente. Mehr über dieses Verhalten in this Q+A.

Verwandte Themen