一、概述
协变是指返回类型返回比声明的类型派生程度更大的类型,关键字:out。
逆变是指方法的参数可以是委托或者泛型接口的参数类型的基类,关键字:in。FCL4.0中支持逆变的常用委托有:Func<in T,out TResult>,Predicate<in T>。
二、泛型中协变事例
class Program { static void Main(string[] args) { ISalary<Programer> s=new BaseSalaryCounter<Programer>(); printSalary(s); Console.ReadKey(); } private static void printSalary(ISalary<Employee> s) { s.Pay(); } } //interface ISalary<T>//这样写会导致编译报错 interface ISalary<out T> { void Pay(); } class BaseSalaryCounter<T> : ISalary<T> { public void Pay() { Console.WriteLine("Pay Base salary"); } } class Employee { public string Name { get ;set; } } class Programer:Employee { //略 } class Manager:Employee { //略 }
编辑器对于接口和委托类型参数的检查是非常严格的,若不使用"out”关键字会导致编译失败。
参数类型ISalary<Employee>无法接收ISalary<Programer>类型变量。
当我们使用"out”关键字后,ISalary<out T>就赋予了协变性,可以实现返回类型返回比声明的类型派生程度更大的类型。
三. 委托中的协变
其实委托中的泛型变量天然是部分支持协变的。
让我们看一下下面的事例:
class Program { public delegate T GetEmployeeHandler<T>(string name);
static void Main(string[] args) { GetEmployeeHandler<Employee> getAEmployee = GetAManager; Employee e = getAEmployee("Abel"); Console.ReadKey(); } private static Manager GetAManager(string name) { Console.WriteLine("我是经理:" + name); return new Manager() { Name = name }; } private static Employee GetAEmployee(string name) { Console.WriteLine("我是员工:" + name); return new Employee() { Name = name }; } } class Employee { public string Name { get ;set; } } class Programer:Employee { //略 } class Manager:Employee { //略 }
以上代码编译是不会有问题的,执行结果是:我是经理:Abel
我们也许认为委托中的泛型变量不再需要out关键字,这是错误的,因为下面情况编译是通不过的:
class Program { public delegate T GetEmployeeHandler<T>(string name); static void Main(string[] args) { GetEmployeeHandler<Manager> getAManager = GetAManager; GetEmployeeHandler<Employee> getAEmployee = getAManager; Employee e = getAEmployee("Abel"); } private static Manager GetAManager(string name) { Console.WriteLine("我是经理:" + name); return new Manager() { Name = name }; } private static Employee GetAEmployee(string name) { Console.WriteLine("我是员工:" + name); return new Employee() { Name = name }; } } class Employee { public string Name { get ;set; } } class Programer:Employee { //略 } class Manager:Employee { //略 }
要让上面代码通过必须为委托中的泛型参数指定out关键字:
public delegate T GetEmployeeHandler<out T>(string name);
四. 为泛型类型参数指定逆变事例:
class Program { static void Main(string[] args) { Programer p = new Programer() { Name = "Tony" }; Manager m = new Manager() { Name = "Abel" }; Test(p, m); Console.ReadKey(); } public static void Test<T>(IMyComparable<T> t1, T t2) { Console.WriteLine(t1.Compare(t2) == 1 ? "Abel名字排在Tony前" : "Tony名字排在Abel前"); } } public interface IMyComparable<T> { int Compare(T other); } class Employee : IMyComparable<Employee> { public string Name { get ;set; } public int Compare(Employee other) { return Name.CompareTo(other.Name); } } class Programer : Employee, IMyComparable<Programer> { public int Compare(Programer other) { return Name.CompareTo(other.Name); } } class Manager : Employee, IMyComparable<Manager> { public int Compare(Manager other) { return Name.CompareTo(other.Name); } }
上面的事例中,如果不为接口IMyComparable的泛型参数T指定in关键字,将会导致Test(p, m)编译报错。
因为引入了逆变性这让方法Test支持更多的应用场景。