? 泛型可以将重构代码并且额外添加一个抽象层,是专门为多段代码在不同的数据类型上执行相同指令而设计的。
? 泛型不是类型,而是类型的模板。
? C# 提供了以下 5 种泛型:
? 其中 1 ~ 4 是类型,5 是成员。
? 泛型类不是实际的类,而是类的模板,因此必须先从它们构建实际的类,然后创建类的引用和实例。
? 声明泛型类后,就可以告诉编译器使用哪些真实类型来替代占位符,编译器将获取这些真实类型并创建构造类型(用来创建真实类对象的模板)。
? 和非泛型类一样,引用和实例可以分开创建。
? 要让泛型更加有用,需要提供额外的信息让编译器直到参数可以接受哪些类型,这些额外的信息称为约束。
? 有关 where 子句的要点如下:
? 泛型方法可以在泛型 / 非泛型类、结构和接口中声明。
? 编译器使用每个构造函数实例产生方法的不同版本。
? 编译器有时可以从方法参数推断类型参数。例如,对于如下的方法声明:
? 编译器可以从 myInt 参数的类型推断出 T 为 int,因此可以省略尖括号。
? 和非泛型类一样,泛型类的扩展方法必须满足如下条件:
? 泛型结构的规则和条件与泛型类一致。
? C# LINQ 特性大量使用泛型委托。
? 泛型接口的声明和非泛型接口的声明类似,但是要在接口名称后的尖括号中放置类型参数。
? 必须保证类型实参的组合不会在类型中产生两个重复的接口。
? 例如,对于下面的泛型接口,会产生潜在的冲突:S 可能用作 int 类型,此时会有两个相同类型的接口,这将不被允许。
? 给出如下例子:
? 我么知道,Dog
类型的变量可以作为 Animal
类型的引用,因为 Dog
由 Animal
派生而来,这里发生了隐式类型转换。
? 进行扩展,添加 Factory 泛型委托、MakeDog 方法,并且 MakeDog 方法可以匹配 Factory 委托。
? Main 函数的第二行尝试将 Factory<Dog>
类型赋给 Factory<Animal>
类型,这将产生报错。
? 问题的原因在于,委托 Factory<Dog>
并没有从 Factory<Animal>
派生得到。
? 我们仅希望传递 Dog
给 Factory<Animal>
委托时,代码对 Dog
类型中的 Animal
部分进行操作,这并不会发生越界访问,是完全合理的。为了完成我们的期望,可以通过添加 out 关键字改变委托声明。
? 与协变相反,如果类型参数只用于方法中的输入参数,那么可以传入更高程度的派生类引用,因为委托的方法中只对其基类部分进行操作。
? 调用委托时,调用代码为方法 ActOnAnimal 传入的 Dog 类型的变量,而其期望的是 Animal 对象,因此可以进行操作。
? 相同的原则也适用于接口。
? 前面的内容讲解了显式的协变和逆变。实际上,编译器可以自动识别某个已构建的委托是协变还是逆变,并且自动进行类型强制转换,但这通常发生在没有为对象的类型赋值的时候。
Factory<Animal>
类型的委托,并直接将方法 MakeDog 赋值给它。由于没有创建 Factory<Dog>
委托,因此编译器清楚这是协变关系,允许这种赋值,哪怕委托中没有 out 标识符。Factory<Dog>
委托,因此后面的协变关系赋值需要 out 标识符才能完成。