数组中自动实现的接口
Auto implemented interfaces in Arrays
我读了一本书"CLR via C# Fourth Edition"。我无法理解一种说法:
So, for example, if you have the following line of code:
FileStream[] fsArray;
then when the CLR creates the FileStream[]
type, it will
cause this type to automatically implement the
IEnumerable<FileStream>
, ICollection<FileStream>
, and
IList<FileStream>
interfaces. Furthermore, the FileStream[]
type
will also implement the interfaces for the base types:
IEnumerable<Stream>
, IEnumerable<Object>
,
ICollection<Stream>
, ICollection<Object>
,
IList<Stream>
, and 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>
和其他的没有实现!
我在某处弄错了吗?还是 Jeffrey Richter 弄错了?
此外,我认为它是无意义的。因为数组支持协方差。
There is no implementation of IEnumerable and others!
没有。然而,IList<Stream> streamList = fsArray;
会起作用。您可以按预期使用 streamList
,但如果您尝试对数组执行无效的操作(只要数组是从零开始且具有单一维度 -"SZ arrays" 用微软的话来说——否则是不允许的)。
想看更糟的东西吗?
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"
的消息,这与我们尝试在 int
或 string[]
上获取该接口时的情况相同。现在是 "Interface maps for generic interfaces on arrays cannot be retrived."
[原文如此]
如果我们尝试获取 IList<Stream>
的接口映射而不是 IList<bool>
.
等完全不受支持的接口,它也会给我们这个
这里发生了一些不寻常的事情。
实际上,FileStream[]
根本不直接支持任何通用接口,就像 class
或 struct
一样。
取而代之的是一个名为 SZArrayHelper
的存根 class,它在运行时为从零开始的一维数组提供这些接口。 .NET Core Version 上的评论内容丰富:
//----------------------------------------------------------------------------------------
// ! 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>
,那么您会得到这个 class 来为您执行调用。如果你调用 GetInterfaces()
你会得到类似的存根代码,只提供与数组类型相关的代码。在任何一种情况下,fsArray
确实 实现了您引用的书中提到的所有接口,但它的实现方式与 class
或 struct
可以。
(类比一下 int
如何既可以是 32 位值的四个字节又可以是具有接口实现、方法覆盖等的 "full" 对象)
所以这本书是正确的,但您也没有遗漏任何东西,因为当类型实现接口时我们期望发生的一些事情并没有发生。
Furthermore, I think it is mean-less. Because Arrays support co-variance.
支持协方差并不意味着他们将实现给定的接口,反之亦然。特别是因为数组的(可以说是破坏的)协变与接口中的协变非常不同,并且早于它,而且实际上让数组实现通用接口也早于接口协变。
然而,决定 FileStream[]
确实应该实现 Stream[]
确实与数组协变有关(否则该决定将是非常错误的),但它需要额外的帮助SZArrayHelper
提供,而不是被它自动包含。
Because Arrays support co-variance.
因为 数组是协变的,所以它们还必须实现元素基 类 的通用接口。换句话说,每个人都希望它能起作用:
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 只知道如何提供具有所需行为的替代对象。像鸭子一样嘎嘎打字。 this Q+A.
中有关此行为的更多信息
我读了一本书"CLR via C# Fourth Edition"。我无法理解一种说法:
So, for example, if you have the following line of code:
FileStream[] fsArray;
then when the CLR creates the
FileStream[]
type, it will cause this type to automatically implement theIEnumerable<FileStream>
,ICollection<FileStream>
, andIList<FileStream>
interfaces. Furthermore, theFileStream[]
type will also implement the interfaces for the base types:IEnumerable<Stream>
,IEnumerable<Object>
,ICollection<Stream>
,ICollection<Object>
,IList<Stream>
, andIList<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>
和其他的没有实现!
我在某处弄错了吗?还是 Jeffrey Richter 弄错了?
此外,我认为它是无意义的。因为数组支持协方差。
There is no implementation of IEnumerable and others!
没有。然而,IList<Stream> streamList = fsArray;
会起作用。您可以按预期使用 streamList
,但如果您尝试对数组执行无效的操作(只要数组是从零开始且具有单一维度 -"SZ arrays" 用微软的话来说——否则是不允许的)。
想看更糟的东西吗?
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"
的消息,这与我们尝试在 int
或 string[]
上获取该接口时的情况相同。现在是 "Interface maps for generic interfaces on arrays cannot be retrived."
[原文如此]
如果我们尝试获取 IList<Stream>
的接口映射而不是 IList<bool>
.
这里发生了一些不寻常的事情。
实际上,FileStream[]
根本不直接支持任何通用接口,就像 class
或 struct
一样。
取而代之的是一个名为 SZArrayHelper
的存根 class,它在运行时为从零开始的一维数组提供这些接口。 .NET Core Version 上的评论内容丰富:
//----------------------------------------------------------------------------------------
// ! 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>
,那么您会得到这个 class 来为您执行调用。如果你调用 GetInterfaces()
你会得到类似的存根代码,只提供与数组类型相关的代码。在任何一种情况下,fsArray
确实 实现了您引用的书中提到的所有接口,但它的实现方式与 class
或 struct
可以。
(类比一下 int
如何既可以是 32 位值的四个字节又可以是具有接口实现、方法覆盖等的 "full" 对象)
所以这本书是正确的,但您也没有遗漏任何东西,因为当类型实现接口时我们期望发生的一些事情并没有发生。
Furthermore, I think it is mean-less. Because Arrays support co-variance.
支持协方差并不意味着他们将实现给定的接口,反之亦然。特别是因为数组的(可以说是破坏的)协变与接口中的协变非常不同,并且早于它,而且实际上让数组实现通用接口也早于接口协变。
然而,决定 FileStream[]
确实应该实现 Stream[]
确实与数组协变有关(否则该决定将是非常错误的),但它需要额外的帮助SZArrayHelper
提供,而不是被它自动包含。
Because Arrays support co-variance.
因为 数组是协变的,所以它们还必须实现元素基 类 的通用接口。换句话说,每个人都希望它能起作用:
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 只知道如何提供具有所需行为的替代对象。像鸭子一样嘎嘎打字。 this Q+A.
中有关此行为的更多信息