前言
泛型並不是C#語言一開始就帶有的特性,而是在FCL2.0之後實現的新功能。基於泛型,我們得以將類型參數化,以便更大范圍地進行代碼復用。同時,它減少了泛型類及泛型方法中的轉型,確保了類型安全。委托本身是一種引用類型,它保存的也是托管堆中對象的引用,只不過這個引用比較特殊,它是對方法的引用。事件本身也是委托,它是委托組,C#中提供了關鍵字event來對事件進行特別區分。一旦我們開始編寫稍微復雜的C#代碼,就肯定離不開泛型、委托和事件。本章將針對這三個方面進行說明。
這裡也有一篇之前我對泛型的簡單理解篇 http://www.cnblogs.com/aehyok/p/3384637.html C# 泛型的簡單理解(安全、集合、方法、約束、繼承)
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要學習記錄以下內容:
建議32、總是優先考慮泛型
建議33、避免在泛型類型中聲明靜態成員
建議34、為泛型參數設定約束
建議32、總是優先考慮泛型
泛型的優點是多方面的,無論是泛型類還是泛型方法都同時具備可重用性、類型安全和高效率等特性,這都是非泛型類和非泛型方法無法具備的。本建議將從可重用性、類型安全和高效率三個方面來進行剖析在實際的編碼過程中為何總是應該優先考慮泛型。
一、可重用性,比如簡單的設計一個集合類
public class MyList { int[] items; public int this[int i] { get { return items[i]; } set { this.items[i] = value; } } public int Count { get { return items.Length; } } ////省略一些其他方法 }
該類型只支持整型,如果要讓類型支持字符串,有一種方法是重新設計一個類。但是這兩個類型的屬性和方法都是非常接近的,如果有一種方法可以讓類型接收一個通用的數據類型,這樣就可以進行代碼復用了,同時類型也只要一個就夠了。泛型完成的就是這樣的功能。
public class MyList<T> { T[] items; public T this[int i] { get { return items[i]; } set { this.items[i] = value; } } public int Count { get { return items.Length; } } ///省略其他方法 }
可以把T理解為一個占位符,在C#泛型編譯生成的IL代碼中,T就是一個占位符的角色。在運行時,即使編譯器(JIT)會用實際代碼中輸入的T類型來代替T,也就是說,在由JIT生成的本地代碼中,已經使用了實際的數據類型。我們可以把MyList<int>和MyList<string>視作兩個完全不同的類型,但是,這僅是對本地代碼而言的,對於實際的C#代碼,它僅僅擁有一個類型,那就是泛型類型MyList<T>。
以上從代碼重用性的角度論證了泛型的優點。繼續從類型MyList<T>的角度論述,如果不用泛型實現代碼重用,另一種方法是讓MyList的編碼從object的角度去設計。在C#的世界中,所有類型(包括值類型和引用類型)都是繼承自object,如果要讓MyList足夠通用,就需要讓MyList針對object編碼,代碼如下:
public class MyList { object[] items; public object this[int i] { get { return items[i]; } set { this.items[i] = value; } } public int Count { get { return items.Length; } } ////省略一些其他方法 }
這會讓以下代碼編譯通過
MyList list = new MyList(); list[0] = 123; list[1] = "123";
由上面兩行代碼帶來的問題就是非”類型安全性“。該問題實際在建議20 http://www.cnblogs.com/aehyok/p/3641896.html 中已經詳細論述過了。讓類型支持類型安全,可以讓程序在編譯期間就過濾掉部分Bug,同時也能讓代碼規避掉”轉型為object類型“或“從object轉型為實際類型”所帶來的效率損耗。尤其是涉及的操作類型是值類型時,還會帶來裝箱和拆箱的性能損耗。
例如,上文代碼中的
list[1] = 123;
就會帶來一次裝箱操作,因為它首先倍轉型為object,繼而存儲到items這個object數組中去了。
泛型為C#帶來的是革命性的變化,FCL之後的很多功能都是借助泛型才得到了很好的實現,如LINQ。LINQ借助於泛型和擴展方法,有效地豐富了集合的查詢功能,同時避免了代碼爆炸並提升了操作的性能。我們在設計自己的類型時,應充分考慮到泛型的優點,讓自己的類型成為泛型類。
建議33、避免在泛型類型中聲明靜態成員
在上一個建議中,已經解釋了應該將MyList<int> 和MyList<string> 視作兩個完全不同的類型,所以,不應將MyList<T>中的靜態成員理解成為MyList<int>和MyList<string>共有的成員。
對於一個非泛型類型,以下的代碼很好理解:
public class MyList { public static int Count { get; set; } public MyList() { Count++; } } class Program { static void Main(string[] args) { MyList myList1 = new MyList(); MyList mylist2 = new MyList(); Console.WriteLine(MyList.Count); Console.ReadLine(); } }
結果返回為2.
如果將MyList換成泛型類型,看看下面的代碼會輸出什麼呢?
public class MyList<T> { public static int Count { get; set; } public MyList() { Count++; } } class Program { static void Main(string[] args) { MyList<int> myList1 = new MyList<int>(); MyList<int> mylist2 = new MyList<int>(); MyList<string> mylist3 = new MyList<string>(); Console.WriteLine(MyList<int>.Count); Console.WriteLine(MyList<string>.Count); Console.ReadLine(); } }
代碼輸出為
public class MyList { public static int Count { get; set; } public static int Func<T>() { return Count++; } } class Program { static void Main(string[] args) { Console.WriteLine(MyList.Func<int>()); Console.WriteLine(MyList.Func<int>()); Console.WriteLine(MyList.Func<string>()); Console.ReadLine(); } }
輸出結果為
建議34、為泛型參數設定約束
”約束“這個詞可能會引起歧義,有些人可能認為對泛型參數設定約束是限制參數的使用,實際情況正好相反。沒有約束的泛型參數作用很有限,倒是”約束“讓泛型參數具有了更多的行為和屬性。
public class Salary { /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 基本工資 /// </summary> public int BaseSalary { get; set; } /// <summary> /// 獎金 /// </summary> public int Bouns { get; set; } } public class SalaryComputer { public int Compare<T>(T t1, T t2) { return 0; } }
查看上面定義實體類可以發現,Compare<T>方法的參數t1或參數t2僅僅具有object的屬性和行為,所以幾乎不能在方法中對它們做任何的操作。但是,在加了約束之後,我們會發現參數t1或參數t2變成了一個有用的對象。由於為其指定了對應的類型,t1和t2現在就是一個Salary了,在方法的內部,它擁有了屬性BaseSalary和Bonus,代碼如下:
public class SalaryComputer { public int Compare<T>(T t1, T t2) where T:Salary { if (t1.BaseSalary > t2.BaseSalary) { return 1; } else if (t1.BaseSalary == t2.BaseSalary) { return 0; } else { return -1; } } }
那麼可以為泛型參數指定那些約束呢?
1、指定參數是值類型(除Nullable外),可以有如下形式:
public void Method1<T>(T t) where T : struct { }
2、指定參數是引用類型,可以有如下形式:
public void Method1<T>(T t) where T : class { } public void Method1<T>(T t) where T : Salary { }
注意object不能用來作為約束。
3、指定參數具有無參數的公共構造函數,可以有如下形式:
public void Method2<T>(T t) where T : new() { }
注意CLR目前只支持無參構造方法約束。
4、指定參數必須是指定的基類、或者派生自指定的基類。
5、指定參數必須是指定的接口、或者實現指定的接口。
6、指定T提供的類型參數必須是為U提供的參數,或者派生自為U提供的參數。
public class Sample<U> { public void Method1<T>(T t) where T : U { } }
7、可以對同一類型的參數設置多個約束,並且約束自身可以是泛型類型。
在編程的過程中應該始終考慮為泛型參數設定約束,正像本建議開始的時候所說,約束使泛型成為一個實實在在的“對象”,讓它具有了我們想要的行為和屬性,而不僅僅是一個object。
英語小貼士
1、Where is the tourist information?——旅游咨詢中心在那裡?
2、Can you recommend a hotel which is not too expensive?——是否可建議一間較為廉價的旅館?
3、Is there an airport bus to the city?——是否有機場巴士可到市區?
4、Is there a hotel which costs under 50 dollars a night?——是否有每晚花費在50美元以下的飯店?
5、Where is the bus stop(taxi stand)?——巴士站牌(出租車招呼站)在那裡?
6、Could you recommend a hotel in the city center?——是否可建議一家位於市中心的旅館?
7、Where can I get the limousine for Hilton Hotel?——我在何處可搭乘希爾頓飯店的接泊巴士?
8、I'd like to stay at a hotel near the station (beach).——我想要住在靠近車站(海灘)的飯店。
作者:aehyok
出處:http://www.cnblogs.com/aehyok/
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,那不妨點個推薦吧,謝謝支持:-O。