泛型
- C#泛型的in, out关键字与Java中的?通配符解决了:如果B是A的子类,则C
<A>
与 C<B>
是否具有子类型化的关系。 Object[] a = new SubClass[10];
没有编译时错误,如果有,那么各种泛型数据结构将不能实现。- C#中in, out关键字用于接口和委托,在定义泛型时就决定了逆变协变关系。Java则是在使用时通过指定super,extends关键字结合通配符
{?}
来决定逆变协变关系。 - 记住一点:C
<A>
与 C<B>
之间是否具有协变或者逆变的关系。 - C# is declaration-site variance, java is use-site variance. C#是声明处型变,Java是使用处型变。
kotlin兼具声明处型变及使用处型变
- 助记表
生产者 | 消费者 |
---|---|
返回值 | 参数 |
PE | CS |
OUT | IN |
协变 | 逆变 |
C# 代码
public class Person { }
public class Student : Person { }
public class LittleStudent : Student { }
public delegate void Action<in T>(T obj);
public void test()
{
List<Student> students = new List<Student>();
Action<Person> pAction = (item) => { };
Action<Student> sAction = p;
Action<LittleStudent> lAction = (item) => { };
// 正确:Action<Person>可以安全的转换为Action<Student>。
// Action<in T> 对T具有逆变安全性。
students.ForEach(pAction);
// 正确
students.ForEach(sAction);
// 编译时错误,原因如下:
// 假定可以通过编译:lAction接受的是LittleStudent,而现在传入的是Student.
// 显然Student不能安全的转换为LittleStudent
students.ForEach(lAction);
}
Java 代码
public class Person { }
public class Student extends Person { }
public class LittleStudent extends Student { }
//////////////////// 定义END ///////////////////////////
public void test() {
List<Student> students = new ArrayList<Student>();
List<? extends Student> y = students;
Consumer<Person> p = (item) -> {};
Consumer<? super Student> c = p;
students.forEach(p);
students.forEach(c);
y.forEach(c);
y.forEach(p);
List<? super Student> superStudents = new ArrayList<Person>();
// 编译时错误
superStudents.forEach(p);
// 编译时错误
superStudents.forEach(c);
Consumer<Object> o = (item)->{};
// 正确
superStudents.forEach(o);
}
C#协变与逆变
- 如果接口要支持对T逆变,那么接口中方法的参数类型都必须支持对T协变才行。这就是
方法参数
的协变-逆变互换原则。- 如果一个接口需要支持对T进行协变或逆变,那么这个接口所有方法的返回值类型必须支持对T同样方向的协变或逆变。这就是
方法返回值
的协变-逆变一致原则