C#中的数组如何部分实现IList <T>?
正如你可能知道的那样,C#中的数组实现IList<T>
以及其他接口。 但不知何故,他们这样做没有公开实施IList<T>
的Count属性! 数组只有一个Length属性。
这是C#/。NET打破它自己的接口实现规则的公然例子,或者我错过了什么吗?
根据汉斯的回答得出新的答案
由于汉斯给出的答案,我们可以看到实施比我们想象的要复杂一些。 编译器和CLR都非常难以给出这样的印象:数组types实现了IList<T>
– 但是数组差异使得这个技巧更加棘手。 与汉斯的答案相反,数组types(一维,基于零)直接实现generics集合,因为任何特定数组的types不是 System.Array
– 这只是数组的基types。 如果你问一个数组types,它支持什么接口,它包括genericstypes:
foreach (var type in typeof(int[]).GetInterfaces()) { Console.WriteLine(type); }
输出:
System.ICloneable System.Collections.IList System.Collections.ICollection System.Collections.IEnumerable System.Collections.IStructuralComparable System.Collections.IStructuralEquatable System.Collections.Generic.IList`1[System.Int32] System.Collections.Generic.ICollection`1[System.Int32] System.Collections.Generic.IEnumerable`1[System.Int32]
对于单维的,基于零的数组,就语言而言,数组确实也实现了IList<T>
。 C#规范的第12.1.2节是这样说的。 因此,无论底层实现如何,语言都必须像T[]
types实现IList<T>
那样使用任何其他接口。 从这个angular度来看,接口是通过一些明确实现的成员来实现的(比如Count
)。 在语言层面上,这是最好的解释。
请注意,这只适用于一维数组(和基于零的数组,而不是C#作为一种语言说非基于零的数组的任何事情)。 T[,]
不执行IList<T>
。
从CLR的angular度来看,更有趣的事情正在发生。 您无法获取通用接口types的接口映射。 例如:
typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))
给出一个例外:
Unhandled Exception: System.ArgumentException: Interface maps for generic interfaces on arrays cannot be retrived.
那为什么这个怪事? 那么,我相信这实际上是由于arrays协方差,这是types系统,国际海事组织疣。 即使IList<T>
不是协变的(也不能安全地),数组协变允许这个工作:
string[] strings = { "a", "b", "c" }; IList<object> objects = strings;
…它使得它看起来像typeof(string[])
实现IList<object>
,当它不真正。
CLI规范(ECMA-335)分区1的8.7.1节有这样的内容:
签名typesT与签名typesU兼容,当且仅当以下至less一项成立
…
T是基于零的秩-1arrays
V[]
,并且U
是IList<W>
,并且V与W是arrays元素兼容的。
(它实际上并没有提到ICollection<W>
或IEnumerable<W>
,我认为这是规范中的一个错误。)
对于非变异,CLI规范直接与语言规范一起使用。 从分区1的第8.9.1节:
此外,创build的元素types为T的vector实现了接口
System.Collections.Generic.IList<U>
,其中U:= T(§8.7)
(一个向量是一个零基数的一维数组。
现在就实现细节而言 ,显然CLR正在做一些时髦的映射来保持赋值的兼容性:当一个string[]
被要求实现ICollection<object>.Count
,它不能很好地处理正常的方式。 这算作显式的接口实现吗? 我认为这样对待是合理的,因为除非直接询问接口映射,否则从语言的angular度来看,它总是performance出来。
ICollection.Count
呢?
到目前为止,我已经谈到了generics接口,但是它的Count
属性是非genericsICollection
。 这次我们可以得到接口映射,而实际上这个接口是由System.Array
直接实现的。 Array
的ICollection.Count
属性实现的文档声明,它是通过显式接口实现来实现的。
如果有人能想到这种显式接口实现与“正常”显式接口实现不同的方式,我很乐意进一步研究。
围绕显式接口实现的旧答案
尽pipe如此,由于数组的知识,这更复杂,您仍然可以通过显式接口实现来做相同的可见效果。
这是一个简单的独立的例子:
public interface IFoo { void M1(); void M2(); } public class Foo : IFoo { // Explicit interface implementation void IFoo.M1() {} // Implicit interface implementation public void M2() {} } class Test { static void Main() { Foo foo = new Foo(); foo.M1(); // Compile-time failure foo.M2(); // Fine IFoo ifoo = foo; ifoo.M1(); // Fine ifoo.M2(); // Fine } }
正如你可能知道的那样,C#中的数组实现
IList<T>
以及其他接口
那么,是的,呃不,不是。 这是.NET 4框架中的Array类的声明:
[Serializable, ComVisible(true)] public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable { // etc.. }
它实现System.Collections.IList, 而不是 System.Collections.Generic.IList <>。 它不能,数组不是通用的。 通用的IEnumerable <>和ICollection <>接口也是如此。
但是CLR会dynamic创build具体的数组types,所以它可以在技术上创build一个实现这些接口的types。 但情况并非如此。 试试这个代码例如:
using System; using System.Collections.Generic; class Program { static void Main(string[] args) { var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable<int>)); var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable<int>)); // Kaboom } } abstract class Base { } class Derived : Base, IEnumerable<int> { public IEnumerator<int> GetEnumerator() { return null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }
GetInterfaceMap()调用失败的具体数组types与“接口未find”。 然而,对IEnumerable <>的转换没有问题。
这就像一个鸭子打字。 这是一种types的打字造成的错觉,每个值types派生自Object的ValueType派生。 编译器和CLR都具有数组types的特殊知识,就像它们的值types一样。 编译器看到你试图投射到IList <>并说“好吧,我知道该怎么做!”。 并发出castclass IL指令。 CLR没有任何问题,它知道如何提供对底层数组对象工作的IList <>的实现。 它内置了隐藏的System.SZArrayHelper类的知识,这是一个实际实现这些接口的包装器。
这个问题并没有像大家声称的那样明确,你问的Count属性如下所示:
internal int get_Count<T>() { //! Warning: "this" is an array, not an SZArrayHelper. See comments above //! or you may introduce a security hole! T[] _this = JitHelpers.UnsafeCast<T[]>(this); return _this.Length; }
是的,你当然可以把这个评论称为“违反规则”:)否则就会得心应手。 而且隐藏得非常好,你可以在CLR的共享源码分发SSCLI20中查看。 search“IList”以查看typesreplace的发生位置。 看到它的最好的地方是clr / src / vm / array.cpp,GetActualImplementationForArrayGenericIListMethod()方法。
与CLR中的语言投影相比,CLR中的这种replace相当温和,允许为WinRT(又名Metro)编写托pipe代码。 几乎所有的核心.NETtypes都被取代了。 IList <>映射到IVector <>,例如,一个完全不受pipe理的types。 本身是一个替代,COM不支持genericstypes。
那么,这是看幕后发生的事情。 生活在地图尾部的龙可能会感到非常不舒服,陌生和陌生的海洋。 让地球变得平坦并为托pipe代码中真正发生的事情build立一个不同的图像是非常有用的。 映射到大家最喜欢的答案是舒适的那种方式。 这对于值types来说效果不好(不要改变一个结构!),但是这个很好隐藏。 GetInterfaceMap()方法失败是我能想到的抽象中唯一的泄漏。
IList<T>.Count
是明确实现的:
int[] intArray = new int[10]; IList<int> intArrayAsList = (IList<int>)intArray; Debug.Assert(intArrayAsList.Count == 10);
这样做是为了当你有一个简单的数组variables时,你不需要直接使用Count
和Length
。
一般来说,当你想确保一个types可以以特定的方式被使用时,使用显式的接口实现,而不是强迫所有types的消费者这样想。
编辑 :哎呀,不好回忆那里。 ICollection.Count
是明确实施的。 通用IList<T>
的处理方式如下汉斯descibes 。
明确的接口实现 。 总之,你声明它像void IControl.Paint() { }
或int IList<T>.Count { get { return 0; } }
int IList<T>.Count { get { return 0; } }
。
这与IList的显式接口实现没有什么不同。 仅仅因为你实现了接口并不意味着它的成员需要显示为类成员。 它确实实现了Count属性,它只是不暴露在X []上。
参考资料来源:
//---------------------------------------------------------------------------------------- // ! 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[]" (ie for primitivs and valuetypes, it will be exactly // "T[]" - for orefs, it may be a "U[]" where U derives from T.) //---------------------------------------------------------------------------------------- sealed class SZArrayHelper { // It is never legal to instantiate this class. private SZArrayHelper() { Contract.Assert(false, "Hey! How'd I get here?"); } /* ... snip ... */ }
具体这个部分:
接口存根调度器将其视为一种特殊情况 ,加载SZArrayHelper, find相应的generics方法(仅 由方法名称 匹配 ) ,将其实例化为types并执行它。
(强调我的)
来源 (向上滚动)。