为什么数组不是通用类型?

为什么数组不是通用类型?,第1张

为什么数组不是通用类型? 历史

如果数组成为泛型类型会出现什么问题?

回到C#1.0中,他们主要从Java复制了数组的概念。泛型当时不存在,但是创建者认为它们很聪明,并复制了Java数组具有的破碎的协变量数组语义。这意味着您可以在没有编译时错误的情况下进行此类 *** 作(而是运行时错误):

Mammoth[] mammoths = new Mammoth[10];Animal[] animals = mammoths; // Covariant conversionanimals[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
课程文档中:

一维数组实现的

IList<T>
ICollection<T>
IEnumerable<T>
IReadOnlyList<T>
IReadOnlyCollection<T>
通用接口。这些实现是在运行时提供给数组的,因此,通用接口不会出现在Array类的声明语法中。


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

否。文档以以下注释继续:

将数组强制转换为这些接口之一时要意识到的关键是,添加,插入或删除元素throw的成员

NotSupportedException

这是因为(例如)

ICollection<T>
有一个
Add
方法,但是您不能向数组添加任何内容。它将引发异常。这是.NET
framework中早期设计错误的另一个示例,该错误会使您在运行时引发异常:

ICollection<Mammoth> collection = new Mammoth[10];  // Cast to interface typecollection.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

如果数组是通用的,我们还需要非通用的

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 : ...
。另外,这意味着您可以将 任何
对象存储在数组中(毕竟,所有数组最终都将继承自type
Array<object>
)。


未来

是否可以在

Array<T>
不影响现有代码太多的情况下添加新的通用数组类型?

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

数组是.NET中的一种特殊类型。它甚至在通用中间语言中都有自己的说明。如果.NET和C#设计人员决定走这条路,他们可以

T[]
为其制作语法语法糖
Array<T>
(就像
T?
语法糖的
Nullable<T>
用法一样),并且仍然使用特殊的指令和支持在内存中连续分配数组。

但是,你会失去的能力,铸造阵列

Mammoth[]
到其基本类型之一
Animal[]
,类似于如何可以不投
List<Mammoth>
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(Tvalue)
Contains(T value)

然后,您可以这样做:

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

甚至有很小的性能改进,因为在设置数组元素的值时,运行时不必检查分配的值是否与数组元素的实际类型兼容。


我的刺

我刺探了这种

Array<T>
类型如何在C#和.NET中实现,并结合上述真正的协变量
IList<T>
ICollection<T>
接口,将如何工作,并且效果很好。我还添加了不变式
IMutableList<T>
IMutableCollection<T>
接口,以提供我的new
IList<T>
ICollection<T>
接口缺乏的突变方法。

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

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


1)数组

T[]
中.NET 4.5工具通过它的基类
Array
ICloneable
IList
ICollection
IEnumerable
IStructuralComparable
IStructuralEquatable
;
默默通过运行时:
IList<T>
ICollection<T>
IEnumerable<T>
IReadOnlyList<T>
,和
IReadOnlyCollection<T>



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5462543.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存