詳細介紹C# 泛型
在C#開發中,必不可少的要用到泛型。泛型是.NET2.0版本就有的,它廣泛應用於C#框架中容器的使用中。下面我們來詳細介紹一下。
一、泛型的主要優勢
1.效能更高。
2.型別更安全。
3.程式碼更多的重用和擴充套件性。
二、泛型的基本使用
泛型的一個主要優點是效能,我們來看下面的例子:
static void Main(string[] args) { //不是泛型的集合類 ArrayList list = new ArrayList(); //新增一個值型別 裝箱操作 list.Add(12); //去除第一個元素12 拆箱操作 int num = (int)list[0]; Console.WriteLine(num); Console.WriteLine("執行結束"); Console.ReadKey(); }
元資料中ArrayList類的Add方法
// // 摘要: // 將物件新增到 System.Collections.ArrayList 的結尾處。 // // 引數: // value: // 要新增到 System.Collections.ArrayList 末尾的 System.Object。該值可以為 null。 // // 返回結果: // value 已新增的 System.Collections.ArrayList 索引。 // // 異常: // T:System.NotSupportedException: // The System.Collections.ArrayList is read-only.-or- The System.Collections.ArrayList // has a fixed size. public virtual int Add(object value);
相信大家都知道,裝箱拆箱是比較損耗效能的,在執行add方法是, 把值型別轉換成引用型別(裝箱),取出來時在去拆箱,那怎麼樣才能避免這種情況發生呢?
再來看下面程式碼:
//泛型集合類 List<int> list = new List<int>(); list.Add(12); int num = list[0]; Console.WriteLine(num); Console.WriteLine("執行結束"); Console.ReadKey();
這個時候,程式碼並沒有發生裝箱拆箱操作。
元資料中List類的Add方法
// // 摘要: // 將物件新增到 System.Collections.Generic.List`1 的結尾處。 // // 引數: // item: // 要新增到 System.Collections.Generic.List`1 末尾的物件。對於引用型別,該值可以為 null。 public void Add(T item);
程式碼寫到這裡時,我們只是建立了一個List泛型集合,你可能還感覺不到泛型優勢到底在哪裡,你也可能不知道泛型到底是怎麼用的。好,下面我們寫個測試還有自己實際應用的例子。
測試arraylist和list集合的效能
static void Main(string[] args) { //不是泛型的集合類 ArrayList arylist = new ArrayList(); //新增一個值型別 裝箱操作 //泛型集合類 List<int> list = new List<int>(); //計時類 Stopwatch watch = new Stopwatch(); watch.Start();//開始計時 for (int i = 0; i < 10000000; i++) { arylist.Add(i); } watch.Stop(); Console.WriteLine("Arraylist用時:"+watch.ElapsedMilliseconds); Stopwatch watch1 = new Stopwatch(); watch1.Start();//開始計時 for (int i = 0; i < 10000000; i++) { list.Add(i); } watch1.Stop(); Console.WriteLine("list用時:" + watch1.ElapsedMilliseconds); Console.WriteLine("執行結束"); Console.ReadKey(); }
執行結果:
以上的例子中,可以看出在計時的過程中程式碼寫的重複了, 怎麼解決這個問題呢, 泛型要排上用場了。
我們想一下,相同的操作(都是迴圈新增元素,計算用時)用同一個方法實現不就ok了,只不過這個方法是泛型的(可以接受ArrayList,也可以接受List),下面我們來寫一下這個方法。
//我們用T來代表泛型 public static long GetTime<T>(T t) { Stopwatch watch = new Stopwatch(); watch.Start();//開始計時 for (int i = 0; i < 10000000; i++) { t.Add(i); } watch.Stop(); return watch.ElapsedMilliseconds; }
但是問題來了, 這裡並沒有Add方法讓我們使用啊。 我們的思路是把這個T在呼叫時可以當作ArrayList型別, 也可以當作List型別,這裡就設計到了泛型約束。
三、泛型約束
如果使用泛型時, 想要呼叫這個泛型型別中的方法, 那麼就需要新增約束。泛型約束主要有以下幾種:
約束 | 說明 |
where T:struct | 對於結構的約束, T必須是值型別 |
where T:class | T必須是引用型別 |
where T:ITest | T必須實現了ITest介面 |
where T:Test | T必須繼承基類Test |
where T:new() | T必須有預設建構函式 |
where T:T2 | T派生自泛型型別T2,也稱為裸型別約束 |
我們接著上個泛型方法來修改,ArrayList和List都實現了介面IList , 這個時候我們加上這個介面約束;
//我們用T來代表泛型 public static long GetTime<T>(T t)where T:IList { Stopwatch watch = new Stopwatch(); watch.Start();//開始計時 for (int i = 0; i < 10000000; i++) { t.Add(i); } watch.Stop(); return watch.ElapsedMilliseconds; }
呼叫結果:
程式碼寫到這裡時,相信你已經對泛型有所瞭解了,但是真要應用到自己以後的邏輯程式設計中時,一定要善於總結:相同型別的相同方法,或者業務邏輯相同,只有某個判斷不同時,可以用上泛型,不僅高效還程式碼量小。
四、應用場景示範
在我們的專案開發中,資料庫的增刪改查肯定是少不了的, 在這裡我們用泛型來定義增刪改查的泛型類。 之後建立一個使用者表(實際應用中對應資料庫中表結構),資料庫中每一個表都可以用泛型類中定義的方法, 不需要每一個都寫增刪改查操作,也是面向物件程式設計的一種思想:
public class BaseDal<T>where T:class,new () { //以下是增刪查改示範 public void Query(int id) { Console.WriteLine(typeof(T).Name+"查詢方法,id="+id); } public void Update(T t) { Console.WriteLine(typeof(T).Name+"更新方法"); } public void Delete(T t) { Console.WriteLine(typeof(T).Name + "刪除方法"); } public void Add(T t) { Console.WriteLine(typeof(T).Name + "新增方法"); } }
public class User { public int Id { get; set; } public string Name { get; set; } }
呼叫示範
BaseDal<User> dal = new BaseDal<User>(); var user = new User() { Id = 0,Name = "使用者1" }; dal.Add(user); dal.Query(0); user.Name = "使用者11"; dal.Update(user); dal.Delete(user); Console.ReadKey();
五、泛型的協變和抗變
協變和抗變主要是對引數和返回值的型別進行轉換,在.NET4之後可以通過協變和抗變為泛型介面或這泛型委託新增這個擴充套件。
現在我們寫倆個類Shape(形狀)、Rectangle(矩形類),Rectangle派生自Shape,寫一個方法public static Rectangle GetRec() ;這個時候你會發現, 方法的泛型型別是抗變的, 就是我返回一個型別為Rectangle但是我可以用Shape來接收, 但泛型在NET4.0之前不支援這個方式, 泛型在NET4.0之後提供了支援泛型介面和泛型委託的協變和抗變。
普通方法抗變程式碼說明
//形狀 public class Shape { public double Width { get; set; } public double Height { get; set; } public override string ToString() { return string.Format("width:{0},height:{1}",Width,Height); } } //矩形 public class Rectangle : Shape { } ///-----------------------------------方法與呼叫 public static Rectangle GetRec() { return new Rectangle(); } Shape s = GetRec(); Console.ReadKey();
1、泛型介面的協變
泛型介面在型別T前加上out關鍵字,這個時候泛型介面就是協變的,也就意味著返回型別只能是T。 直接看程式碼:
//泛型介面的協變 public interface IIndex<out T> { T GetT(int index); int Count { get; } } public class RecCollection : IIndex<Rectangle> { private Rectangle[] data = new Rectangle[2] { new Rectangle() { Width=10,Height=20 },new Rectangle() {Width=20,Height=30 } }; public int Count { get { return data.Count(); } } public Rectangle GetT(int index) { return data[index]; } }
//呼叫 Shape s1 = new RecCollection().GetT(1); Console.WriteLine(s1.ToString()); IIndex<Rectangle> rec = new RecCollection(); IIndex<Shape> shapes = rec; for (int i = 0; i < shapes.Count; i++) { Console.WriteLine(shapes.GetT(i)); } Console.ReadKey();
以上程式碼可以看出, 我們把泛型介面的泛型定義為矩形(Rectangle), 但是在接受的時候可以用基類(Shape),在這裡實現了協變。
2.泛型介面的抗變
如果泛型型別用in關鍵字標註,那麼這個泛型介面就是抗變的,這樣,介面只能把泛型型別T用作其方法的輸入。
//泛型介面的抗變 public interface IDisplay<in T> { void Show(T item); } public class ShapeDisplay : IDisplay<Shape> { public void Show(Shape item) { Console.WriteLine(item); } } //-------------------------以下呼叫------ Rectangle recs = new Rectangle() { Width=100,Height=200}; IDisplay<Shape> shapeDisplay = new ShapeDisplay(); shapeDisplay.Show(recs); IDisplay<Rectangle> recDisplay = shapeDisplay; recDisplay.Show(recs); Console.ReadKey();
以上程式碼可以看出泛型也是支援抗變和協變的。
六、總結
泛型是C#語言發展帶來的新方法,以上例子只是簡單的運用,希望大家能通過以上例子有所啟發,能在專案中更好的使用泛型。以上還有泛型快取沒有說到,大家有興趣可以找下資料,今天就到這裡吧, 沒有說到的還有不好的地方, 歡迎大家指正!
以上就是詳細介紹C# 泛型的詳細內容,更多關於C# 泛型的資料請關注我們其它相關文章!