一、什麼是泛型? 泛型是C#語言和公共語言運行庫(CLR)中的一個新功能,它將類型參數的概念引入.NET Framework。類型參數使得設計某些類和方法成為可能,例如,通過使用泛型類型參數T,可以大大簡化類型之間的強制轉換或裝箱操作的過程(下一篇將說明如何解決裝箱、拆箱問題)。說白了,泛型就是通過 ...
一、什麼是泛型?
泛型是C#語言和公共語言運行庫(CLR)中的一個新功能,它將類型參數的概念引入.NET Framework。類型參數使得設計某些類和方法成為可能,例如,通過使用泛型類型參數T,可以大大簡化類型之間的強制轉換或裝箱操作的過程(下一篇將說明如何解決裝箱、拆箱問題)。說白了,泛型就是通過參數化類型來實現在同一份代碼上操作多種數據類型,利用“參數化類型”將類型抽象化,從而實現靈活的復用。
使用泛型給代碼帶來的5點好處:1、可以做大限度的重用代碼、保護類型的安全以及提高性能。
2、可以創建集合類。
3、可以創建自己的泛型介面、泛型方法、泛型類、泛型事件和泛型委托。
4、可以對泛型類進行約束,以訪問特定數據類型的方法。
5、關於泛型數據類型中使用的類型的信息,可在運行時通過反射獲取。
例子:
using System; namespace ConsoleApp { class Program { class Test<T> { public T obj; public Test(T obj) { this.obj = obj; } } static void Main(string[] args) { int obj1 = 2; var test = new Test<int>(obj1); Console.WriteLine("int:" + test.obj); string obj2 = "hello world"; var test1 = new Test<string>(obj2); Console.WriteLine("String:" + test1.obj); Console.ReadKey(); } } }
輸出結果是:
int:2
String:hello world
分析:
1、 Test是一個泛型類。T是要實例化的範型類型。如果T被實例化為int型,那麼成員變數obj就是int型的,如果T被實例化為string型,那麼obj就是string類型的。
2、 根據不同的類型,上面的程式顯示出不同的值。
二、泛型的主約束和次約束是什麼?
六種類型的約束:
T:結構 |
類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。有關更多信息,請參見使用可空類型(C# 編程指南)。 |
T:類 |
類型參數必須是引用類型,包括任何類、介面、委托或數組類型。 |
T:new() |
類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最後指定。 |
T:<基類名> |
類型參數必須是指定的基類或派生自指定的基類。 |
T:<介面名稱> |
類型參數必須是指定的介面或實現指定的介面。可以指定多個介面約束。約束介面也可以是泛型的。 |
T:U |
為 T 提供的類型參數必須是為 U 提供的參數或派生自為 U 提供的參數。這稱為裸類型約束。 |
例子:
1.介面約束。
例如,可以聲明一個泛型類 MyGenericClass,這樣,類型參數 T 就可以實現 IComparable<T> 介面:
public class MyGenericClass<T> where T:IComparable { }
2.基類約束。
指出某個類型必須將指定的類作為基類(或者就是該類本身),才能用作該泛型類型的類型參數。這樣的約束一經使用,就必須出現在該類型參數的所有其他約束之前。
class MyClassy<T, U> where T : class where U : struct { }
3.構造函數約束。
以使用 new 運算符創建類型參數的實例;但類型參數為此必須受構造函數約束 new() 的約束。new() 約束可以讓編譯器知道:提供的任何類型參數都必須具有可訪問的無參數(或預設)構造函數。new() 約束出現在 where 子句的最後。
public class MyGenericClass <T> where T: IComparable, new() { T item = new T(); }
4.對於多個類型參數,每個類型參數都使用一個 where 子句。
interface MyI { } class Dictionary<TKey,TVal> where TKey: IComparable, IEnumerable where TVal: MyI { public void Add(TKey key, TVal val) { } }
5.還可以將約束附加到泛型方法的類型參數。
public bool MyMethod<T>(T t) where T : IMyInterface { }
6. 裸類型約束
用作約束的泛型類型參數稱為裸類型約束。當具有自己的類型參數的成員函數需要將該參數約束為包含類型的類型參數時,裸類型約束很有用。
class List<T> { void Add<U>(List<U> items) where U : T {} }
為什麼要有約束呢?
當一個泛型參數沒有任何約束時,它可以進行的操作和運算是非常有限的,因為不能對實參做任何類型上的保證,這時候就需要用到泛型的約束。泛型的主要約束和次要約束都是指泛型的實參必須滿足一定的規範。C#編譯器在編譯的過程中可以根據約束來檢查所有泛型類型的實參並確保其滿足約束條件。
一個泛型參數可以至多擁有一個主要約束,主要約束可以是一個引用類型、class或者struct。如果指定一個引用類型,則實參必須是該類型或者該類型派生類型。class規定實參必須是一個引用類型。struct規定了參數必須是一個之類新。以下代碼是泛型參數主要約束的示例。
using System; namespace Test { class GenericPrimaryConstraint { static void Main() { Console.Read(); } } //主要約束限定T繼承自Exception類型 public class ClassT1<T> where T : Exception { private T myException; public ClassT1(T t) { myException = t; } public override string ToString() { //主要約束保證了myException擁有Source成員 return myException.Source; } } //主要約束限定T是引用類型 public class ClassT2<T> where T : class { private T myT; public void Clear() { //T是引用類型,可以置null myT = null; } } //主要約束限定T是值類型 public class ClassT3<T> where T : struct { private T myT; public override string ToString() { //T是值類型,不會發生NullReferenceException異常 return myT.ToString(); } } }
以上代碼,泛型參數具備了主要約束後,就能夠在類型中對其進行一定的操作,否則任何演算法就只能基於一個System.Object類型的成員。
可以說,主要約束是實參類型的限定,而相對的次要約束,則是指實參實現的介面的限定。對於一個泛型類型,可以有0至無限的次要約束,次要約束規定了參數必須實現所有次要約束中規定的介面。次要約束的語法和主要約束基本一致,區別僅在於提供的不是一個引用類型而是一個或多個介面。
ps:同時擁有主要約束和次要約束的泛型參數,表示實參必須同時滿足主要約束和次要約束。
三、什麼是泛型集合?
字元串可以說是一個字元的集合,和字元串一樣,數據對象也可以是集合的方式存在,所以泛型類對象也可以是集合的方式存在(泛型集合)
同傳統的集合相比,泛型集合是一種強類型的集合,它解決了類型安全問題,同時避免了集合中每次的裝箱與拆箱的操作,提升了性能。
泛型集合類型:
1. List,這是我們應用最多的泛型種類,它對應ArrayList集合。
2. Dictionary,這也是我們平時運用比較多的泛型種類,對應Hashtable集合。
3. Collection對應於CollectionBase
4. ReadOnlyCollection 對應於ReadOnlyCollectionBase,這是一個只讀的集合。
5. Queue,Stack和SortedList,它們分別對應於與它們同名的非泛型類。
性能問題
下麵以ArrayList與List<T>為例說明泛型集合的優點及非泛型集合的缺點。例如,有這麼一段代碼:
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace Web { public partial class b : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { ArrayList numbers = new ArrayList(); numbers.Add(1);//裝箱 numbers.Add(2);//裝箱 int number = (int)numbers[1];//拆箱 Label1.Text = number.ToString(); } } }
這段代碼的背後會發生什麼呢?首先,ArrayList中將所有元素都看成Object類型的,是引用類型。調用Add方法增加兩個整數,在這個過程中,整數1,2被CLR裝箱(boxing)成object類型的,而後二個元素時又被拆箱(unboxing),裝箱與拆箱大體上會發生以下過程
1. 在托管堆中非配一個新的object
2. 基於棧(stack-based)的數據必須移動到剛非配的記憶體區中
3. 當拆箱時,位於堆中的數據又得移動到棧中
4. 堆中無用的數據進行垃圾回收
當涉及大量裝箱與拆箱操作時,必然會影響應用程式的性能。而是用泛型的集合類時就會減少裝箱與拆箱的工作,當存在大量數據時,自然可以提高很多性能。,比如用
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace Web { public partial class b : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { List<int> numbers = new List<int>();//找不同 numbers.Add(1); numbers.Add(2); int number = numbers[1];//找不同 Label1.Text = number.ToString(); } } }
類型安全問題
對於ArrayList,下麵的代碼編譯時時不會報錯的。
ArrayList numbers = new ArrayList(); numbers.Add(22); numbers.Add(35.5); numbers.Add(true); foreach (object item in numbers) { Console.WriteLine((int)item); }
因為可以將int類型,float等數值類型裝箱成object,因此即使ArrayList增加的數據類型不一致。編譯器也不會提示錯誤,但是運行時,會報錯。
但是如果是用泛型類型比如List<T>,那麼在編譯時就會進行類型檢查。防止運行時錯誤。
ps:此文章是本人參考網上內容加上自己的理解整合而成,如無意中侵犯了您的權益,請與本人聯繫。