配色: 字号:
泛型
2017-12-16 | 阅:  转:  |  分享 
  
泛型要点:泛型概述创建泛型类泛型类的特性泛型接口泛型结构泛型方法代码下载:http://www.wrox.com/go/procsharp泛
型概述泛型不仅是C#编程语言的一部分,而且与程序集中的IL代码紧密集成。有了泛型,就可以创建独立于被包含类型的类的方法了。为们不必
为不同的类型编写相同功能的许多方法或类,只创建一个方法或类即可。另一个减少代码的选项是使用Object类,但Object类不是类型
安全的。泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型。这就保证了类型安全性:如果某个类不支持泛型类,编译器就会报错。泛型
不仅限于类,本章还将介绍用于接吕和方法的泛型。泛型与C++中的模板很相似。但是,C++模板和泛型还是有很大区别:对于C++模板,在
用特定的类型实例化模板时,需要模板的源码。相反,泛型不仅是C#的一种结构,而且是CLR定义的。所以,即使泛型在C#中定义,在Vi
sualBasic中也可以用一个特定类实例化泛型。这表示不需要C#源代码也可以实例化泛型类下面几节将将介绍泛型的优点和缺点,尤其
是:性能类型安全二进制代码重用代码的拓展命名约定性能泛型的一个主要优点是性能。System.Collections(非泛型集合类
)和System.Collections.Generic(泛型集合类)。对于值类型使用非泛型集合类,在把值类型转换成引用类型,和把
引用类型转换为值类型时,需要进行装箱和拆箱操作。ArrayListlist=newArrayList();list.Ad
d(44);//装箱--把值类型转换成引用类型inti1=(int)list[0];//拆箱---把引用类型转换成值类型
、装箱和拆箱操作很容易实现,但性能损失较大,遍历许多项时尤其如此。List不使用对象,而是在使用时定义类型,下面的例子中,L
ist的泛型被定义成int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作:ListTl
ist=newList();Tlist.Add(44);类型安全ArrayListlist=newArr
ayList();list.Add(44);//装箱--把值类型转换成引用类型inti1=(int)list[0];/
/拆箱---把引用类型转换成值类型list.Add("hello");foreach(intiteminlist){
//因为"hello"是一个字符串,强制转成int时,会在运行时抛出异常Console.WriteLine(item);
}错误应尽早发现,如果用List:Listlist=newList();list.Add(44
);inti1=(int)list[0];list.Add("hello");//编译错误foreach(int
iteminlist){Console.WriteLine(item);}二进制代码重用泛型允许更好地重用二进制代码。
泛型类可以定义一次,并且可以用许多不同的类型实例化。不需要像C++模板那样访问源码。泛型类型可以在一种语言中定义,在任何其它.ne
t语言中使用。代码的拓展在用不同的给特定类型实例化时,会创建多少代码?因为泛型类会放在程序集中,所以用特定类实例化泛型类不会在IL
代码中复制这些类。但是,在JIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类。引用类型共享同一个本地类的所有相同实现
的代码。命名约定以下是泛型类型的命名规则:1.泛型类型的名称用T作为前缀2.如果没有特殊要求,泛型可以用任类型代替,且只使用了一个
泛型类型,就可以用T作为泛型类型的名称。publicclasslist{}如果泛型有特定要求(例如,它必须实现一个接口
或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称:publicdelegatevoidEvent
Handler(objectsender,TEventArgse);publicclassSor
tList{}创建泛型类首先介绍一个一般的、非泛型的简化链表类,以后再把这个类转成泛型类。usin
gSystem;usingSystem.Collections;usingSystem.Collections.Generi
c;usingSystem.Linq;usingSystem.Text;namespace非泛型的简单链表{public
classLinkedList:IEnumerable{publicLinkedListNodeFirst{ge
t;privateset;}publicLinkedListNodeLast{get;privateset;}
publicLinkedListNodeAddLast(objectnode){varnewNode=newLi
nkedListNode(node);if(First==null){First=newNode;Last=n
ewNode;}else{LinkedListNodeprevious=Last;Last.Next=newN
ode;Last=newNode;Last.Prev=previous;}returnnewNode;}pu
blicIEnumeratorGetEnumerator(){LinkedListNodecurrent=First
;while(current!=null){yieldreturncurrent.Value;current=
current.Next;}}}publicclassLinkedListNode{publicLinkedL
istNode(objectvalue){Value=value;}publicobjectValue{ge
t;privateset;}publicLinkedListNodeNext{get;internalset;
}publicLinkedListNodePrev{get;internalset;}}}使用这个类:Link
edListlist=newLinkedList();list.AddLast(1);list.AddLast("6"
);foreach(intiteminlist){Console.WriteLine(item);}此处会抛出就个
异常创建泛型类usingSystem;usingSystem.Collections;usingSystem.Collect
ions.Generic;usingSystem.Linq;usingSystem.Text;namespace简单链表{
publicclassLinkedList:IEnumerable{publicLinkedListNo
deFirst{get;privateset;}publicLinkedListNodeLast{
get;privateset;}publicLinkedListNodeAddLast(Tnode){var
newNode=newLinkedListNode(node);if(First==null){First
=newNode;Last=newNode;}else{LinkedListNodeprevious=
Last;Last.Next=newNode;Last=newNode;Last.Prev=previous;
}returnnewNode;}publicIEnumeratorGetEnumerator(){Link
edListNodecurrent=First;while(current!=null){yieldre
turncurrent.Value;current=current.Next;}}IEnumeratorIEnum
erable.GetEnumerator(){returnGetEnumerator();}}publicclas
sLinkedListNode{publicLinkedListNode(Tvalue){Value=va
lue;}publicTValue{get;privateset;}publicLinkedListNode
Next{get;internalset;}publicLinkedListNodePrev{g
et;internalset;}}}LinkedListlist=newLinkedList(
);list.AddLast(1);list.AddLast("6");foreach(intiteminlist)
{Console.WriteLine(item);}编译时报错泛型类的功能在创建泛型类时,还需要一些其它C#关键字。例如,不
能把null赋给泛型。此时,如下一节所述,可以用default关键字。如果泛型不需要Object类的功能,但需要调用泛型类上的某些
特定方法,就可以定义约束。本节讨论如下主题:默认值约束继承静态成员先介绍一个文档管理器的示例usingSystem;using
System.Collections.Generic;usingSystem.Linq;usingSystem.Text;na
mespace文档管理器{publicclassDocumentManager{privatereadonly
QueuedocumentQueue=newQueue();publicvoidAddDocument
(Tdoc){lock(this){documentQueue.Enqueue(doc);}}publicbo
olIsDocumentAvaliable{get{returndocumentQueue.Count>0;}
}}}默认值现在给DocumentManager添加GetDocument()方法publicTGetDocument
(){Tdoc=default(T);lock(this){doc=documentQueue.Deque
ue();}returndoc;}不能把null赋予泛型类型。原因是泛型也可以实例化为值类型,而null只能用于引用类型。
为解决这个问题,可以使用default关键字,它将null赋予引用,将0赋予值类型。约束约束实例化泛型类型的具体类必须满足某些条件
对于DocumentManager,文档的所标题应在DispalyAllDocuments()方法中显示。Document类
实现带Title和Content属性的IDocument接口:usingSystem;usingSystem.Collecti
ons.Generic;usingSystem.Linq;usingSystem.Text;namespace文档管理器{
publicinterfaceIDocument{stringTitle{get;set;}stringCo
ntent{get;set;}}publicclassDocument:IDocument{public
stringContent{get;set;}publicstringTitle{get;set;}}}
要使用DocumentManager类型显示文档,可以将类型强转成IDocument接口publicvoidDispal
yAllDocuments(){foreach(TitemindocumentQueue){Console.Wri
teLine(((IDocument)item).Title);}}问题是,如果T没有实现IDocument接口,强转时就会出
现一个异常。最好给DocumentManager定义一个约束:TDocument必须实现IDocument接
口:publicclassDocumentManagerwhereTDocument:IDocume
ntDispalyAllDocuments可以必成这样:publicvoidDispalyAllDocuments(){f
oreach(TDocumentitemindocumentQueue){Console.WriteLine(item
.Title);}}泛型支持的几种约束类型:约束说明whereT:structT必须是值类型whereT:classT必须
是引用类型whereT:IFooT必须实现IFoo接口whereT:FooT必须派生自基类FoowhereT:new()T必
须有一个默认的构造函数whereT1:T2T1派生自派泛型类型T2,也称为裸约束使用泛型类型还可以合并多个约束:publicc
lassDocumentManagerwhereTDocument:IDocument,new()
TDocument必须实现IDocument接口且必须有一个默认的构造函数。继承前面创建的LinkedList实现了IEnu
merablepublicclassLinkedList:IEnumerable泛型类型可以实现泛型接口
,也可以派生自一个类。泛型类可以派生自泛型基类:publicclassBase{}publicclassDe
rvied:Base{}其要求是必须重复接口的泛型类型,或者必须指定基类的类型:publicclassBase
{}publicclassDervied:Base{}于是派生类可以是泛型类或非泛型类。
静态成员泛型的静态成员只能在类的一个实例中共享。Base.x=0;Base.x+=1;
Base.x=10;Console.WriteLine(Base.x);//1Console
.WriteLine(Base.x);//10publicclassBase{staticpublic
intx;}泛型接口使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。.Net为不同的情况提供了泛型接口,如ICompa
rableICollection等,同一接口常常存在比较老的非泛型版本。如IComparable。协变和抗变在.ne
t4之前,泛型接口是不变的..net4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变是指对参数和返回值的类型
进行转换。例如,可以栓一个需要Shape的参数的方法传递Rectangle参数吗?下面的示例说明这些扩展的优点。在.net中,参数
类型是协变的。假定:publicvoidDisplay(Shapeo){}现在可以传递派生自Shape基类的任意对象。
varr=newRectangle(){Width=10,Height=10};Display(r);方
法的返回值类型是抗变的。当一个方法返回值是Shape时,就不能把它赋给Rectangle因为Shape不一定总是Rectangle
。反过来是可行的。publicRectangleGetRectangle();可以把返回值赋给ShapeShapes=G
etRectangle();//完全可行下面定义,Shape基类和Rectangle类:usingSystem;usingS
ystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;nam
espace泛型接口{publicclassShape{publicdoubleWidth{get;set;
}publicdoubleHeight{get;set;}publicoverridestringToSt
ring(){returnString.Format("Width:{0},Height{1}",Width,Height)
;}}}usingSystem;usingSystem.Collections.Generic;usingSystem.
Linq;usingSystem.Text;namespace泛型接口{publicclassRectangle:Sha
pe{}}泛型接口的协变如果泛型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T.接口IIndex与类型T是
协变的,并从一个只读索引器中返回这个类型:interfaceIIndex{Tthis[intindex]
{get;}intCount{get;}}usingSystem;usingSystem.Collections
.Generic;usingSystem.Linq;usingSystem.Text;namespace泛型接口{clas
sRectangleCollection:IIndex{privateRectangle[]d
ata=newRectangle[]{newRectangle(){Height=2,Width=5},
newRectangle(){Height=3,Width=7},newRectangle(){Height
=2.5,Width=2.9},};privatestaticRectangleCollectioncoll;
publicstaticRectangleCollectionGetRectangles(){//??运算符称作nu
ll合并运算符。如果此运算符的左操作数不为null,则此运算符将返回左操作数;否则返回右操作数。returncoll??
(coll=newRectangleCollection());}publicRectanglethis[int
index]{get{if(index<0||index>data.Length){thrownew
ArgumentOutOfRangeException("index");}returndata[index];}}p
ublicintCount{get{returndata.Length;}}}}usingSystem;us
ingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Tex
t;namespace泛型接口{classProgram{staticvoidMain(string[]args)
{IIndexrectangles=RectangleCollection.GetRectangl
es();IIndexshapes=rectangles;for(inti=0;ies.Count;i++){Console.WriteLine(shapes[i]);}}}}因为IIndextangle>是协变的,所以也可以把返回值赋给IIndex泛型接口的抗变usingSystem;usingSys
tem.Collections.Generic;usingSystem.Linq;usingSystem.Text;names
pace泛型接口{interfaceIDispaly{voidShow(Titem);}}using
System;usingSystem.Collections.Generic;usingSystem.Linq;usingS
ystem.Text;namespace泛型接口{classShapeDispaly:IDispaly{
publicvoidShow(Shapeitem){Console.WriteLine(item.GetType().
Name+":"+item.ToString());}}}usingSystem;usingSystem.Coll
ections.Generic;usingSystem.Linq;usingSystem.Text;namespace泛型接
口{classProgram{staticvoidMain(string[]args){IIndexngle>rectangles=RectangleCollection.GetRectangles();IIndexape>shapes=rectangles;for(inti=0;i{Console.WriteLine(shapes[i]);}IDispalyshape=newSha
peDispaly();IDispalyrect=shape;rect.Show(rectangl
es[0]);}}}因为IDispaly是抗变的所以IDispalyrect=shape;合法;输
入---》抗变方法参数类型要求为基类,但可以传入它的派生类输出---》协变Shape一定是Rectangle泛型结构与类
相似,结构也可以是泛型的。它们非常类似于泛型类,只是没继续特性。本节介绍泛型结构Nullable,它同。NETFramew
ork定义。数据库中的数字和编程语言中的数字在显著不同的特征,因为数据库的数字是可以为空,而C#中的数字不能为空。Int32是一个
结构,而结构的实现同值类型,所以结构不能为空。这种区别常常令人很头痛,映射数据也要做多许多辅助工作。这个问题不仅存在于数据库中,也
存在于把XML数据映射到.net类型。一种决解办法是把数据库和XML文件中的数字映射为引用类型,因为引用类型可以为空值。但这也会在
运行期间带来额外的系统开销。使用Nullable结构很容易解决这个问题。int?可空类型的定义泛型方法voidSwap
(refTx,refTy){Ttemp=x;x=y;y=temp;}inti=4;i
ntj=5;Swap(refi,refj);//因为C#编译器会通过Swap()方法来获取参数类型,所以不
需要把泛型类开赋予方法调用//泛型方法可以像非泛型好样调用Swap(refi,refj);泛型方法的示例usingSys
tem;usingSystem.Collections.Generic;usingSystem.Linq;usingSyst
em.Text;namespace泛型方法{publicclassAccount{stringname;decim
albalance;publicstringName{get{returnname;}set{name
=value;}}publicdecimalBalance{get{returnbalance;}set
{balance=value;}}publicAccount(stringname,decimalbalan
ce){this.Name=name;this.Balance=balance;}}}usingSystem;
usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.T
ext;namespace泛型方法{staticpublicclassAlgirithm{publicstatic
decimalAccumulateSimple(IEnumerablesoruce){decimal
sum=0;foreach(variteminsoruce){sum+=item.Balance;}re
turnsum;}}}usingSystem;usingSystem.Collections.Generic;using
System.Linq;usingSystem.Text;namespace泛型方法{classProgram{st
aticvoidMain(string[]args){varaccounts=newList(
){newAccount("001",1000),newAccount("002",1040),newAccount
("003",5000),newAccount("004",1060),};Console.WriteLine(Algir
ithm.AccumulateSimple(accounts));}}}带约束的泛型方法第一个实现代码的问题是,它只能用于Ac
count对象。使用泛型方法就可以解决这个问题。Accumulate()方法的第二个版本实现了IAccount接口的任意类型。publicstaticdecimalAccumulateSimple(IEnumerablesoruce)whereTAccount:IAccount{decimalsum=0;foreach(variteminsoruce){sum+=item.Balance;}returnsum;}给定约束:TAccount必须实现IAccount接口带委托的泛型方法泛型类型实现IAccount接口要求过于严格。下面的示例演示了,如何通过传递一个泛型委托来修改Acconmulate()方法.publicstaticT2AccumulateSimple(IEnumerablesoruce,Funcaction){T2sum=default(T2);foreach(variteminsoruce){sum=action(item,sum);}returnsum;}调用如下:Console.WriteLine(Algirithm.AccumulateSimple(accounts,(item,sum)=>{returnsum+=item.Balance;}));泛型方法规范泛型方法可以重载,为特定的类型定义规范,这也适用于带泛型参数的方法。Foo()定义了两个版本,第1版,参数是一个泛型参数,第2版用于int参数的专用版本。publicstaticvoidFoo(Tval){}publicstaticvoidFoo(intval){}
献花(0)
+1
(本文系luan_it首藏)