为什么不是一个genericstypes?
Array
声明如下:
public abstract class Array : ICloneable, IList, ICollection, IEnumerable {
我想知道为什么不是这样:
public partial class Array<T> : ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
-
如果它被声明为一个通用types,会出现什么问题呢?
-
如果是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>
。 1从Array
类文档 :
在.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
实现非通用IList
, ICollection
和IEnumerable
接口。 这是为所有数组提供特定方法和接口的唯一合理方法,也是Array
基类的主要用途。 您可以看到枚举的相同select:它们是值types,但inheritance了Enum
成员; 以及从MulticastDelegate
inheritance的委托。
现在支持generics的非generics基类
Array
可以被移除吗?
是的,所有数组共享的方法和接口可以在genericsArray<T>
类上定义,如果它存在的话。 然后,您可以写入,例如Copy<T>(T[] source, T[] destination)
而不是Copy(Array source, Array destination)
。
然而,从面向对象编程的angular度来看,拥有一个通用的非generics基类Array
是很好的,它可以用来引用任何数组,而不pipe其元素的types如何。 就像IEnumerable<T>
如何从IEnumerable
inheritance(它仍然在一些LINQ方法中使用)。
Array
基类是否可以从Array<object>
派生?
不,那会创build一个循环依赖: Array<T> : Array : Array<object> : Array : ...
另外,这意味着你可以在数组中存储任何对象(毕竟,所有的数组最终都将从typesArray<object>
inheritance)。
未来
可以添加新的通用数组types
Array<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
因为这会破坏协方差,但是,对于Contains
和IndexOf
,对于传入不正确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[]
通过它的基类Array
: ICloneable
, IList
, ICollection
, IEnumerable
, IStructuralComparable
, IStructuralEquatable
; 并静静地通过运行时: 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>
, IList
, ICollection
和IEnumerable
。
如果您知道这一点,您可以轻松创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为IList
, ICollection
和IEnumerable
。 嘿,那么我们应该怎样才能获得这个大小呢? 当使用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。 下面的猜测…
为了使“数组”通用(现在有意义),你也可以
-
保留现有的
Array
并添加通用版本。 这很好,但“Array”的大部分用法都是随着时间的推移而增长的,而最有可能的原因是更好地实现了相同的概念List<T>
。 此时添加“不增长的元素的顺序列表”的通用版本看起来不太吸引人。 -
删除非通用
Array
并用相同的接口replace为通用Array<T>
实现。 现在,您必须为旧版本编译代码,以使用新types而不是现有的Array
types。 尽pipe框架代码可能(也很可能很难)支持这种迁移,但总是有很多其他人编写的代码。由于
Array
是非常基本的types,几乎每一个现有的代码(包括自定义代码与反思和本地代码和COM编组)使用它。 因为版本之间的微小不兼容性(.Net Framework的1.x – > 2.x)的结果价格会非常高。
所以结果Array
types是永远留下来的。 我们现在有List<T>
作为通用等价物被使用。
除了人们提到的其他问题之外,尝试添加一个通用的Array<T>
会带来一些其他困难:
-
即使今天的协变特征从引入generics开始就已经存在,但对arrays来说还不够。 一个被devise为对
Car[]
进行sorting的例程将能够对Buick[]
进行sorting,即使它必须将数组中的元素复制到Car
types的元素中,然后将其复制回来。 将Car
types的元素复制到Buick[]
中并不是真正的types安全的,但是非常有用。 我们可以定义一个协变数组的单维数组接口,使得sorting成为可能[例如,通过包含一个`Swap(int firstIndex,int secondIndex)方法],但是很难做出像数组是。 -
虽然
Array<T>
types可能对T[]
,但在genericstypes系统中定义一个包含T[]
,T[,]
,T[,,]
,T[,,,]
等,对于任意数量的下标。 -
在.net中没有办法expression两种types应该被认为是相同的概念,这样一个
T1
types的variables可以被复制到T2
types中的一个,反之亦然,两个variables都保持对同一个对象的引用。 使用Array<T>
types的人可能希望能够将实例传递给需要T[]
代码,并接受来自使用T[]
代码的实例。 如果旧样式的数组无法传递到使用新样式的代码,那么新样式的数组就不是一个function。
对于types系统来说,可能有许多方法可以允许一个types为Array<T>
的types,但是这种types的行为在许多方面与其他typestypes完全不同,而且由于已经有了一个types实现期望的行为(即T[]
),但不清楚定义另一个行为会产生哪些好处。
也许我失去了一些东西,但除非数组实例被铸造或用作ICollection,IEnumerable等。那么你不会得到任何东西与一个T的数组。
数组速度快,已经是安全types,不会产生任何装箱/拆箱的开销。