前言
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要學習記錄以下內容:
建議42、使用泛型參數兼容泛型接口的不可變性
建議43、讓接口中的泛型參數支持協變
建議44、理解委托中的協變
建議45、為泛型類型參數指定協變
建議42、使用泛型參數兼容泛型接口的不可變性
讓返回值類型返回比聲明的類型派生程度更大的類型,就是“協變”。協變不是一種新出現的技術,在以往的編碼中,我們已經在不自覺地使用協變。以下的代碼就是一個不自覺應用協變的例子:
class Employee { public string Name { get; set; } } class Programmer : Employee { } class Program { public Employee GetAEmployee(string name) { Console.WriteLine("我是雇員:"+name); return new Programmer() { Name = name }; } static void Main(string[] args) { } }
Programmer是Employee的子類,所以一個Programmer對象也就是一個Employee對象。方法GetAEmployee返回一個Programmer的對象,也就是相當於返回了一個Employee對象。正是因為在FCL4.0以前的版本中,協變是如此自然的一種應用,所以我們很有可能寫出這樣的代碼:
class Employee { public string Name { get; set; } } class Programmer : Employee { } interface ISalary<T> { void Pay(); } class BaseSalaryCounter<T> : ISalary<T> { public void Pay() { Console.WriteLine("Pay base salary"); } }
接下來看一下Main函數中的調用:
class Program { static void Main(string[] args) { ISalary<Programmer> s = new BaseSalaryCounter<Programmer>(); PrintSalary(s); } static void PrintSalary(ISalary<Employee> s) { s.Pay(); } }
咋眼一看,問題應該不大,不過編譯器卻出現了錯誤。
編譯器對於接口和委托類型參數的檢查是非常嚴格的,除非用關鍵字out特別聲明(這個會在下一個建議中進行闡述),不然這段代碼只會編譯失敗,要讓PrintSalary完成需求,我們可以使用泛型類型參數:
static void Main(string[] args) { ISalary<Programmer> s = new BaseSalaryCounter<Programmer>(); PrintSalary(s); } static void PrintSalary<T>(ISalary<T> s) { s.Pay(); }
可能有人會注意到,本建議開頭處指出“協變”是針對返回值而言的,但是所舉的這個例子卻並沒有體現“返回值”這個概念。實際上,只要泛型類型參數在一個接口聲明中不被用來作為方法的輸入參數,我們都可姑且把它堪稱是“返回值”類型的。所以,本建議中這種模式是滿足“協變”的定義的。但是,只要將T作為輸入參數,便不滿足“協變”的定義了。
建議43、讓接口中的泛型參數支持協變
除了建議42中提到的使用泛型參數兼容泛型接口的不可變性外,還有一種辦法就是為接口中的泛型聲明加上out關鍵字來支持協變,如下所示:
namespace ConsoleApplication10 { class Employee { public string Name { get; set; } } class Programmer : Employee { } interface ISalary< out T> { void Pay(); } class BaseSalaryCounter<T> : ISalary<T> { public void Pay() { Console.WriteLine("Pay base salary"); } } class Program { static void Main(string[] args) { ISalary<Programmer> s = new BaseSalaryCounter<Programmer>(); PrintSalary(s); } static void PrintSalary(ISalary<Employee> s) { s.Pay(); } } }
out關鍵字是FCL4.0中新增的功能,它可以在泛型接口和委托中使用,用來讓類型參數支持協變性。通過協變,可以使用比聲明的參數派生類型更大的參數。通過以上的例子我們應該能理解這種應用。
FCL4.0對多個接口進行了修改以支持協變,如IEnumerable<out T>、IEnumerator<out T>、IQueryable<out T>等。由於IEnumerable<out T>現在支持協變,所以上段代碼在FCL4.0中能運行得很好。
在我們自己的代碼中,如果要編寫泛型接口,除非確定該接口中的泛型參數不涉及變體,否則都建議加上out關鍵字。協變增大了接口的使用范圍,而且幾乎不會帶來什麼副作用。
建議44、理解委托中的協變
委托中的泛型變量天然是部分支持協變的。為什麼說是“部分支持協變”呢?來看一下下面的例子:
class Employee { public string Name { get; set; } } class Manager:Employee { } class Program { public delegate T GetEmployeeHandler<T>(string name); static void Main(string[] args) { GetEmployeeHandler<Employee> getAEmployee = GetAManager; Employee e = getAEmployee("Mike"); Console.ReadLine(); } static Manager GetAManager(string name) { Console.WriteLine("我是經理:" + name); return new Manager() { Name = name }; } static Employee GetAEmployee(string name) { Console.WriteLine("我是雇員:"+name); return new Employee() {Name=name }; } }
上文中,方法GetAManager返回的是一個Manager,但是在使用中,其實是將其賦值給了一個泛型參數為Employee的委托變量。我們也許會認為委托中的泛型變量不再需要out關鍵字,這是錯誤的理解。因為存在下面這樣一種情況,所以編譯報錯:
要讓上面的代碼編譯通過,同樣需要為委托中的泛型參數指定out關鍵字:
public delegate T GetEmployeeHandler< out T>(string name);
除非考慮到該委托聲明肯定不會用於可變性,否則,為委托中的泛型參數指定out關鍵字將會拓展該委托的應用,建議在實際的編碼工作中永遠這樣使用,實際上,FCL4.0中的一些委托聲明已經用out關鍵字來讓委托支持協變了,如我們常常會使用到的:
public delegate TResult Func<out TResult>();
建議45、為泛型類型參數指定協變
逆變是指方法的參數可以是委托或泛型接口的參數類型的基類。FCL4.0中支持逆變的常用委托有:
Func<in T,out TResult> Predicate<in T>
常用泛型接口有:
ICompare<in T>
下面的例子演示了泛型類型參數指定逆變所帶來的好處:
public interface IMyComparable<in T> { int Compare(T other); } public class Employee:IMyComparable<Employee> { public string Name { get; set; } public int Compare(Employee other) { return Name.CompareTo(other.Name); } } public class Programmer:Employee,IMyComparable<Programmer> { public int Compare(Programmer other) { return Name.CompareTo(other.Name); } } public class Manager : Employee { } class Program { static void Main(string[] args) { Programmer p = new Programmer() { Name = "Mike" }; Manager m = new Manager() { Name = "aehyok" }; Test(p, m); } static void Test<T>(IMyComparable<T> t1, T t2) { //省略 } }
在上面的這個例子中,如果不為接口IMyComparable的泛型參數T指定in關鍵字,將會導致Test(p,m)編譯錯誤。由於引入了接口的逆變性,這讓方法Test支持了更多的應用場景。在FCL4.0之後版本的實際編碼中應該始終注意這一點。
英語小貼士
1、Then, please give me a new reservation.——那麼,請幫我重新訂位。
2、Sorry, this flight is full.——抱歉,這班飛機已客滿。
3、What is the possibility of my gettinga seat if I wait?——若是我在此等候,有機位的機率有多大?
4、When will the next flight to Los Angeles leave?——下一班飛往洛杉機的班機何時起飛?
5、The day after tomorrow, Friday.——後天,星期五。
6、That will be fine. What's the flight number and departure time?——太好了。請告訴我班機號碼與起飛時間?
7、What is the fare?——費用多少?
作者:aehyok
出處:http://www.cnblogs.com/aehyok/
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,那不妨點個推薦吧,謝謝支持:-O。