一、概述
協變是指返回型別返回比宣告的型別派生程度更大的型別,關鍵字: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支援更多的應用場景。