1、什麼是泛型?算法
泛型是C#語言和公共語言運行庫(CLR)中的一個新功能,它將類型參數的概念引入.NET Framework。類型參數使得設計某些類和方法成爲可能,例如,經過使用泛型類型參數T,能夠大大簡化類型之間的強制轉換或裝箱操做的過程(下一篇將說明如何解決裝箱、拆箱問題)。說白了,泛型就是經過參數化類型來實如今同一份代碼上操做多種數據類型,利用「參數化類型」將類型抽象化,從而實現靈活的複用。編程
使用泛型給代碼帶來的5點好處:一、能夠作大限度的重用代碼、保護類型的安全以及提升性能。數組
二、能夠建立集合類。安全
三、能夠建立本身的泛型接口、泛型方法、泛型類、泛型事件和泛型委託。ide
四、能夠對泛型類進行約束,以訪問特定數據類型的方法。函數
五、關於泛型數據類型中使用的類型的信息,可在運行時經過反射獲取。性能
例子:this
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(); } } }
輸出結果是:spa
int:2設計
String:hello world
分析:
一、 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:同時擁有主要約束和次要約束的泛型參數,表示實參必須同時知足主要約束和次要約束。
3、什麼是泛型集合?
字符串能夠說是一個字符的集合,和字符串同樣,數據對象也能夠是集合的方式存在,因此泛型類對象也能夠是集合的方式存在(泛型集合)
同傳統的集合相比,泛型集合是一種強類型的集合,它解決了類型安全問題,同時避免了集合中每次的裝箱與拆箱的操做,提高了性能。
泛型集合類型:
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>,那麼在編譯時就會進行類型檢查。防止運行時錯誤。