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是基于零的秩-1arraysV[] ,并且UIList<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直接实现的。 ArrayICollection.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时,你不需要直接使用CountLength

一般来说,当你想确保一个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并执行它。

(强调我的)

来源 (向上滚动)。