为什么Array类没有直接暴露其索引器?

要回答的事情:

  1. 不要担心方差 ,而有问题的项目是Array而不是T[]

  2. 多维数组的类似情况是[ 这里 ]

也就是说,N-dims线性变换总是可行的。 因此,这个问题引起了我的注意,因为它已经为线性索引器实现了IList


题:

在我的代码中,我有以下声明:

 public static Array ToArray(this T source); 

我的代码知道如何使souce呈现一个数组(在运行时)。 我试图让消费代码直接访问其索引器。 但如果没有“作为IList”,它就不可能做到。 返回object[]可能需要额外的转换/转换,这就是我要阻止做的事情。 我能做的是:

 public static IList ToArray(this T source); 

但我认为名为ToArray的方法返回一个看起来很奇怪的IList

因此,我对此感到困惑:

Array的声明中,有

 object IList.this[int index]; 

这样我们就可以

 Array a; a=Array.CreateInstance(typeof(char), 1); (a as IList)[0]='a'; 

但我们做不到

 a[0]='a'; 

除非它被宣布为

 public object this[int index]; 

我能看到的唯一区别是它需要我们通过实现它的接口IList显式地使用它的索引器,但为什么呢? 有好处吗? 或者是否存在暴露问题?

Array不能有索引器,因为它需要能够表示具有任意数量维度的数组。 二维数组的索引器具有与一维数组不同的签名。

如果提供索引器并在表示二维数组的Array上使用,应该发生什么?

语言设计者选择的解决方案就是根本不包括索引器。

如果您知道您的ToArray方法将始终返回一维数组,那么请考虑使用:

 public static T[] ToArray(this T source); 

那将有一个索引器。

如果数组中的元素不是全部都是T类型,那么您可以返回一个object[]

 public static object[] ToArray(this T source); 

a as IList (基本上)是铸造。 所以先把它抛出来:

 char[] a = (char[])Array.CreateInstance(typeof(char), 1); a[0] = 'a'; 

编辑 :原因是:因为Array的接口根本没有定义索引器。 它使用SetValue(Object, Int32)Object GetValue(Int32) 。 注意那里不祥的ObjectArray不是特定于类型的; 它是为最小公分母而构建的: Object 。 它可以很容易地定义一个索引器,但实际上你仍然有un / boxing问题。

我认为Array没有直接实现该索引器的一个原因是因为所有特定的数组类型(如char[] )都派生自Array

这意味着像这样的代码是合法的:

 char[] array = new char[10]; array[0] = new object(); 

像这样的代码不应该是合法的,因为它不是类型安全的。 以下是合法的并抛出exception:

 char[] array = new char[10]; array.SetValue(new object(), 0); 

但是通常不使用SetValue() ,所以这不是一个大问题。

Array类中的IList方法(包括其索引器)的问题是它们的显式实现在运行时被添加到类的Array对象中 :

从.NET Framework 2.0开始, Array类实现System.Collections.Generic.IListSystem.Collections.Generic.ICollectionSystem.Collections.Generic.IEnumerablegenerics接口。 这些实现在运行时提供给数组,因此文档构建工具不可见。 因此,通用接口不会出现在Array类的声明语法中,并且没有可通过将数组转换为通用接口类型(显式接口实现)来访问的接口成员的参考主题。

当类显式实现接口时,访问接口方法需要强制转换 :

实现接口的类可以显式实现该接口的成员。 当显式实现成员时,不能通过类实例访问它,而只能通过接口的实例访问它。

提供“常规”(而不是“显式”)接口实现的问题是Array类不是通用的:没有类型参数,你不能写

 class Array : IList 

仅仅因为T未定义。 环境不能将接口实现打到Array类上,直到T参数的类型变为已知,这可能仅在运行时发生:

 // The type of [T] is char Array a = Array.CreateInstance(typeof(char), 1); // The type of [T] is int Array b = Array.CreateInstance(typeof(int), 1); // The type of [T] is string Array c = Array.CreateInstance(typeof(string), 1); 

同时, abc的静态类型保持不变 – 它是System.Array 。 但是,在运行时a将实现IListb将实现IListcIList 。 它们在编译时都不知道,阻止编译器“看到” IList的索引器和其他方法。

此外,并非所有Array实例都实现IList – 只有具有单个维度的数组执行 :

 Array x = new int[5]; Console.WriteLine(x.GetType().GetInterface("IList`1") != null); // True Array y = new int[5,5]; Console.WriteLine(y.GetType().GetInterface("IList`1") != null); // False 

以上所有内容都阻止编译器在没有显式强制转换的情况下访问IList方法,包括索引器。

简短回答

System.Array是ND数组的基类(不仅仅是1-D),这就是为什么1-D索引器(对象this [i] {get; set;})不能成为基本成员的原因。

答案很长

如果你说创建二维数组并尝试访问它的IList索引器:

 Array a; a=Array.CreateInstance(typeof(char), 1,1); (a as IList)[0]='a'; 

你将得到不支持的例外。

好问题是:

为什么System.Array实现IListIEnumerable而其大部分实现将为非1-D数组抛出NotSupportedException

还有一件有趣的事情需要提及。 从技术上讲,非数组在内部具有经典含义的类索引器。 Indexer的经典含义是属性“Item”+ get(+ set)方法。 如果你深入到reflection,你会看到typeof(string [])没有indexer属性,它只有2个方法GetSet – 在string []类中声明的那些方法(不在基类中,与Array.SetValue不同, Array.GetValue)并且它们用于编译时索引。

即使数组都是1D,你仍然会有协方差和逆变问题:

如果基类有一个

 public Object this[int index] { get; set; } 

indexer属性,然后是具体类型的索引器属性

 public TValue this[int index] { get; set; } 

会与基类型的碰撞(因为参数是setter的参数是相同的,但返回值不是)。

将基类转换为基本接口或IList或IList之类的通用接口解决了这个问题,因为可以显式实现非特定索引器。 这是一样的

 Add(Object value) 

 Add(TValue value) 

方法。

理论上,通过定义1D索引和nD索引之间的转换(例如[n] = [n / length(0),n%length(0)])可以克服多维问题,因为nD矩阵存储为一个连续的缓冲。

从技术上讲,有两种类型的arrays。 矢量类型或矩阵类型。 运行时将Vector类型称为Sz_Array ,它们是您声明1d数组*时获得的类型。 我不知道为什么。 矩阵类型表示多维数组。 不幸的是,它们都inheritance自Array而没有其他中间类型。

为什么Array类没有直接暴露其索引器?

当你将它作为T[]时,你只能访问1d数组的索引器的原因是因为1d数组的索引器是在运行时通过IL操作码实现的。

例如

 static T GetFirst(T[] a) { return a[0]: } 

转换为以下il ::

 L_0000: ldarg.0 L_0001: ldc.i4.0 L_0002: ldelem.any !!T L_0007: ret 

在哪里作为以下C#

 private static T GetFirst(IList a) { return a[0]; } 

翻译成这个IL。

 L_0000: ldarg.0 L_0001: ldc.i4.0 L_0002: callvirt instance !0 [mscorlib]System.Collections.Generic.IList`1::get_Item(int32) L_0007: ret 

所以我们可以看到一个是使用操作码ldelem.any ,另一个是callvirt一个方法。

运行时在运行时为数组注入IList,IEnumerable 。 它们在MSIL中的逻辑位于类SZArrayHelper

提供实现的运行时还为生成的每个数组创建两个辅助方法,以帮助不支持索引器的语言(如果存在这样的语言)C#不公开它们,但它们是有效的调用方法。 他们是:

 T Get(Int32) void Set(Int32, T) 

这些方法也是根据维度为矩阵类型数组生成的,并且在调用索引器时由C#使用。

但是,除非您实际指定您是一个类型化数组,否则您将无法获得索引器。 由于Array没有索引器方法。 无法使用操作码,因为在编译时您需要知道所讨论的数组是矢量类型和数组的元素类型。

但是Array实现了IList! 那有一个索引器我不能称之为吗?

是的,但是IList方法的实现是显式实现,因此它们只能在转换时在C#中调用,或者在通用约束的约束下调用。 可能,因为对于任何非向量类型的数组,当您调用其任何方法时,它会抛出一个不受支持的exception。 由于它只是有条件地支持运行时的创建者可能希望你把它投射一次,当你知道这是一个1d数组,但我现在不能命名类型。

为什么数组实现IList如果对于任何多维数组,实现抛出不支持?

这可能是1.0以来的错误。 他们现在无法修复它,因为无论出于何种原因,某人可能正在向IList投射多维数组。 在2.0中,他们添加了一些运行时魔法,以便在运行时向向量类类添加实现,这样只有1d数组实现IListIEnumerable

我们可以插入您的下一个问题::

如何在不进行投射的情况下显示索引器? 将方法签名更改为:: ::

 public static T[] ToArray(this T source) 

但你可能会说你的ToArray没有返回T [],它会返回其他内容我该怎么办? 如果您可以明确指定返回类型,那就这样做。 如果它是引用类型,您可以始终滥用数组协方差并将返回类型更改为object[]但是您将受到ArrayTypeMismatchException的支配。 如果你得到一个值类型,因为该转换是非法的,这将不起作用。 此时你可以只返回IList但是你正在装箱元素,你仍然受到ArrayTypeMismatchException的支配。 Array是所有数组类型的基类,原因在于它有助手方法来帮助您访问GetValueSetValue等内容,并且您会注意到它们具有带索引数组的重载,因此您可以访问Nd中的元素以及1d数组。 例如

 IList myArray = ToArray(myValues); // myArray is actually a string array. myArray[0]='a'; // blows up here as you can't explicitly cast a char to a string. 

所以缺点是你不知道明确的类型。 而实现IListArray每个inheritance者,即使它没有多大意义,也是一个实现细节 ,它们自1.0以来就无法改变。

  • 从技术上讲,这不是100%真实。 您可以创建只有1维的矩阵类型数组。 这个eldritch憎恶可以在IL中创建,或者你可以使用typeof(int).MakeArrayType(1)创建类型typeof(int).MakeArrayType(1)注意你现在有一个System.Int32[*]而不是System.Int32[]

来自msdn:

Array类是支持数组的语言实现的基类。 但是,只有系统和编译器可以从Array类中显式派生。 用户应该使用该语言提供的数组结构。

如果它为您提供索引器,则与Array类的初始意图相矛盾。 您应该使用编译器实现。

再次来自msdn:

要点:从.NET Framework 2.0开始,Array类实现System.Collections.Generic.IList,System.Collections.Generic.ICollection和System.Collections.Generic.IEnumerable通用接口。 这些实现在运行时提供给数组,因此文档构建工具不可见。 因此,通用接口不会出现在Array类的声明语法中,并且没有可通过将数组转换为通用接口类型(显式接口实现)来访问的接口成员的参考主题。 将数组转换为其中一个接口时要注意的关键是添加,插入或删除元素的成员会抛出NotSupportedException。

我认为这是事后的想法。

C#规范“12.1.1 System.Array类型”说,“注意System.Array本身不是一个数组类型”

因为它不是数组类型。

请注意“6.1.6隐式引用转换”说,“从一维数组类型S []到System.Collections.Generic.IList及其基接口,前提是存在从S到S的隐式标识或引用转换T”

C#规范: http : //www.microsoft.com/en-us/download/details.aspx?id = 7029

关于索引器访问为何如此神秘,请查看其他SOpost: 隐式vs显式接口实现

希望能帮助到你。