C#提供了一组关键字in&out
,在泛型接口和泛型委托中,若不使用关键字修饰类型参数T
,则该类型参数是不可变的(即不允许协变/逆变转换),若使用in
修饰类型参数T
,保证“只将T
用于输入”,则允许T
的逆变转换;若使用out
修饰类型参数T
,保证“只将T
用于输出”,则允许T
的协变转换。下面我们解释两个问题:
T
仅用于输出时可以进行协变转换?T
仅用于输入时可以进行逆变转换?//定义两个类型 class Animal {} class Dog : Animal {} //定义两个泛型委托 delegate T OutputOnly<T>(); //只将类型参数T用于输出 delegate void ForInput<T>(T obj); //将类型参数T用于输入 //此时协变的含义是:允许将xxx<Dog>类型的委托转换为xxx<Animal>类型的委托 //对于OutputOnly<T>委托: OutputOnly<Dog> ood = ReturnADog; //一个假设的符合该委托约束条件的方法,返回一个Dog对象 //对委托类型进行协变转换: OutputOnly<Animal> ooa = ood; Animal an = ooa.Invoke(); //调用ooa委托时,它期望获取一个Animal对象,而ooa委托实际保存的ReturnADog方法会返回一个Dog对象, //对于ooa委托来说,它成功获取到了一个Animal对象,所以在仅输出时协变转换是合法的 //对于ForInput<T>委托: ForInput<Dog> fid = NeedADog; //一个假设的符合该委托约束条件的方法,要求传入一个Dog对象 //对委托类型进行协变转换: ForInput<Animal> fia = fid; fia.Invoke(new Animal()); //调用fia委托时,fia根据要求传入一个Animal对象,而此时fia委托实际保存的方法需要一个Dog对象 //所以传入的Animal对象不符合要求。fia委托其实是懵逼的,因为在它看来你要的就是Animal啊, //它并不知道实际需要的是Dog对象,所以当将参数T用于输入时,协变转换是非法的 //此时逆变的含义是:允许将xxx<Animal>类型的委托转换为xxx<Dog>类型的委托 //对于ForInput<T>委托: ForInput<Animal> fia = NeedAAnimal; //一个假设的符合该委托约束条件的方法,要求传入一个Animal //对委托类型进行逆变转换 ForInput<Dog> fid = fia; fid.Invoke(new Dog()); //调用fid委托时,fid根据要求传入一个Dog对象,而此时fid委托实际保存的方法需要一个Animal对象 //所以fid委托传入的对象符合要求,所以在用于输入时逆变转换是合法的 //对于OutputOnly<T>委托: OutputOnly<Animal> ooa = ReturnAAnimal; //一个假设的符合该委托约束条件的方法,返回一个Animal //对委托类型进行逆变转换 OutputOnly<Dog> ood = ooa; Dog dog = ood.Invoke(); //调用ood委托时,它期望获取一个Dog对象,而ood委托实际保存的ReturnAAnimal方法会返回一个Animal //所以ood委托等于是被骗了,它想要的根本不是Animal对象,所以在用于输出时逆变转换是非法的
由此可见,仅用于输出时协变转换才合法,仅用于输入时逆变转换才合法,而in&out
关键字正是C#语言设计的用来监督用户“你想用协变/逆变可以,但不许瞎搞类型转换”的关键字,可以说是很优秀的一个特性了。