泛型 总结
定义时:不知道 类型
调用时:必须 给出 类型
- 编译器可以推断出T的 具体类型 时,具体类型 可省略
- 编译器推断不出lamda表达式中的 具体类型,这种情况下 具体类型 不可省略
编译时:
T
尚未 被替换成 具体类型
运行时:
T
被替换成 具体类型
协变抗变
协变和抗变是非常general的概念:
抗变:定义了父类变量 但用子类实例化了它。
- 可继续在此父类变量上 调用 父类的成员。
- 对于父类变量来说,这种
“变化”
是它所允许的,是它能“抵抗”的
。
协变:定义了父类变量 但用子类实例化了它。
- 可继续在此父类变量上 调用 子类成员。
- 对于父类变量来说,它
“协同”
子类实例发生了“变化”
.NET的方法,参数是抗变的,返回值是协变的
泛型接口的协变和逆变
泛型接口的抗变
给ICustomerList<T>
添加了in
关键字
ICustomerList<Animal> animals = new CustomerList<Animal>();
//子类在左 父类在右
ICustomerList<Cat> cats = animals;
重点是: cats 被允许调用 只在
ICustomerList<Animal>
定义的方法
- cats调用
ICustomerList<Animal>
定义的方法时,其实现是CustomerList<Animal>
里的 - 在
CustomerList<Animal>
中,方法的入参的类型都定义为Animal
- 而当用cats来调用
ICustomerList<Animal>
定义的方法时,方法实际上传入的参数是类型Cat
- 如果
T
只能做方法的入参
由于方法的入参是抗变的,将子类实例赋给父类入参变量,意味着 方法内部使用入参时,会放心大胆地调用父类的成员
泛型接口的抗变 实际上 是在遵循方法的入参是抗变的这一条规律
- 加了
in
关键字 在使用泛型接口时 就可以放心地按“ 子类在左 父类在右 ”来使用
泛型接口的协变
给IEnumerable<T>
加了out
关键字
//父类在左 子类在右
IEnumerable<Animal> animals = new List<Cat>();
重点是:animals可以调用
IEnumerable<Cat>
中定义的方法
- animals调用
IEnumerable<Cat>
中定义的方法时,其实现是在List<Cat>
里的 - 在
List<Cat>
中,方法的返回值都定义为Cat
类型 - 而当用animals来调用
IEnumerable<Cat>
中方法时,实际用来承接方法返回值的实例的类型是Animal
- 如果
T
只能做方法的返回值
由于 方法的返回值是协变的,用父类变量来承接子类返回值,意味着 父类变量可以放心大胆地调用子类的成员
泛型接口的协变 实际上 是在遵循 方法的返回值是协变的 这一条规律
- 加了
out
关键字 在使用泛型接口时 就可以放心地按“ 父类在左 子类在右” 来使用
理解 泛型接口的协变和逆变的 一个关键点:
- 等号右边 是实现方式 是实际的 方法定义(包括入参类型, 返回值类型)
- 等号左边 是实际的 传入的实例 (包括入参的实例的类型, 接收返回值的实例的类型)