|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
不可能天天有学习.net),我一同学说,你应该早就有作品啦。我惶惶然……编程|教程|进门教程 在2005岁尾微软公司正式公布了C#2.0,与C#1.x比拟,新版本增添了良多新特征,个中最主要的是对泛型的撑持。经由过程泛型,我们能够界说范例平安的数据布局,而无需利用实践的数据范例。这能明显进步功能并失掉更高质量的代码。泛型并非甚么奇怪的器材,他在功效上相似于C++的模板,模板多年前就已存在C++上了,而且在C++上有大批成熟使用。
本文会商泛型利用的一样平常成绩,好比为何要利用泛型、泛型的编写办法、泛型中数据范例的束缚、泛型中静态成员利用要注重的成绩、泛型中办法重载的问、泛型办法等,经由过程这些使我们能够大抵懂得泛型并把握泛型的一样平常使用,编写出更复杂、通用、高效的使用体系。
甚么是泛型
我们在编写程序时,常常碰到两个模块的功效十分类似,只是一个是处置int数据,另外一个是处置string数据,大概其他自界说的数据范例,但我们没有举措,只能分离写多个办法处置每一个数据范例,由于办法的参数范例分歧。有无一种举措,在办法中传进通用的数据范例,如许不就能够兼并代码了吗?泛型的呈现就是专门办理这个成绩的。读完本篇文章,你会对泛型有更深的懂得。
为何要利用泛型
为了懂得这个成绩,我们先看上面的代码,代码省略了一些内容,但功效是完成一个栈,这个栈只能处置int数据范例:
publicclassStack
{
privateint[]m_item;
publicintPop(){...}
publicvoidPush(intitem){...}
publicStack(inti)
{
this.m_item=newint;
}
}
下面代码运转的很好,可是,当我们必要一个栈来保留string范例时,该怎样办呢?良多人城市想到把下面的代码复制一份,把int改成string不就好了。固然,如许做自己是没有任何成绩的,但一个优异的程序是不会如许做的,由于他想到若今后再必要long、Node范例的栈该如何做呢?还要再复制吗?优异的程序员会想到用一个通用的数据范例object来完成这个栈:
publicclassStack
{
privateobject[]m_item;
publicobjectPop(){...}
publicvoidPush(objectitem){...}
publicStack(inti)
{
this.m_item=new;
}
}
这个栈写的不错,他十分天真,能够吸收任何数据范例,能够说是与日俱增。但周全地讲,也不是没出缺陷的,次要体现在:
当Stack处置值范例时,会呈现装箱、折箱操纵,这将在托管堆上分派和接纳大批的变量,若数据量年夜,则功能丧失十分严峻。
在处置援用范例时,固然没有装箱和折箱操纵,但将用到数据范例的强迫转换操纵,增添处置器的包袱。
在数据范例的强迫转换上另有更严峻的成绩(假定stack是Stack的一个实例):
Node1x=newNode1();
stack.Push(x);
Node2y=(Node2)stack.Pop();
下面的代码在编译时是完整没成绩的,但因为Push了一个Node1范例的数据,但在Pop时却请求转换为Node2范例,这将呈现程序运转时的范例转换非常,但却逃离了编译器的反省。
针对object范例栈的成绩,我们引进泛型,他能够文雅地办理这些成绩。泛型用用一个经由过程的数据范例T来取代object,在类实例化时指定T的范例,运转时(Runtime)主动编译为当地代码,运转效力和代码质量都有很年夜进步,而且包管数据范例平安。
利用泛型
上面是用泛型来重写下面的栈,用一个通用的数据范例T来作为一个占位符,守候在实例化时用一个实践的范例来取代。让我们来看看泛型的能力:
publicclassStack<T>
{
privateT[]m_item;
publicTPop(){...}
publicvoidPush(Titem){...}
publicStack(inti)
{
this.m_item=newT;
}
}
类的写法稳定,只是引进了通用数据范例T就能够合用于任何数据范例,而且范例平安的。这个类的挪用办法:
//实例化只能保留int范例的类
Stack<int>a=newStack<int>(100);
a.Push(10);
a.Push("8888");//这一行编译欠亨过,由于类a只吸收int范例的数据
intx=a.Pop();
//实例化只能保留string范例的类
Stack<string>b=newStack<string>(100);
b.Push(10);//这一行编译欠亨过,由于类b只吸收string范例的数据
b.Push("8888");
stringy=b.Pop();
这个类和object完成的类有一模一样的区分:
1.他是范例平安的。实例化了int范例的栈,就不克不及处置string范例的数据,其他数据范例也一样。
2.无需装箱和折箱。这个类在实例化时,依照所传进的数据范例天生当地代码,当地代码数据范例已断定,以是无需装箱和折箱。
3.无需范例转换。
<P> 泛型类实例化的实际
C#泛型类在编译时,师长教师成两头代码IL,通用范例T只是一个占位符。在实例化类时,依据用户指定的数据范例取代T并由立即编译器(JIT)天生当地代码,这个当地代码中已利用了实践的数据范例,同等于用实践范例写的类,以是分歧的关闭类的当地代码是纷歧样的。依照这个道理,我们能够如许以为:
泛型类的分歧的关闭类是分离分歧的数据范例。
例:Stack<int>和Stack<string>是两个完整没有任何干系的类,你能够把他当作类A和类B,这个注释对泛型类的静态成员的了解有很年夜匡助。
泛型类中数据范例的束缚
程序员在编写泛型类时,老是会对通用数据范例T举行成心或偶然地有设想,也就是说这个T一样平常来讲是不克不及顺应一切范例,但如何限定挪用者传进的数据范例呢?这就必要对传进的数据范例举行束缚,束缚的体例是指定T的先人,即承继的接口或类。由于C#的单根承继性,以是束缚能够有多个接口,但最多只能有一个类,而且类必需在接口之前。这时候就用到了C#2.0的新增关头字:
publicclassNode<T,V>whereT:Stack,IComparable
whereV:Stack
{...}
以上的泛型类的束缚标明,T必需是从Stack和IComparable承继,V必需是Stack或从Stack承继,不然将没法经由过程编译器的范例反省,编译失利。
通用范例T没有特指,但由于C#中一切的类都是从object承继来,以是他在类Node的编写中只能挪用object类的办法,这给程序的编写形成了坚苦。好比你的类计划只必要撑持两种数据范例int和string,而且在类中必要对T范例的变量对照巨细,但这些却没法完成,由于object是没有对照巨细的办法的。懂得决这个成绩,只需对T举行IComparable束缚,这时候在类Node里就能够对T的实例实行CompareTo办法了。这个成绩能够扩大到其他用户自界说的数据范例。
假如在类Node里必要对T从头举行实例化该怎样办呢?由于类Node中不晓得类T究竟有哪些机关函数。为懂得决这个成绩,必要用到new束缚:
publicclassNode<T,V>whereT:Stack,new()
whereV:IComparable
必要注重的是,new束缚只能是无参数的,以是也请求响应的类Stack必需有一个无参机关函数,不然编译失利。
C#中数据范例有两年夜类:援用范例和值范例。援用范例如一切的类,值范例通常为言语的最基础范例,如int,long,struct等,在泛型的束缚中,我们也能够年夜局限地限定范例T必需是援用范例或必需是值范例,分离对应的关头字是class和struct:
publicclassNode<T,V>whereT:class
whereV:struct
泛型办法
泛型不但能感化在类上,也可独自用在类的办法上,他可依据办法参数的范例主动顺应各类参数,如许的办法叫泛型办法。看上面的类:
publicclassStack2
{
publicvoidPush<T>(Stack<T>s,paramsT[]p)
{
foreach(Ttinp)
{
s.Push(t);
}
}
}
本来的类Stack一次只能Push一个数据,这个类Stack2扩大了Stack的功效(固然也能够间接写在Stack中),他能够一次把多个数据压进Stack中。个中Push是一个泛型办法,这个办法的挪用示比方下:
Stack<int>x=newStack<int>(100);
Stack2x2=newStack2();
x2.Push(x,1,2,3,4,6);
strings="";
for(inti=0;i<5;i++)
{
s+=x.Pop().ToString();
}//至此,s的值为64321
泛型中的静态成员变量
在C#1.x中,我们晓得类的静态成员变量在分歧的类实例间是共享的,而且他是经由过程类名会见的。C#2.0中因为引进了泛型,招致静态成员变量的机制呈现了一些变更:静态成员变量在不异关闭类间共享,分歧的关闭类间不共享。
这也十分简单了解,由于分歧的关闭类固然有不异的类称号,但因为分离传进了分歧的数据范例,他们是完整分歧的类,好比:
Stack<int>a=newStack<int>();
Stack<int>b=newStack<int>();
Stack<long>c=newStack<long>();
类实例a和b是统一范例,他们之间共享静态成员变量,但类实例c倒是和a、b完整分歧的范例,以是不克不及和a、b共享静态成员变量。
泛型中的静态机关函数
静态机关函数的划定规矩:只能有一个,且不克不及有参数,他只能被.NET运转时主动挪用,而不克不及野生挪用。
泛型中的静态机关函数的道理和非泛型类是一样的,只需把泛型中的分歧的关闭类了解为分歧的类便可。以下两种情形可引发静态的机关函数:
1.特定的关闭类第一次被实例化。
2.特定关闭类中任一静态成员变量被挪用。
<P> 泛型类中的办法重载
办法的重载在.NetFramework中被大批使用,他请求重载具有分歧的署名。在泛型类中,因为通用范例T在类编写时其实不断定,以是在重载时有些注重事项,这些事项我们经由过程以下的例子申明:
publicclassNode<T,V>
{
publicTadd(Ta,Vb)//第一个add
{
returna;
}
publicTadd(Va,Tb)//第二个add
{
returnb;
}
publicintadd(inta,intb)//第三个add
{
returna+b;
}
}
下面的类很分明,假如T和V都传进int的话,三个add办法将具有一样的署名,但这个类仍旧能经由过程编译,是不是会引发挪用搅浑将在这个类实例化和挪用add办法时判别。请看上面挪用代码:
Node<int,int>node=newNode<int,int>();
objectx=node.add(2,11);
这个Node的实例化引发了三个add具有一样的署名,但却能挪用乐成,由于他优先婚配了第三个add。但假如删除第三个add,下面的挪用代码则没法编译经由过程,提醒办法发生的搅浑,由于运转时没法在第一个add和第二个add之间选择。
Node<string,int>node=newNode<string,int>();
objectx=node.add(2,"11");
这两行挪用代码可准确编译,由于传进的string和int,使三个add具有分歧的署名,固然能找到独一婚配的add办法。
由以上示例可知,C#的泛型是在实例的办法被挪用时反省重载是不是发生搅浑,而不是在泛型类自己编译时反省。同时还得出一个主要准绳:
当一样平常办法与泛型办法具有不异的署名时,会掩盖泛型办法。
泛型类的办法重写
办法重写(override)的次要成绩是办法署名的辨认划定规矩,在这一点上他与办法重载一样,请参考泛型类的办法重载。
泛型的利用局限
本文次要是在类中报告泛型,实践上,泛型还能够用在类办法、接口、布局(struct)、托付等下面利用,利用办法大抵不异,就不再报告。
小结
C#泛型是开辟工具库中的一个价值千金。它们能够进步功能、范例平安和质量,削减反复性的编程义务,简化整体编程模子,而这统统都是经由过程文雅的、可读性强的语法完成的。只管C#泛型的基本是C++模板,但C#经由过程供应编译时平安和撑持将泛型进步到了一个新程度。C#使用了两阶段编译、元数据和诸履约束和一样平常办法之类的立异性的观点。毫无疑问,C#的未来版本将持续开展泛型,以便增加新的功效,而且将泛型扩大到诸如数据会见或当地化之类的其他.NETFramework范畴。
C#中有两处地方用到new关键字,第一处也是最常见的一处是用在调用构造函数的时候,这种情况也是大家见的最多的一种。另一处是用在派生类中,作用有隐藏成员,切断继承关系等,相信第二处的用法大家明显要比第一处生疏。 |
|