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

时间:2013-01-26 02:04:57

标签: c# arrays ilist indexer

要回答的问题:

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

  2. 多维数组的类似案例是[here]

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


    问题:

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

    public static Array ToArray<T>(this T source); 
    

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

    public static IList ToArray<T>(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显式地使用它的索引器,但为什么呢? 有好处吗?或者是否存在问题?

9 个答案:

答案 0 :(得分:20)

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

如果在代表二维数组的Array上提供并使用了索引器会发生什么?

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

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

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

那将有一个索引器。

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

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

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:4)

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

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

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

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

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

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

答案 3 :(得分:3)

IList<T>类中Array类的方法(包括其索引器)的问题在于它们的显式实现是added to Array objects of the class at run time

  

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

当类实现接口显式时,访问接口方法requires a cast

  

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

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

class Array : IList<T>

只是因为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将实施IList<char>b将实施IList<int>c - IList<string>。在编译时都不知道,阻止编译器“看到”{(1}}的索引器和其他方法。

此外,并非IList<T>实施Array的所有实例 - only arrays with a single dimension do

IList<T>

以上所有内容都阻止编译器在没有显式强制转换的情况下访问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 方法,包括索引器。

答案 4 :(得分:2)

简短回答

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)方法。如果你深入反思,你会看到 typeof(string [])没有索引器属性,它只有2种方法获取设置 - 在string []类中声明的那些方法(不在基类中,与Array.SetValue,Array.GetValue不同),它们用于编译时索引。

答案 5 :(得分:2)

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

如果基类有

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

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

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

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

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

与此相同
Add(Object value)

VS

Add(TValue value)

方法

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

答案 6 :(得分:1)

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

  

为什么Array类没有直接公开它的索引器?

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

e.g。

static T GetFirst<T>(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<T>(IList<T> 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<!!T>::get_Item(int32)
L_0007: ret 

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

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

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

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

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

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

  

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

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

  

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

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

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

  

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

public static T[] ToArray<T>(this T source)

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

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.

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

  • 从技术上讲,这不是100%真实。您可以创建仅具有1维的矩阵类型数组。可以在IL中创建此eldritch憎恶,或者您可以使用typeof(int).MakeArrayType(1)来创建类型。请注意您现在如何使用System.Int32[*]代替System.Int32[]

答案 7 :(得分:1)

来自msdn:

  

Array类是语言实现的基类   支持数组。但是,只有系统和编译器才能派生出来   显式来自Array类。用户应该使用该阵列   语言提供的结构。

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

再次来自msdn:

  

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

我认为这是事后的想法。

答案 8 :(得分:0)

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

因为它不是数组类型。

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

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

关于索引器访问为何如此神秘,请查看其他SO帖子: implicit vs explicit interface implementation

希望它有所帮助。