Array
:
public abstract class Array
: ICloneable, IList, ICollection, IEnumerable {
我想知道为什么不呢:
public partial class Array<T>
: ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
如果它被声明为泛型类型会有什么问题?
如果它是泛型类型,我们是否还需要非泛型类型,还是来自Array<T>
?如
public partial class Array: Array<object> {
答案 0 :(得分:116)
如果数组成为泛型类型会出现什么问题?
回到C#1.0,他们主要从Java复制了数组的概念。当时Generics并不存在,但是创建者认为它们很聪明并且复制了Java数组所具有的破坏的协变数组语义。这意味着你可以在没有编译时错误的情况下完成这样的事情(但是运行时错误):
Mammoth[] mammoths = new Mammoth[10];
Animal[] animals = mammoths; // Covariant conversion
animals[1] = new Giraffe(); // Run-time exception
在C#2.0中引入了泛型,但没有协变/逆变泛型类型。如果数组是通用的,那么你就不能将Mammoth[]
转换为Animal[]
,这是你之前可以做的事情(即使它被破坏了)。因此,使数组通用会破坏很多代码。
仅在C#4.0中引入了接口的协变/逆变通用类型。这使得有可能一劳永逸地修复破碎的阵列协方差。但同样,这会打破许多现有代码。
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
class documentation:
一维数组实现
IList<T>
,ICollection<T>
,IEnumerable<T>
,IReadOnlyList<T>
和IReadOnlyCollection<T>
通用接口。这些实现在运行时提供给数组,因此,泛型接口不会出现在Array类的声明语法中。
您可以使用数组实现的接口的所有成员吗?
没有。文档继续这句话:
将数组转换为其中一个接口时要注意的关键是添加,插入或删除元素的成员抛出
NotSupportedException
。
那是因为(例如)ICollection<T>
有Add
方法,但你无法向数组中添加任何内容。它会引发异常。这是.NET Framework中早期设计错误的另一个示例,它将使您在运行时向您抛出异常:
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>
interface也由引擎 1 下的数组实现,但它只包含Count
所以它的用途有限。
Array
如果数组是通用的,我们还需要非泛型
Array
类吗?
在早期我们做到了。所有数组都实现非泛型IList
,
ICollection
和
IEnumerable
通过其基类Array
进行接口。这是为所有数组提供特定方法和接口的唯一合理方法,也是Array
基类的主要用途。您会看到枚举的相同选择:它们是值类型,但是从Enum
继承成员;和继承自MulticastDelegate
。
在支持泛型的情况下,是否可以删除非通用基类
Array
?
是的,所有数组共享的方法和接口都可以在泛型Array<T>
类中定义(如果它已经存在)。然后,您可以编写Copy<T>(T[] source, T[] destination)
而不是Copy(Array source, Array destination)
,并获得某些类型安全性的额外好处。
但是,从面向对象的编程的角度来看,拥有一个可用于引用任何数组的公共非泛型基类Array
是很好的,无论它是什么其元素的类型。就像IEnumerable<T>
从IEnumerable
继承的那样(在某些LINQ方法中仍然使用)。
Array
基类是否可以来自Array<object>
?
不,这会产生循环依赖:Array<T> : Array : Array<object> : Array : ...
。此外,这意味着您可以将任何对象存储在数组中(毕竟,所有数组最终都将从类型Array<object>
继承)。
是否可以添加新的通用数组类型
Array<T>
而不会过多影响现有代码?
没有。虽然可以使语法适合,但是不能使用现有的数组协方差。
数组是.NET中的特殊类型。它甚至在通用中间语言中有自己的指令。如果.NET和C#设计师决定走这条路,他们可以为T[]
制作Array<T>
语法语法糖(就像T?
Nullable<T>
的语法糖一样{{1} }),仍然使用在内存中连续分配数组的特殊指令和支持。
但是,您将无法将Mammoth[]
数组投射到其基本类型之一Animal[]
,类似于您无法将List<Mammoth>
投射到{{1} }}。但无论如何阵列协方差都被打破了,还有更好的选择。
数组协方差的替代方法?
所有数组都实现List<Animal>
。如果将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);
}
,因为这会破坏协方差。但T
对于object
和Contains
来说已经足够了传递一个不正确类型的对象时会返回IndexOf
。实现这些接口的集合可以提供它们自己的通用false
和IndexOf(T value)
。)
然后你可以这样做:
Contains(T value)
甚至可以提高性能,因为在设置数组元素的值时,运行时不必检查指定的值是否与数组元素的实际类型兼容。
如果它将在C#和.NET中实现,并结合上面描述的真正的协变Array<Mammoth> mammoths = new Array<Mammoth>(10);
IList<Animals> animals = mammoths; // Covariant conversion
和Array<T>
接口,我会尝试这样的IList<T>
类型是如何工作的,而且效果很好。我还添加了不变的ICollection<T>
和IMutableList<T>
接口,以提供我的新IMutableCollection<T>
和IList<T>
接口缺少的变异方法。
我围绕它构建了一个简单的集合库,您可以download the source code and compiled binaries from BitBucket或安装NuGet包:
M42.Collections - 比内置.NET集合类具有更多功能,特性和易用性的专用集合。
1 ).Net 4.5中的数组ICollection<T>
通过其基类T[]
实现:ICloneable
,IList
,{{3} },ICollection
,IEnumerable
,IStructuralComparable
;并默默地通过运行时:IStructuralEquatable
,IList<T>
,ICollection<T>
,IEnumerable<T>
和IReadOnlyList<T>
。
答案 1 :(得分:11)
兼容性。数组是一种历史类型,可以追溯到没有泛型的时候。
今天有Array
,然后Array<T>
,然后是特定的类是有道理的;)
答案 2 :(得分:5)
因此,我想知道为什么不是:
原因是第一版C#中没有泛型。
但我无法弄清楚自己会出现什么问题。
问题在于它会破坏使用Array
类的大量代码。 C#不支持多重继承,所以像这样的行
Array ary = Array.Copy(.....);
int[] values = (int[])ary;
会被打破。
如果MS从头开始重新编写C#和.NET,那么将Array
作为泛型类可能没有问题,但这不是现实。
答案 3 :(得分:3)
除了人们提到的其他问题之外,尝试添加通用Array<T>
会带来一些其他困难:
即使今天的协方差特征从引入仿制药的那一刻起就存在,它们对阵列来说还不够。设计用于对Car[]
进行排序的例程将能够对Buick[]
进行排序,即使它必须将数组中的元素复制到Car
类型的元素中,然后将其复制回来。将元素从Car
类型复制回Buick[]
并不是真正的类型安全,但它很有用。人们可以定义一个协变阵列的单维阵列接口,以便使分类成为可能[例如,通过包含`Swap(int firstIndex,int secondIndex)方法],但是很难做出像数组一样灵活的东西。
虽然Array<T>
类型可能适用于T[]
,但通用类型系统中无法定义包含T[]
,{{对于任意数量的下标,1}},T[,]
,T[,,]
等。
.net中没有任何方法可以表达两种类型应该被视为相同的概念,这样类型T[,,,]
的变量可以复制到T1
类型的变量中,并且反之亦然,两个变量都持有对同一对象的引用。使用T2
类型的人可能希望能够将实例传递给期望Array<T>
的代码,并接受使用T[]
的代码中的实例。如果无法将旧样式数组传递给使用新样式的代码,那么新样式数组将成为一个障碍而不是功能。
可能有一些方法可以使类型系统允许类型T[]
表现得如此,但是这种类型的行为方式与其他泛型类型完全不同,并且因为存在已经是一种实现所需行为的类型(即Array<T>
),不清楚定义另一种行为会产生什么好处。
答案 4 :(得分:2)
正如大家所说 - 原始Array
是非泛型的,因为在v1中存在时没有泛型。猜测如下......
要使“数组”通用(现在才有意义),你可以
保留现有Array
并添加通用版本。这很好,但是“阵列”的大多数用法都会随着时间的推移而逐渐增加,这很可能是因为实现了相同概念List<T>
的更好实现。此时添加“不增长的元素的顺序列表”的通用版本看起来不太吸引人。
删除非泛型Array
并使用相同的接口替换为通用Array<T>
实现。现在,您必须为旧版本编译已编译的代码,以使用新类型而不是现有的Array
类型。虽然框架代码支持这种迁移是可能的(也很可能很难),但总有很多代码由其他人编写。
由于Array
是非常基本的类型,因此几乎所有现有代码(包括使用本机代码和COM进行反射和编组的自定义代码)都使用它。因此,版本之间甚至微小不兼容的价格(.x框架的1.x - > 2.x)将非常高。
因此结果Array
类型永远存在。我们现在将List<T>
作为通用等效项使用。
答案 5 :(得分:0)
也许我错过了一些东西,但除非将数组实例转换为ICollection,IEnumerable等,否则你就不会获得任何T数组。
阵列很快,并且已经是类型安全的,不会产生任何装箱/拆箱开销。