数组中自动实现的接口

本文关键字:接口 实现 数组 | 更新日期: 2023-09-27 18:06:25

我读了一本书"CLR via C# Fourth Edition"。我无法理解一种说法:

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

FileStream[] fsArray;

然后当 CLR 创建 FileStream[] 类型时,它将 导致此类型自动实现 IEnumerable<FileStream>ICollection<FileStream>IList<FileStream>接口。此外,FileStream[]型 还将实现基类型的接口: IEnumerable<Stream>IEnumerable<Object>ICollection<Stream>ICollection<Object>IList<Stream>IList<Object>

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

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<Stream>和其他!我在某处犯了错误吗?还是杰弗里·里希特犯了错误?

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

数组中自动实现的接口

没有 IEnumerable 和其他的实现!

不。然而,IList<Stream> streamList = fsArray;会起作用。您可以按预期使用streamList,但如果您尝试在数组上执行无效的操作,则运行时会例外(只要数组从零开始并且具有单个维度 - Microsoft的说法是"SZ 数组" - 否则是不允许的(。

想看到更糟糕的东西吗?

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

所以在这方面,FileStream[]甚至没有实现IList<FileStream>;如果它实现了,那么上面的行肯定应该有效。

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

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

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

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

取而代之的是一个名为 SZArrayHelper 的存根类,它在运行时为从零开始的一维数组提供这些接口。关于.NET Core版本的注释内容丰富:

//----------------------------------------------------------------------------------------
// ! 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.)
//----------------------------------------------------------------------------------------

这就是发生的事情。如果您尝试将fsArray投射到IList<Stream>那么您将获得该类为您做调用。如果调用GetInterfaces()则会得到类似的存根代码,仅提供与数组类型相关的代码。无论哪种情况,fsArray实现了你引用的书中提到的所有接口,但它不像classstruct那样

实现。

(考虑类比,int既可以是 32 位值的四个字节,也可以是具有接口实现、方法覆盖等的"完整"对象。

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

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

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

然而,决定FileStream[]确实应该实现Stream[]确实与数组协变有关(否则这个决定会是奇怪的错误(,但它需要SZArrayHelper提供的额外帮助,而不是自动由它提供。

因为数组支持协方差。

这是因为数组是协变的,所以它们还必须实现元素基类的泛型接口。 换句话说,每个人都希望这能奏效:

    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
对 Stream[

] 对象引用的赋值不会以任何方式改变对象,它仍然是底层的 FileStream[]。 所以这里的硬要求是FileStream[]也实现了IList<Stream>。 和IList<Object>. 和IEnumerable<Stream>,等等。

因此,您真正发现的是反射并不能完美地模拟数组协方差。 为此可以原谅。 数组实际上并不实现这些接口,CLR 只知道如何提供具有所需行为的替代对象。 像鸭子一样嘎嘎叫。 有关此行为的详细信息,请参阅此 Q+A。