在数组中自动实现接口

我读了一本书“通过C#第四版CLR”。 我无法理解一个陈述:

因此,例如,如果您有以下代码行:

FileStream[] fsArray; 

然后,当CLR创建FileStream[]类型时,它将使此类型自动实现IEnumerableICollectionIList接口。 此外, FileStream[]类型还将实现基类型的接口: IEnumerableIEnumerableICollectionICollectionIListIList

我用这段代码测试了这个语句:

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

结果,我有这个:

 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] 

IEnumerable和其他没有实现! 我在某处弄错了吗? 或杰弗里里希特犯了错误?

此外,我认为这是无意义的。 因为Arrays支持协方差。

IEnumerable和其他没有实现!

不。 然而, IList streamList = fsArray; 将工作。 并且您可以按照预期使用streamList ,但如果您尝试在arrays上执行某些无效操作,则可以使用streamList (只要该arrays从零开始并且具有单个维度 – 在Microsoft的用语中使用“SZ数组” -否则不允许)。

想看到更糟糕的事情?

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

所以在这方面, FileStream[]甚至没有实现IList ; 如果确实如此,那么上述线应该可行。

我们从.NET 4.0中获得了一个有趣的线索。 在此之前, ArgumentException将有一个消息"Interface not found" ,就像我们试图在intstring[]上获取该接口时一样。 现在它是"Interface maps for generic interfaces on arrays cannot be retrived." [原文如此]

如果我们尝试获取IList的接口映射,而不是像IList那样完全不受支持的接口,它也会给我们这个。

这里发生了一些不寻常的事情。

它是什么,是FileStream[]根本不直接支持任何通用接口,就像classstruct一样。

相反,有一个名为SZArrayHelper的存根类,它在运行时为基于零的一维数组提供这些接口。 关于.NET核心版本的评论是提供信息的:

 //---------------------------------------------------------------------------------------- // ! 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, // IList, etc., etc. all the way up to IList. When the following call is // made: // // ((IList) (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  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.) //---------------------------------------------------------------------------------------- 

这就是发生的事情。 如果您尝试将fsArrayIList那么您可以让此类为您执行调用。 如果调用GetInterfaces()则会得到类似的存根代码,只提供与数组类型相关的存根代码。 在任何一种情况下, fsArray实现您引用的书中提到的所有接口,但它不会像classstruct那样执行它。

(考虑类比如何int可以是32位值的四个字节,也可以是具有接口实现,方法覆盖等的“完整”对象)

所以这本书是正确的,但你也没有遗漏任何东西,因为当类型实现接口时我们期望发生的一些事情不会。

此外,我认为这是无意义的。 因为Arrays支持协方差。

支持协方差并不意味着它们将实现给定的接口,反之亦然。 特别是因为数组(可以说是破坏的)协方差与接口中的协方差非常不同,并且早于它,并且实际上具有arrays实现通用接口也早于接口协方差。

但是,确定FileStream[]应该确实实现Stream[]确实与数组是协变的(否则决定本来就是奇怪的错误),但它需要SZArrayHelper提供的额外帮助,而不是自动SZArrayHelper它。

因为Arrays支持协方差。

这是因为数组是协变的,它们还必须实现元素基类的通用接口。 换句话说,每个人都希望这个工作:

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

Stream []对象引用的赋值不以任何方式改变对象,它仍然是引擎盖下的FileStream []。 因此,岩石硬盘要求是FileStream []也实现了IList 。 和IList 。 和IEnumerable ,等等。

所以你真正发现的是,Reflection并不能完美地模拟数组协方差。 为此可以原谅。 数组实际上并没有实现这些接口,CLR只知道如何提供具有所需行为的替代对象。 嘎嘎叫鸭子打字。 更多关于此Q + A中此行为的信息 。