为什么不是一个genericstypes?

Array声明如下:

 public abstract class Array : ICloneable, IList, ICollection, IEnumerable { 

我想知道为什么不是这样:

 public partial class Array<T> : ICloneable, IList<T>, ICollection<T>, IEnumerable<T> { 
  1. 如果它被声明为一个通用types,会出现什么问题呢?

  2. 如果是genericstypes,我们还需要非genericstypes吗? 如果我们愿意,它可以从Array<T> (如上所述)派生吗?

     public partial class Array: Array<object> { 

更新:

第二点对于不再使非genericsArray成为基本types的假设也很重要。

历史

如果数组变成generics,会出现什么问题?

回到C#1.0,​​他们主要从Java复制了数组的概念。 generics在当时并不存在,但是创build者认为他们很聪明并且复制了Java数组所具有的破坏的协变数组语义。 这意味着你可以在没有编译时错误的情况下解决这个问题(但是运行时错误):

 Mammoth[] mammoths = new Mammoth[10]; Animal[] animals = mammoths; // Covariant conversion animals[1] = new Giraffe(); // Run-time exception 

在C#2.0generics被引入,但没有协变/逆变的genericstypes。 如果数组是通用的,那么你不能把Mammoth[]放到Animal[]身上,以前可以做的事情(即使它被打破了)。 所以使数组通用会破坏很多代码。

只有在C#4.0中引入了接口的协变/反变types。 这使得可以一劳永逸地修复破坏的arrays协方差。 但是,这又会破坏很多现有的代码。

 Array<Mammoth> mammoths = new Array<Mammoth>(10); Array<Animal> animals = mammoths; // Not allowed. IEnumerable<Animals> animals = mammoths; // Covariant conversion 

数组实现通用接口

为什么不实现通用的IList<T>ICollection<T>IEnumerable<T>接口?

由于运行时间技巧,每个数组T[]实现IEnumerable<T>ICollection<T>IList<T>1Array类文档 :

在.NET Framework 2.0版中,Array类实现了IList<T>ICollection<T>IEnumerable<T>通用接口。 这些实现在运行时提供给数组,因此对于文档构build工具是不可见的。 因此,通用接口不会出现在Array类的声明语法中。


你可以使用数组实现的接口的所有成员?

没有。文件继续这个评论:

将数组转换为这些接口之一时要注意的关键是,添加,插入或移除元素的成员会抛出NotSupportedException

这是因为(例如) ICollection<T>有一个Add方法,但是不能向数组添加任何东西。 它会抛出一个exception。 这是.NET Framework中的一个早期devise错误的另一个例子,它会让您在运行时抛出exception:

 ICollection<Mammoth> collection = new Mammoth[10]; // Cast to interface type collection.Add(new Mammoth()); // Run-time exception 

而且由于ICollection<T>不是协变的(出于显而易见的原因),你不能这样做:

 ICollection<Mammoth> mammoths = new Array<Mammoth>(10); ICollection<Animal> animals = mammoths; // Not allowed 

当然,现在还有协变的IReadOnlyCollection<T>接口 ,它也是由数组1实现的 ,但它只包含Count所以它的用途有限。


基类Array

如果数组是generics的,我们是否仍然需要非generics的Array类?

在早期,我们做到了。 所有数组通过其基类Array实现非通用IListICollectionIEnumerable接口。 这是为所有数组提供特定方法和接口的唯一合理方法,也是Array基类的主要用途。 您可以看到枚举的相同select:它们是值types,但inheritance了Enum成员; 以及从MulticastDelegateinheritance的委托。

现在支持generics的非generics基类Array可以被移除吗?

是的,所有数组共享的方法和接口可以在genericsArray<T>类上定义,如果它存在的话。 然后,您可以写入,例如Copy<T>(T[] source, T[] destination)而不是Copy(Array source, Array destination)

然而,从面向对象编程的angular度来看,拥有一个通用的非generics基类Array是很好的,它可以用来引用任何数组,而不pipe其元素的types如何。 就像IEnumerable<T>如何从IEnumerableinheritance(它仍然在一些LINQ方法中使用)。

Array基类是否可以从Array<object>派生?

不,那会创build一个循环依赖: Array<T> : Array : Array<object> : Array : ... 另外,这意味着你可以在数组中存储任何对象(毕竟,所有的数组最终都将从typesArray<object>inheritance)。


未来

可以添加新的通用数组typesArray<T>而不会影响现有的代码太多?

不可以。虽然可以使语法适合,但是不能使用现有的数组协方差。

数组是.NET中的一种特殊types。 它甚至有自己的通用中级语言的说明。 如果.NET和C#devise人员决定走这条路,他们可以为Array<T>创buildT[]语法语法糖(就像T?Nullable<T>语法糖一样),仍然使用在内存中连续分配arrays的特殊指令和支持。

但是,您将失去将Mammoth[]数组投射到其基类Animal[] ,类似于您无法将List<Mammoth>List<Animal> 。 但是数组协方差已经被打破了,还有更好的select。

arrays协方差的替代?

所有数组都实现IList<T> 。 如果将IList<T>接口设置为适当的协变接口,那么您可以将任何数组Array<Mammoth> (或任何此类列表)转换为IList<Animal> 。 但是,这需要重写IList<T>接口以删除所有可能更改基础数组的方法:

 interface IList<out T> : ICollection<T> { T this[int index] { get; } int IndexOf(object value); } interface ICollection<out T> : IEnumerable<T> { int Count { get; } bool Contains(object value); } 

(请注意,input位置上的参数types不能是T因为这会破坏协方差,但是,对于ContainsIndexOf ,对于传入不正确types的对象,它们只会返回false ,并且实现这些接口的集合可以提供自己的通用IndexOf(T value)Contains(T value) 。)

那么你可以这样做:

 Array<Mammoth> mammoths = new Array<Mammoth>(10); IList<Animals> animals = mammoths; // Covariant conversion 

性能改善甚至很小,因为在设置数组元素的值时,运行时不必检查赋值是否与数组元素的实际types兼容。


我刺伤了它

我试了一下,如果在C#和.NET中实现这样一个Array<T>types,它将与上面描述的真正的协变IList<T>ICollection<T>接口结合起来,并且工作得非常好。 我还添加了不变的IMutableList<T>IMutableCollection<T>接口,以提供新的IList<T>ICollection<T>接口缺less的突变方法。

我围绕它构build了一个简单的集合库,您可以从BitBucket下载源代码和编译的二进制文件 ,或者安装NuGet包:

M42.Collections – 与内置.NET集合类相比,具有更多function,特性和易用性的专用集合。


1 ).NET 4.5中的一个数组T[]通过它的基类ArrayICloneableIListICollectionIEnumerableIStructuralComparableIStructuralEquatable ; 并静静地通过运行时: IList<T>ICollection<T>IEnumerable<T>IReadOnlyList<T>IReadOnlyCollection<T>

兼容性。 数组是一个历史性的types,可以追溯到没有generics的时候。

今天有理由有Array ,然后Array<T> ,然后具体的类;)

[更新,新的见解,感觉到现在有些东西丢失了]

关于较早的答案:

  • 数组可以像其他types一样协变。 你可以实现像'object [] foo = new string [5];' 与协变,所以这不是原因。
  • 兼容性可能是不重新考虑devise的原因,但我认为这也不是正确的答案。

然而,我能想到的另一个原因是因为数组是内存中线性元素的“基本types”。 我一直在考虑使用Array <T>,这也是你可能想知道为什么T是一个对象,为什么这个“对象”甚至存在? 在这种情况下,T []就是我认为Array <T>的另一个语法,它与Array是协变的。 由于types实际上不同,我认为这两种情况是相似的。

请注意,基本对象和基本数组都不是OO语言的要求。 C ++就是一个很好的例子。 没有这些基本结构的基本types的警告不能使用reflection来处理数组或对象。 对于习惯于制作Foo的东西来说,使对象感觉自然。 实际上,没有一个数组基类使得同样不可能做到Foo–这不是经常使用的,而是对范式同样重要。

因此,让C#没有数组基types,但是丰富的运行时types(特别是reflection)是不可能的。

所以更多的细节…

数组在哪里使用,为什么是数组

有一个基本types的东西作为一个基本的数组被用于很多事情,并有很好的理由:

  • 简单的数组

很好,我们已经知道人们使用T[] ,就像他们使用List<T> 。 两者都实现了一组通用的接口,具体如下: IList<T>ICollection<T>IEnumerable<T>IListICollectionIEnumerable

如果您知道这一点,您可以轻松创build一个数组。 我们也都知道这是真的,这并不令人兴奋,所以我们正在继续…

  • 创build集合。

如果你深入List,最终会得到一个数组 – 最准确的说:一个T []数组。

那为什么呢? 虽然你可以使用一个指针结构(LinkedList),但它不完全相同。 列表是连续的内存块,通过连续的内存块来获得速度。 这里有很多原因,但简单地说:处理连续内存是处理内存的最快方式,甚至在CPU中有更多的指令可以让内存更快。

一个仔细的读者可能会指出,事实上你不需要一个数组,而是一个连续的ILtypes的元素,IL可以理解和处理。 换句话说,你可以在这里摆脱数组types,只要你确定有另外一种types可以被IL用来做同样的事情。

请注意,有价值和类的types。 为了保持最好的性能,你需要把它们存储在你的块中,但是对于编组来说,这只是一个需求。

  • 编组。

编组使用所有语言同意进行交stream的基本types。 这些基本types是像byte,int,float,pointer …和array这样的东西。 最值得注意的是数组在C / C ++中的使用方式,如下所示:

 for (Foo *foo = beginArray; foo != endArray; ++foo) { // use *foo -> which is the element in the array of Foo } 

基本上,这将在数组的开始处设置一个指针,并将指针递增(使用sizeof(Foo)字节),直到达到数组的末尾。 该元素在* foo处被获取 – 它获取指针“foo”指向的元素。

再次注意,有值types和引用types。 你真的不希望有一个MyArray,只是将所有盒装的东西都存储为一个对象。 实现MyArray只是一个更棘手的地狱。

一些仔细的读者可以在这里指出,你并不真的需要一个数组,这是真的。 你需要一个连续的types为Foo的元素块 – 如果它是一个值types,它必须作为值types的(字节表示)存储在块中。

  • multidimensional array

那么更多……多维度呢? 显然规则并不是那么黑白,因为突然间我们没有所有的基础类:

 int[,] foo2 = new int[2, 3]; foreach (var type in foo2.GetType().GetInterfaces()) { Console.WriteLine("{0}", type.ToString()); } 

强types刚刚离开窗口,最终收集types为IListICollectionIEnumerable 。 嘿,那么我们应该怎样才能获得这个大小呢? 当使用Array基类时,我们可以使用这个:

 Array array = foo2; Console.WriteLine("Length = {0},{1}", array.GetLength(0), array.GetLength(1)); 

…但是如果我们看一下像IList这样的替代品,就没有相同的东西。 我们如何解决这个问题? 应该在这里引入一个IList<int, int> ? 当然这是错误的,因为基本types只是int 。 那么IMultiDimentionalList<int>呢? 我们可以做到这一点,并用当前在Array中的方法填充它。

  • 数组有一个固定的大小

你有没有注意到有重新分配数组的特殊要求? 这一切都与内存pipe理有关:数组是如此低级别,他们不明白什么增长或缩小。 在C语言中,你会使用'malloc'和'realloc',你应该实现你自己的'malloc'和'realloc'来理解为什么具有固定大小对于你直接分配的所有东西都很重要。

如果你看看它,只有几个东西被分配成“固定”大小:数组,所有基本的值types,指针和类。 显然我们处理数组的方式不同,就像我们处理基本types的方式不同。

关于types安全的一个旁注

那么,为什么首先需要这些“接入点”接口呢?

在所有情况下,最好的做法是为用户提供一个types安全的访问点。 这可以通过比较这样的代码来说明:

 array.GetType().GetMethod("GetLength").Invoke(array, 0); // don't... 

代码如下:

 ((Array)someArray).GetLength(0); // do! 

types安全使您在编程时可以马虎。 如果使用正确,编译器会发现错误,而不是find运行时。 我不能强调这是多么重要 – 毕竟,你的代码可能根本不会在testing用例中被调用,而编译器会一直评估它!

把它放在一起

所以…让我们把它放在一起。 我们想要:

  • 一个强types的数据块
  • 这有它的数据不断存储
  • IL支持,以确保我们可以使用酷的CPU指令,使其迅速stream血
  • 提供所有function的通用界面
  • types安全
  • 多维度
  • 我们希望将值types存储为值types
  • 和其他任何语言一样的编组结构
  • 和一个固定的大小,因为这使内存分配更容易

对于任何集合来说,这都是相当低级别的要求……它需要以某种方式组织内存,并将其转换为IL / CPU ……我想说这是一个很基本的types。

因此,我想知道为什么它不是:

原因是generics不在C#的第一个版本中。

但我无法弄清楚自己会有什么问题。

问题是它会破坏大量使用Array类的代码。 C#不支持多重inheritance,所以像这样的行

 Array ary = Array.Copy(.....); int[] values = (int[])ary; 

将被打破。

如果MS从头开始重新编写C#和.NET,那么将Array作为generics类可能没有问题,但这不是现实。

正如大家所说 – 原始Array是非generics的,因为在v1中存在时没有generics。 下面的猜测…

为了使“数组”通用(现在有意义),你也可以

  1. 保留现有的Array并添加通用版本。 这很好,但“Array”的大部分用法都是随着时间的推移而增长的,而最有可能的原因是更好地实现了相同的概念List<T> 。 此时添加“不增长的元素的顺序列表”的通用版本看起来不太吸引人。

  2. 删除非通用Array并用相同的接口replace为通用Array<T>实现。 现在,您必须为旧版本编译代码,以使用新types而不是现有的Arraytypes。 尽pipe框架代码可能(也很可能很难)支持这种迁移,但总是有很多其他人编写的代码。

    由于Array是非常基本的types,几乎每一个现有的代码(包括自定义代码与反思和本地代码和COM编组)使用它。 因为版本之间的微小不兼容性(.Net Framework的1.x – > 2.x)的结果价格会非常高。

所以结果Arraytypes是永远留下来的。 我们现在有List<T>作为通用等价物被使用。

除了人们提到的其他问题之外,尝试添加一个通用的Array<T>会带来一些其他困难:

  • 即使今天的协变特征从引入generics开始就已经存在,但对arrays来说还不够。 一个被devise为对Car[]进行sorting的例程将能够对Buick[]进行sorting,即使它必须将数组中的元素复制到Cartypes的元素中,然后将其复制回来。 将Cartypes的元素复制到Buick[]中并不是真正的types安全的,但是非常有用。 我们可以定义一个协变数组的单维数组接口,使得sorting成为可能[例如,通过包含一个`Swap(int firstIndex,int secondIndex)方法],但是很难做出像数组是。

  • 虽然Array<T>types可能对T[] ,但在genericstypes系统中定义一个包含T[]T[,]T[,,]T[,,,]等,对于任意数量的下标。

  • 在.net中没有办法expression两种types应该被认为是相同的概念,这样一个T1types的variables可以被复制到T2types中的一个,反之亦然,两个variables都保持对同一个对象的引用。 使用Array<T>types的人可能希望能够将实例传递给需要T[]代码,并接受来自使用T[]代码的实例。 如果旧样式的数组无法传递到使用新样式的代码,那么新样式的数组就不是一个function。

对于types系统来说,可能有许多方法可以允许一个types为Array<T>的types,但是这种types的行为在许多方面与其他typestypes完全不同,而且由于已经有了一个types实现期望的行为(即T[] ),但不清楚定义另一个行为会产生哪些好处。

也许我失去了一些东西,但除非数组实例被铸造或用作ICollection,IEnumerable等。那么你不会得到任何东西与一个T的数组。

数组速度快,已经是安全types,不会产生任何装箱/拆箱的开销。