泛型:通過參數(shù)化類型來實(shí)現(xiàn)在同一份代碼上操作多種數(shù)據(jù)類型。利用“參數(shù)化類型”將類型抽象化,從而實(shí)現(xiàn)靈活的復(fù)用。在.NET類庫中處處都可以看到泛型的身影,尤其是數(shù)組和集合中,泛型的存在也大大提高了程序員的開發(fā)效率。更重要的是,C#的泛型比C++的模板使用更加安全,并且通過避免裝箱和拆箱操作來達(dá)到性能提升的目的。因此,我們很有必要掌握并善用這個強(qiáng)大的語言特性。
C#泛型特點(diǎn):
1、如果實(shí)例化泛型類型的參數(shù)相同,那么JIT編輯器會重復(fù)使用該類型,因此C#的動態(tài)泛型能力避免了C++靜態(tài)模板可能導(dǎo)致的代碼膨脹的問題。
2、C#泛型類型攜帶有豐富的元數(shù)據(jù),因此C#的泛型類型可以應(yīng)用于強(qiáng)大的反射技術(shù)。
3、C#的泛型采用“基類、接口、構(gòu)造器,值類型/引用類型”的約束方式來實(shí)現(xiàn)對類型參數(shù)的“顯示約束”,提高了類型安全的同時,也喪失了C++模板基于“簽名”的隱式約束所具有的高靈活性
C#泛型繼承:
C#除了可以單獨(dú)聲明泛型類型(包括類與結(jié)構(gòu))外,也可以在基類中包含泛型類型的聲明。但基類如果是泛型類,它的類型要么以實(shí)例化,要么來源于子類(同樣是泛型類型)聲明的類型參數(shù),看如下類型
class C<U,V>
class D:C<string,int>
class E<U,V>:C<U,V>
class F<U,V>:C<string,int>
class G:C<U,V> //非法
E類型為C類型提供了U、V,也就是上面說的來源于子類
F類型繼承于C<string,int>,個人認(rèn)為可以看成F繼承一個非泛型的類
G類型為非法的,因?yàn)镚類型不是泛型,C是泛型,G無法給C提供泛型的實(shí)例化
泛型類型的成員:
泛型類型的成員可以使用泛型類型聲明中的類型參數(shù)。但類型參數(shù)如果沒有任何約束,則只能在該類型上使用從System.Object繼承的公有成員。如下圖:
泛型接口:
泛型接口的類型參數(shù)要么已實(shí)例化,要么來源于實(shí)現(xiàn)類聲明的類型參數(shù)
泛型委托:
泛型委托支持在委托返回值和參數(shù)上應(yīng)用參數(shù)類型,這些參數(shù)類型同樣可以附帶合法的約束
delegate bool MyDelegate<T>(T value);
class MyClass
{
static bool F(int i){...}
static bool G(string s){...}
static void Main()
{
MyDelegate<string> p2 = G;
MyDelegate<int> p1 = new MyDelegate<int>(F);
}
}
泛型方法:
1、C#泛型機(jī)制只支持“在方法聲明上包含類型參數(shù)”——即泛型方法。
2、C#泛型機(jī)制不支持在除方法外的其他成員(包括屬性、事件、索引器、構(gòu)造器、析構(gòu)器)的聲明上包含類型參數(shù),但這些成員本身可以包含在泛型類型中,并使用泛型類型的類型參數(shù)。
3、泛型方法既可以包含在泛型類型中,也可以包含在非泛型類型中。
泛型方法聲明:如下
public static int FunctionName<T>(T value){...}
泛型方法的重載:
public void Function1<T>(T a);
public void Function1<U>(U a);
這樣是不能構(gòu)成泛型方法的重載。因?yàn)榫幾g器無法確定泛型類型T和U是否不同,也就無法確定這兩個方法是否不同
public void Function1<T>(int x);
public void Function1(int x);
這樣可以構(gòu)成重載
public void Function1<T>(T t) where T:A;
public void Function1<T>(T t) where T:B;
這樣不能構(gòu)成泛型方法的重載。因?yàn)榫幾g器無法確定約束條件中的A和B是否不同,也就無法確定這兩個方法是否不同
泛型方法重寫:
在重寫的過程中,抽象類中的抽象方法的約束是被默認(rèn)繼承的。如下:
abstract class Base
{
public abstract T F<T,U>(T t,U u) where U:T;
public abstract T G<T>(T t) where T:IComparable;
}
class MyClass:Base
{
public override X F<X,Y>(X x,Y y){...}
public override T G<T>(T t) where T:IComparable{}
}
對于MyClass中兩個重寫的方法來說
F方法是合法的,約束被默認(rèn)繼承
G方法是非法的,指定任何約束都是多余的
泛型約束:
1、C#泛型要求對“所有泛型類型或泛型方法的類型參數(shù)”的任何假定,都要基于“顯式的約束”,以維護(hù)C#所要求的類型安全。
2、“顯式約束”由where子句表達(dá),可以指定“基類約束”,“接口約束”,“構(gòu)造器約束”,“值類型/引用類型約束”共四種約束。
3、“顯式約束”并非必須,如果沒有指定“顯式約束”,范型類型參數(shù)將只能訪問System.Object類型中的公有方法。例如:在開始的例子中,定義的那個obj成員變量。比如我們在開始的那個例子中加入一個Test1類,在它當(dāng)中定義兩個公共方法Func1、Func2,如下圖:
我們今天來討論下泛型的用法。首先說下泛型的概念,用通俗的語言來講,泛型其實(shí)就是類的一個參數(shù),但是要求參數(shù)必須是一個類,而不能是一個對象。很多人可能對泛型中T的作用不太理解,其中T在泛型中扮演的角色就相當(dāng)于一個占位符,確切的說,是類型占位符。凡是出現(xiàn)T的地方都會替換成你所傳遞的類型。
那么下面我們就來寫一個泛型的例子,讓大家體驗(yàn)一下泛型的威力。
首先咱們來看常用的List<T>泛型集合
01,List<T>集合,其中T可以是任何類型(int,string,數(shù)組類型,甚至是用戶自定義的類類型)
List<string> intList = new List<string>();
intList.Add("李曉玲");
intList.Add("章子怡");
foreach (string item in intList)
{
Console.WriteLine(item);
}
也可以在聲明List<T>時使用類似于數(shù)組初始化器的集合初始化器。
例如:List<string> nameList=new List<string>(){“Jack”,”Rose”,”Harvard”};
(該特性在.net3.0以及更高版本中使用)
List<T>常用方法:
1. 要向泛型中添加一個元素:使用Add()方法
添加多個元素:使用AddRange()方法
2.在指定位置插入元素使用Insert()方法
3.訪問元素可以通過索引,也可以使用foreach循環(huán)遍歷
4.刪除元素可以使用Remove()或者RemovAt()方法,使用Clear()方法可以刪除所有元素。
然后來看下鍵值對泛型集合Dictionary<key,value>
C#也為HashTable提供了泛型類型,即Dictionary<K,V>,通常稱為”字典”。
Dictionary<K,V>存儲數(shù)據(jù)的特點(diǎn):
1, 存儲數(shù)據(jù)的方式和哈希表類似,也是通過key/value保存元素。
2, 鍵必須是唯一的。不能為null,但是如果值為引用類型,該值可以為空。
主要屬性:count:獲取包含的鍵/值對數(shù)
Keys:鍵的集合
//02,Dictionary<K,V>集合
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("01", "李小龍");
dic.Add("02","李連杰");
//遍歷key
foreach (string item in dic.Keys)
{
Console.WriteLine("key"+item+" value"+dic[item]);
}
//一次性遍歷鍵和值
foreach (KeyValuePair<string,string> item in dic)
{
Console.WriteLine("key"+item.Key+" value"+item.Value);
}
這時候大家可能會有疑問,為什么要用泛型呢?
泛型有三個好處:
1,實(shí)現(xiàn)算法的重用。
在泛型出現(xiàn)之前,我們?yōu)榱吮WC性能安全而自定義強(qiáng)類型集合時,就需要為每種類型創(chuàng)建幾乎相同自定義集合。這樣就會重復(fù)勞動而且可維護(hù)性差。
2,避免拆箱和裝箱。
這點(diǎn)大家可以這樣理解,使用ArrayList和HashTable存取變量,會帶來頻繁的裝箱(將值類型轉(zhuǎn)換成引用類型)和拆箱(將引用類型轉(zhuǎn)換成值類型)操作,對性能有所影響。
3,類型安全(編譯時會自動檢測參數(shù)類型)
泛型的特點(diǎn):
泛型將操作的數(shù)據(jù)類型定義為一個參數(shù),類型參數(shù)使得設(shè)計如下類和方法成為可能。這些類和方法將一個或多個類型的指定推遲到客戶端代碼聲明并實(shí)例化類或方法的時候。
使用where約束類型參數(shù)
可以使用where約束類型參數(shù):
Where T:struct 中T必須在其繼承鏈中有System.ValueType值類型。
Where T:class 中T必須是引用類型
Where T:new()中T必須有一個默認(rèn)的構(gòu)造函數(shù)。在有多個約束的類型上,此約束必須列在末尾。
Where T:NameOfBaseClass中T必須派生于NameOfBaseClass指定的類。
當(dāng)然,泛型不僅能用在類上,也可單獨(dú)用在類的方法中,它可根據(jù)方法參數(shù)的類型自動適應(yīng)各種參數(shù),這樣的方法就叫做泛型方法。
Public class Stack2
{
Public void Push<T>(Stack<T> s, params T[] p)
{
Foreach(T t in p)
{
s.Push(t);
}
}
}
原來的類Stack一次只能Push一個數(shù)據(jù),這個類Stack2擴(kuò)展了Stack的功能,可以一次把多個數(shù)據(jù)壓入Stack中,其中Push是一個泛型方法。這個方法的調(diào)用示例如下:
Stack<int> stack=new Stack<int>(100);
Stack2 mystack2=new Stack2();
mystack2.Push(x,1,2,3);
string str=string.Empty;
for(int i=0;i<3;i++)
{
Str+=stack.Pop().ToString();
}
結(jié)果輸出str的值是64321