22

要回答的事情:

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

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

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


问题:

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

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

我的代码知道如何制作souce一个数组(在运行时)。我试图让消费代码直接访问它的索引器。但是没有“as 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实现它的接口显式地使用它的索引器,但为什么呢?有好处吗?还是有暴露的问题?

4

9 回答 9

20

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

如果提供了一个索引器并将其用于Array表示二维数组的数组,会发生什么?

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

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

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

那将有一个索引器。

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

public static object[] ToArray<T>(this T source); 
于 2013-01-30T18:26:04.233 回答
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. 它可以很容易地定义一个索引器,但实际上你仍然会遇到取消/装箱问题。

于 2013-01-26T02:17:15.993 回答
4

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

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

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

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

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

但是SetValue()一般不使用,所以这不是什么大问题。

于 2013-01-26T03:32:19.390 回答
3

The problem with IList<T>'s methods in the Array class, including its indexer, is that their explicit implementations are added to Array objects of the class at run time:

Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic.IList<T>, System.Collections.Generic.ICollection<T>, and System.Collections.Generic.IEnumerable<T> generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations).

When classes implement interfaces explicitly, accessing interface methods requires a cast:

A class that implements an interface can explicitly implement a member of that interface. When a member is explicitly implemented, it cannot be accessed through a class instance, but only through an instance of the interface.

The problem with providing a "regular" (as opposed to an "explicit") interface implementation is the fact that the Array class is not generic: without a type parameter, you cannot write

class Array : IList<T>

simply because T is undefined. The environment cannot slap an interface implementation onto the Array class until the type of the T parameter becomes known, which may happen only at run time:

// 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);

At the same time, the static type of a, b, and c remains the same - it's System.Array. However, at run time a will be implementing IList<char>, b will be implementing IList<int>, and c - IList<string>. None of it is known at compile time, prevents the compiler from "seeing" the indexer and other methods of IList<T>.

Moreover, not all instances of Array implement IList<T> - only arrays with a single dimension do:

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

All of the above prevents the compiler from accessing IList<T> methods, including the indexer, without an explicit cast.

于 2013-02-01T18:02:15.020 回答
2

简短的回答

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

长答案

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

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

您将获得不受支持的异常。

好问题是:

为什么要System.Array实现IListIEnumerable而它的大部分实现都会抛出NotSupportedException非一维数组?

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

于 2013-02-01T00:01:07.443 回答
2

即使数组都是一维的,你仍然会遇到协方差和逆变问题

如果基类有一个

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

indexer 属性,然后是具体类型 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 矩阵存储为一个连续的缓冲。

于 2013-02-03T15:16:16.307 回答
1

From msdn:

The Array class is the base class for language implementations that support arrays. However, only the system and compilers can derive explicitly from the Array class. Users should employ the array constructs provided by the language.

If it provide you an indexer it contradicts with the original intention of Array class. You should use the compiler implementation.

Again from msdn:

Important: Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic.IList, System.Collections.Generic.ICollection, and System.Collections.Generic.IEnumerable generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

It is an afterthought, I presume.

于 2013-02-06T18:07:42.463 回答
1

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

为什么 Array 类不直接公开其索引器?

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

例如

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# 中在强制转换或受泛型约束绑定时调用。可能是因为对于任何非向量类型的数组,当您调用其任何方法时,它都会引发不受支持的异常。由于它唯一有条件地支持运行时的创建者,因此当您知道这是一个一维数组时,可能希望您将其强制转换,但我现在无法命名该类型。

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

这可能是自 1.0 以来就存在的错误。他们现在无法修复它,因为无论出于何种原因,他们都可能将多维数组转换为IList. 在 2.0 中,他们添加了一些运行时魔法,以便在运行时向向量类型类添加实现,以便只有一维数组实现IList<T>IEnumerable<T>.

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

如何在不强制转换的情况下让索引器出现?将方法签名更改为::

public static T[] ToArray<T>(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.

所以短处是你不知道显式类型。Array并且implementation的每个继承者IList,即使它没有多大意义,也是一个他们无法更改的实现细节,因为它自 1.0 以来就在那里。

  • 从技术上讲,这不是 100% 正确的。您可以创建一个只有一维的矩阵类型数组。这个可怕的憎恶可以在 IL 中创建,或者您可以使用typeof(int).MakeArrayType(1)通知您现在如何拥有System.Int32[*]而不是创建类型System.Int32[]
于 2013-02-05T22:39:30.840 回答
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 帖子: 隐式与显式接口实现

希望能帮助到你。

于 2013-02-06T11:56:18.940 回答