1. 什么是协变和逆变
在C#中,协变(covariance)和逆变(contravariance)是泛型的特性,用于描述类型转换的行为。它们提供了更加灵活的类型兼容性,使得泛型在处理派生类型和基类型之间的转换时更加方便。
2. 协变
2.1 协变的概念
协变表示类型参数能够隐式地转换为更具体的类型。在实际应用中,协变主要发生在返回类型和委托类型的参数类型上。
举个例子来说明,假如有一个接口IVehicle和派生类Car,我们可以将Car类型作为IVehicle类型的返回值:
interface IVehicle { }
class Car : IVehicle { }
List<Car> cars = new List<Car>();
IEnumerable<IVehicle> vehicles = cars;
2.2 out关键字
在C#中,协变的实现是通过使用out关键字来修饰泛型类型参数,在委托类型的参数列表中使用out修饰符,表示该参数是协变的。例如:
delegate TResult Func<out TResult>();
使用out修饰的类型参数只能出现在方法的返回类型和委托类型的参数列表中,而不能出现在参数的类型或者普通的泛型类中。
3. 逆变
3.1 逆变的概念
逆变表示类型参数能够隐式地转换为更一般的类型。逆变常用于委托类型的参数类型上,允许将派生类型作为基类型的参数使用。
举个例子来说明,假如有一个接口IPrinter和派生类LaserPrinter,我们可以将LaserPrinter类型作为IPrinter类型的参数:
interface IPrinter { void Print(); }
class LaserPrinter : IPrinter { }
Action<LaserPrinter> printAction = (printer) => { printer.Print(); };
Action<IPrinter> printerAction = printAction;
3.2 in关键字
在C#中,逆变的实现是通过使用in关键字来修饰泛型类型参数,在委托类型的参数列表中使用in修饰符,表示该参数是逆变的。例如:
delegate void Action<in T>(T obj);
使用in修饰的类型参数也只能出现在委托类型的参数列表中,而不能出现在其他位置。
4. 协变和逆变的使用场景
协变和逆变的特性在各种场景中都能提供便利,尤其在处理集合类型时更为显著。
4.1 协变的使用场景
协变通常在处理返回类型时使用,如可以将派生类的集合赋值给基类的集合:
IEnumerable<Derived> derivedList = new List<Derived>();
IEnumerable<Base> baseList = derivedList;
通过协变,我们可以方便地遍历派生类的集合,而无需将其显式转换为基类的集合。
4.2 逆变的使用场景
逆变通常在处理委托类型的参数时使用,如将基类的委托作为派生类的委托使用:
Action<Base> baseAction = (obj) => { /* Do something with Base */ };
Action<Derived> derivedAction = baseAction;
通过逆变,我们可以将基类的委托直接赋值给派生类的委托,省去了显式转换的过程。
5. 总结
协变和逆变是C#泛型的重要特性,它们提供了更加灵活的类型兼容性,方便了派生类型和基类型之间的转换。协变适用于返回类型和委托类型的参数类型,通过out关键字实现;逆变适用于委托类型的参数类型,通过in关键字实现。
协变和逆变的应用场景包括处理集合类型和委托类型的参数,通过简化类型转换的步骤,提高了代码的可读性和可维护性。