我們先來看一個最為常見的泛型類型List<T>的定義
(真正的定義比這個要復雜的多,我這裡刪掉了很多東西)
- [Serializable]
- public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>
- {
- public T this[int index] { get; set; }
- public void Add(T item);
- public void Clear();
- public bool Contains(T item);
- public int IndexOf(T item);
- public bool Remove(T item);
- public void Sort();
- public T[] ToArray();
- }
List後面緊跟著一個<T>表示它操作的是一個未指定的數據類型(T代表著一個未指定的數據類型)
可以把T看作一個變量名,T代表著一個類型,在List<T>的源代碼中任何地方都能使用T。
T被用作方法的參數和返回值。
Add方法接收T類型的參數,ToArray方法返回一個T類型的數組
注意:
泛型參數必須以T開頭,要麼就叫T,要麼就叫TKey或者TValue;
這跟接口要以I開頭是一樣的,這是約定。
下面來看一段使用泛型類型的代碼
- var a = new List<int>();
- a.Add(1);
- a.Add(2);
- //這是錯誤的,因為你已經指定了泛型類型為int,就不能在這個容器中放入其他的值
- //這是編譯器錯誤,更提升了排錯效率,如果是運行期錯誤,不知道要多麼煩人
- a.Add("3");
- var item = a[2];
請注意上面代碼裡的注釋
二、泛型的作用(1):
作為程序員,寫代碼時刻不忘代碼重用。
代碼重用可以分成很多類,其中算法重用就是非常重要的一類,假設你要為一組整型數據寫一個排序算法,又要為一組浮點型數據寫一個排序算法,如果沒有泛型類型,你會怎麼做呢?
你可能想到了方法的重載。
寫兩個同名方法,一個方法接收整型數組,另一個方法接收浮點型的數組。
但有了泛型,你就完全不必這麼做,只要設計一個方法就夠用了,你甚至可以用這個方法為一組字符串數據排序。
三、泛型的作用(2):
假設你是一個方法的設計者,這個方法需要有一個輸入參數,但你並能確定這個輸入參數的類型,那麼你會怎麼做呢?
有一部分人可能會馬上反駁:“不可能有這種時候!”
那麼我會跟你說,編程是一門經驗型的工作,你的經驗還不夠,還沒有碰到過類似的地方。
另一部分人可能考慮把這個參數的類型設置成Object的,這確實是一種可行的方案,但會造成下面兩個問題,如果我給這個方法傳遞整形的數據(值類型的數據都一樣),就會產生額外的裝箱、拆箱操作,造成性能損耗。
如果你這個方法裡的處理邏輯不適用於字符串的參數,而使用者又傳了一個字符串進來,編譯器是不會報錯的,只有在運行期才會報錯。
(如果質管部門沒有測出這個運行期BUG,那麼不知道要造成多大的損失呢)
這就是我們常說的:類型不安全。
四、泛型的示例:
像List<T>和Dictionary<TKey,TValue>之類的泛型類型我們經常用到,下面我介紹幾個不常用到的泛型類型。
ObservableCollection<T>
當這個集合發生改變後會有相應的事件得到通知。
請看如下代碼:
- static void Main(string[] args)
- {
- var a = new ObservableCollection<int>();
- a.CollectionChanged += a_CollectionChanged;
- }
- static void a_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- //可以通過Action來判斷是什麼操作觸發了事件
- //e.Action == NotifyCollectionChangedAction.Add
- //可以根據以下兩個屬性來得到更改前和更改後的內容
- //e.NewItems;
- //e.OldItems;
- }
使用這個集合需要引用如下兩個名稱空間
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
BlockingCollection<int>是線程安全的集合
來看看下面這段代碼
- var bcollec = new BlockingCollection<int>(2);
- //試圖添加1-50
- Task.Run(() =>
- {
- //並行循環
- Parallel.For(1, 51, i =>
- {
- bcollec.Add(i);
- Console.WriteLine("加入:" + i);
- });
- });
- Thread.Sleep(1000);
- Console.WriteLine("調用一次Take");
- bcollec.Take();
- //等待無限長時間
- Thread.Sleep(Timeout.Infinite);
輸出結果為:
- 加入:1
- 加入:37
- 調用一次Take
- 加入:13
BlockingCollection<int>還可以設置CompleteAdding和IsCompleted屬性來拒絕加入新元素。
.NET類庫還提供了很多的泛型類型,在這裡就不一一例舉了。
五、泛型的繼承:
在.net中一切都繼承字Object,泛型也不例外,泛型類型可以繼承自其他類型。
來看一下如下代碼
- public class MyType
- {
- public virtual string getOneStr()
- {
- return "base object Str";
- }
- }
- public class MyOtherType<T> : MyType
- {
- public override string getOneStr()
- {
- return typeof(T).ToString();
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- MyType target = new MyOtherType<int>();
- Console.WriteLine(target.getOneStr());
- Console.ReadKey();
- }
- }
泛型類型MyOtherType<T>成功的重寫了非泛型類型MyType的方法。
如果我試圖按如下方式從MyOtherType<T>類型派生子類型就會導致編譯器錯誤。
- //編譯期錯誤
- public class MyThirdType : MyOtherType<T>
- {
- }
但是如果寫成這種方式,就不會出錯
- public class MyThirdType : MyOtherType<int>
- &nbs