Automatically implemented interfaces in arrays

I read the book "CLR through the fourth edition of C #". And I can not understand one statement:

So, for example, if you have the following line of code:

FileStream[] fsArray; 

then when the CLR creates the type FileStream[] , it will force this type to automatically execute IEnumerable<FileStream> , ICollection<FileStream> and IList<FileStream> . In addition, the FileStream[] type will also implement interfaces for the basic types: IEnumerable<Stream> , IEnumerable<Object> , ICollection<Stream> , ICollection<Object> , IList<Stream> and IList<Object> .

I tested this instruction using this code:

 FileStream[] fsArray = new FileStream[0]; string s = null; foreach (var m in fsArray.GetType().GetInterfaces()) s += m.ToString() + Environment.NewLine; 

and as a result, I have this:

 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] 

There is no implementation of IEnumerable<Stream> and others! Am I wrong somewhere? Or did Jeffrey Richter make a mistake?

In addition, I think it is scarce. Because arrays support joint dispersion.

+6
source share
2 answers

There is no implementation of IEnumerable and others!

Nope. And yet IList<Stream> streamList = fsArray; will work. And you can use streamList , as you would expect, albeit with runtime exceptions, if you try to do something invalid in the array (as long as the array is zero and has one size - "SZ arrays" in Microsoft parlance, otherwise it is not allowed).

Want to see something worse?

 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 this respect, FileStream[] does not even implement IList<FileStream> ; if so, then of course the above line should work.

We get an interesting tip with .NET 4.0. Prior to this, there would be an "Interface not found" message in ArgumentException , the same would be if we tried to get this interface on int or string[] . Now it's "Interface maps for generic interfaces on arrays cannot be retrived." [sic]

And it also gives us this if we try to get an interface map for IList<Stream> , but not for a completely unsupported interface like IList<bool> .

Something unusual is happening here.

And what it is is that FileStream[] does not directly support any common interface, just like class or struct .

Instead, there is a stub class called SZArrayHelper that provides these interfaces at runtime for one-dimensional arrays with a zero value. The comment on the .NET Core Version is informative:

 //---------------------------------------------------------------------------------------- // ! READ THIS BEFORE YOU WORK ON THIS CLASS. // // The methods on this class must be written VERY carefully to avoid introducing security holes. // That 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[]" (ie for primitivs and valuetypes, it will be exactly // "T[]" - for orefs, it may be a "U[]" where U derives from T.) //---------------------------------------------------------------------------------------- 

And what is happening. If you try to apply fsArray to IList<Stream> , then you get this class that makes calls for you. If you call GetInterfaces() , you get a similar stub code that provides only those that are of an array type. In any case, fsArray executes all the interfaces mentioned in the book you are quoting, but does not do it the same way as class or struct .

(Consider, by analogy, how an int can be either four bytes of a 32-bit value, or a "full" object with interface implementations, method overrides, etc.)

So, the book is correct, but you don’t miss anything either, because some of the things that we would expect when a type implements an interface do not.

In addition, I think it is scarce. Because arrays support joint dispersion.

Support for co-dispersion does not mean that they implement this interface or vice versa. Moreover, the covariance of arrays (possibly broken) is very different from that in interfaces and precedes it, and even has arrays that implement common interfaces, also precedes interface covariance.

However, it was decided that FileStream[] really should implement Stream[] , refers to arrays that are covariant (the solution would be just strangely wrong otherwise), but it needs the extra help that SZArrayHelper provides rather than automatically entails his.

+3
source

Since arrays support codimension.

This is because arrays are covariant, that they must also implement the common interfaces of the base element classes. In other words, everyone expects this to work:

  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 

Assigning a reference to an object Stream [] does not modify the object in any way; it is still FileStream [] under the hood. So awesome is the requirement that FileStream [] also implements IList<Stream> . And IList<Object> . And IEnumerable<Stream> , etcetera.

So, you really found that Reflection does not perfectly emulate array covariance. For this you can forgive. Arrays do not actually implement these interfaces, the CLR just knows how to provide a placeholder object that has the desired behavior. Quacks-like-a-duck typing. More on this behavior in this Q + A.

+1
source

All Articles