用泛型解决参数化类型

泛型

泛型(generic)为.NET 框架引入了类型参数(type parameters)的概念。

泛型实现了类型和方法的“参数化”,就像在普通的方法调用中,经常要用参数来告诉它们使用什么值。同样,泛型类型和方法也可以让参数告诉它们使用什么类型。

泛型带来的好处非常像静态语言较之动态语言的优点:更好的编译时检查,更多在代码中能直接表现的信息,更多的IDE 支持,更好的性能。

泛型有两种形式:泛型类型(包括类、接口、委托和结构——没有泛型枚举)和 泛型方法。

类型参数(type parameter)是真实类型的占位符。在泛型声明中,类型参数要放在一对尖括号内,并以逗号分隔。如 Dictionary<TKey,TValue> ,类型参数是 TKey 和 TValue 。当使用泛型类型或方法时,要用真实的类型代替。如 Dictionary<string, int>。这些真实的类型称为类型实参(type argument)。

如同 类型不是对象而是对象的模板,同样,泛型类型也不是类型,而是类型的模板。

判读泛型声明:

Converter<int, double> converter = TaskSquareRoot;   // Converter<int, double> 委托,输入int,输出double
List<double> doubles = testInt.ConvertAll<double>(converter);  // List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> conv)

static double TaskSquareRoot(int x)
{
    return Math.Sqrt(x);
}

.NET 中的泛型集合

最基础的泛型集合接口为 IEnumerable,表示可迭代的项的序列。IEnumerable 可以请求一个 IEnumerator 类型的迭代器。由于分离了可迭代序列和迭代器,这样多个迭代器可以同时独立地操作同一个序列。如果从数据库角度来考虑,表就是 IEnumerable,而游标是 IEnumerator。

泛型的类型约束(type constraint)用于进一步控制可指定的类型实参。

  • 引用类型约束 <T> where T : class
  • 值类型约束 <T> where T : struct
  • 构造函数类型约束 <T> where T : new()
  • 转换类型约束 :允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。

就像实例字段从属于一个实例一样,静态字段从属于声明它们的类型。对于泛型,每个封闭类型都有它自己的静态字段集。

JIT 编译器如何处理泛型

对于所有不同的封闭类型,JIT 的职责就是将泛型的 IL 转换成本地代码,使其能真正运行起来。

使用 List<T> 作为例子。JIT 为每个值类型实参 (int, long, float) 都创建不同的本地代码,为引用类型 (string, stream 等) 共享相同的本地代码。之所以可以这样是因为所有引用都具有相同的大小,对其操作也是一样的。

假设一个 ArrayList 和一个 List<byte>,它们分别包含相同的6个值。

先来看看 ArrayList,假定使用的是一个32位 CLR 每个已装箱的字节都要产生8字节的对象开销,另加4字节(本来是1字节,但要向上取整到一个字的边界)用于数据本身。除此之外,引用本身也要消耗4字节。所以,每个有效数据都要花费至少16字节。除此之外,缓冲区中还要为引用准备一些额外的未使用的空间。

把它和 List<byte> 进行比较。列表中的每个字节都占用元素数组中一个字节的空间。缓冲区仍有“浪费”的空间,它们等着由新添加的项使用--但最起码,每个未使用的元素只会浪费一个字节。

泛型迭代

在 C#1 中,为了使用 foreach,集合要么必须实现 System.Collections.IEnumerable 接口,要么必须有一个类似于返回一个类型的 GetEnumerator() 方法,而且该类型含有一个恰当的 MoveNext() 方法和 Current 属性。

泛型迭代的例子

泛型在 C# 和其他语言中的限制

为什么不能将 List<string> 转换成 List<object> ?

泛型可变性的缺乏

1. 泛型为何不支持协变性

在左侧的代码中,animals 实际引用的对象是一个 Cat[] ;(第二句会在运行时报错:System.ArrayTypeMismatchException:“尝试访问类型与数组不兼容的元素。”)

在右侧的代码中,animals 实际引用的是一个 List<Cat>。(第一句连编译都不会通过)(单看第二句其实是可以运行的 (一个 Dog 类当然能放到一个动物列表中))

两者都只要求存储对 Cat 实例的引用。左侧的数组版本虽然可以通过编译,但执行时一样会失败。且泛型的设计者认为,这比编译时就失败还要糟糕——静态类型的全部意义就在于在代码运行之前找出错误。

说明:那么,为什么数组是协变的?因为 .Net 第一版允许支持从 Java 中编译来的代码。因为 Java 有,所以 .Net 也就有了。

2. 协变性在什么时候有用

实际上,当 someType 只描述返回类型参数的操作时,协变就是安全的;而当 someType 只描述接受类型参数的操作时,逆变就是安全的。

(someType 代词,某种类型,可替换为 IEnumerable<T>)

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值