【泛型】 总结

91 阅读3分钟

泛型 总结

定义时:不知道 类型

调用时:必须 给出 类型

  • 编译器可以推断出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关键字 在使用泛型接口时 就可以放心地按“ 父类在左 子类在右” 来使用

理解 泛型接口的协变和逆变的 一个关键点:

  • 等号右边 是实现方式 是实际的 方法定义(包括入参类型, 返回值类型)
  • 等号左边 是实际的 传入的实例 (包括入参的实例的类型, 接收返回值的实例的类型)