资源描述
,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,2012/12/25,#,单击此处编辑母版标题样式,第,16,章 泛型,泛型是,C#,2.0,的一个新增加的特性,是,C#,编程中不可缺少的部分。它是程序设计语言的一种特性,为使用,C#,语言编写面向对象程序增加了极大的,有效性,和灵活性。它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。泛型的功能非常重要,因为通过使用泛型创建的类、结构、接口、方法和委托能过以一种类型安全的方式操作各种数据。许多算法在逻辑上是相同的。本章将详细介绍泛型的语法、理论以及用法。,16.1,泛型简介,我们在编写程序时,经常会遇到两个模块功能非常相似,但方法的参数类型不同的情况。例如下面的例子:,在本例中,我们在,MyClass,类中写了一个参数为,int,型的方法,F(),,其作用是输出,i,是,int,类型。上面的代码运行地很好,但是当我们需要一个参数为,string,类型的方法时,该怎么做?很多人会想到把方法,F(),复制一份,将参数类型由,int,改为,string,就可以了。这样做本身是没有问题的,但是如果我们还需要,long,、,double,类型作为参数时该怎么做呢?还是复制吗?有没有办法在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就解决了这个问题。泛型使用一个通用的数据类型,T,来代替,object,,在类实例化时指定,T,的类型,运行时自动编译为本地代码,运行效率和代码质量都有很大,提高,。,声明泛型类的语法如下:,class class-name ,声明一个对泛型类的引用的语法如下:,class-name var-name=new class-name(cons-arg-list);,下面的泛型用来重写上面的例子,用一个通用的数据类型,T,来作为一个占位符,等待在实例化时用一个实际的类型来代替。,16.2,类型约束,可以使用任意类型替换类型形参。例如:,class Gen ,可以为,T,指定任意类型。因此,在创建,Gen,对象时使用,int,、,double,、,string,、,FileStream,或者任意其他类型替换,T,都是合法的。尽管对类型实参不加限制在大多数情况下都适用,但是在某些情况下,限制可以用作类型实参的类型也是必要的。例如,不允许使用,int,类型作为类型实参。为了处理某些情况,,C#,提供了类型约束。在指定一个类型形参是,可以指定类型形参必须满足的约束条件。通过在指定类型形参时使用,where,子句来实现指定。,C#,定义了如下类型的约束:,基类约束,接口约束,构造函数约束,引用类型约束,值类型约束,16.2.1,基类约束,基类约束有两个功能。首先,它允许在泛型类中使用自由约束指定的基类所定义的成员。通过提高基类约束,编译器将知道所有的类型实参都拥有由指定的基类所定义的成员。基类约束的第二个功能是,确保只使用支持指定基类的类型实参。这意味着对于任意给定的基类约束,类型实参必须是基类本身或者是派生于该基类的类。基类约束使用如下形式的,where,子句:,where T,:,base-class-name,其中,,T,是类型形参的名称,,base-class-name,是基类的名称。只能指定一个基类。,【,示例,16-1】,演示基类约束的用法。,16.2.2,接口约束,接口约束是指定某个类型实参必须实现的接口。它的两个主要功能与基类约束一样,允许开发人员在泛型类中使用接口的成员;确保只能使用实现了特定接口的类型实参。也就是说对任何给定的接口约束,类型实参必须是接口本身或者实现了该接口的类。接口约束使用的,where,子句具有以下形式:,where T:interface-name,其中,,T,是类型形参的名称,,interface-name,是接口的名称。可以使用逗号分隔开指定的多个接口。若某个约束同时包含基类和接口,则需先指定基类列表,再指定接口列表。,【,示例,16-2】,下面的程序用来说明接口约束的用途。,在本例中,,Test,类指定的接口约束要求类型实参实现,MyClass1,接口。因为,MyClass2,类实现了,MyClass1,接口,所有它是绑定给,T,的有效类型。倘若定义一个,MyClass3,类,没有实现,MyClass1,的接口,因此不可以绑定给,T,。此时,程序不能通过编译。,16.2.3 new(),构造函数约束,new(),构造函数约束允许开发人员实例化一个泛型类型的对象。,new(),约束要求类型实参必须提供一个无参数的公有构造函数。使用,new(),约束时,可以通过调用该无参数的构造函数来创建对象。,new(),构造函数约束的形式为:,where T:new(),使用,new(),约束时应当注意,3,点:,1.new(),约束可以与其他约束一起使用,但必须位于约束列表的末端。,2.new(),约束仅允许开发人员使用无参数的构造函数构造一个对象,即使同时存在其他的构造函数也是如此。即不允许给类型形参的构造函数传递实参。,3.,不可以同时使用,new(),约束和值类型约束。,【,示例,16-3】,下面通过一个简单是示例来说明,new(),构造函数约束的用法。,16.2.4,引用类型约束和值类型约束,如果引用类型和值类型之间的差别对于泛型代码非常重要,引用类型约束和值类型约束就很有用。当同时存在其他约束时,引用类型约束或值类型约束必须位于约束列表的开头。,1.,引用类型约束将一个类型形参限定为引用类型。引用类型一般是用户定义的类型,包含类、接口、委托、字符串和数组类型。引用类型约束使用,class,关键字,它的通用形式为,where T:class,在这个,where,子句中,,class,关键字是指定,T,必须是引用类型。因此,尝试对,T,使用值类型,将会导致编译错误。,【,示例,16-4】,下面的代码展示如何实现引用类型约束。,在本例中,泛型类,Test,中的第二个,class,关键字是指定泛型类型的参数,T,必须是引用类型。而且,还可以指定一个逗号分隔符的接口约束列表,来指定该类型必须实现的接口,比如示例中的接口,MyClass1,。,2.,值类型约束将一个类型形参限定为值类型。值类型派生于,System.ValueType,类型。基元和结构都是值类型。值类型约束使用,struct,关键字,它的通用形式为:,where T:struct,在该形式中,,struct,关键字指定,T,必须是值类型。因此,尝试对,T,使用引用类型,将导致编译错误。,【,示例,16-5】,下面的代码展示如何实现值类型约束。,在本例中,泛型类,Test,中的,struct,关键字是指定泛型类型的参数,T,必须是值类型。,Test,和,Test,都是有效的,而,Test,不是有效的。要验证这点,可以删除类,Program,中的注释符号,然后重新编译程序,此时将产生错误。,16.2.5,使用约束建立两个类型形参之间的关系,基类约束有一个变体形式,它允许开发人员在两个类型形参之间建立一种关系。仔细观察如下声明:,class Testwhere V:T,在该声明中,,where,子句告诉编译器绑定给,V,的类型实参必须等同于或者继承于传递给,T,的类型实参。如果在声明,Test,类型的对象时这种关系不存在,那么将导致编译错误。如上面所示,使用这种类型形参的约束称为“裸类型约束”。,【,示例,16-6】,下面的程序演示了这种约束。,在本例中,,MyClass2,类继承了,MyClass1,类,所以,Test t=new Test();,是合法的。倘若将,MyClass1,和,MyClass2,的位置调换,就会出错,因为,MyClass1,类没有继承,MyClass2,类。,16.2.6,使用多个约束,同一个类型形参可以使用多个约束。这种情况下,需要使用一个由逗号分隔的约束列表。在该列表中,第一个约束必须是引用类型约束或者值类型约束,或者是基类约束。指定引用类型约束或值类型约束的同时也指定基类约束是非法的。接下来必须是所有的接口约束,最后是,new(),约束。下面是一个合法的声明:,class Testwhere T:MyClass,Iinterface,new(),在上述声明中,用于替换,T,的类型实参必须继承,MyClass,类,实现,Iinterface,接口,并且拥有一个无参数的构造函数。,在使用两个或者更多的类型形参时,可以使用多条,where,子句分别为它们指定约束。,【,示例,16-7】,下面的程序演示了这种约束。,16.3,泛型结构,C#,允许创建泛型结构。它们非常类似于泛型类,只是没有继承特性。其语法与创建泛型类完全一样。泛型结构的声明方式为:,struct A,【,示例,16-8】,下面的程序演示了泛型结构的用法。,在本例中,声明了一个泛型结构,A,。与泛型类相似,泛型结构也可以使用约束。如下所示为将泛型结构,A,声明的类型实参限制为值类型:,struct Awhere T:struct,16.4,泛型方法,泛型方法是声明了类型参数的方法。泛型方法可以在类、结,构,或接口声明中声明,这些类、结构、或接口本身可以是泛型或非泛型。泛型类内部的方法可以使用类的类型参数,自动创建与该类型形参相关的泛型。也可以声明使用一个或多个自定义类型形参的泛型方法。泛型方法可以是静态的,也可以是非静态的。泛型方法的声明形式如下:,public void F,【,示例,16-9】,下面程序演示了泛型方法的用法。,在上面的例子中,声明了一个名为,A,的非泛型类,在该类中有一个静态的泛型方法,F(),。类型形参的声明紧跟在方法名称之后,但位于参数列表之前。,F(),方法是静态的,因此与其调用与具体的对象无关。,16.5,泛型委托,与方法一样,委托也可以是泛型的。因为泛型内在的类型安全性,无法将不兼容的方法赋给委托。声明泛型委托的形式为:,delegate ret-type delegate-name(arg-list);,类型形参的声明紧跟在委托的名称之后。泛型委托的优点在于,它允许开发人员以类型安全的方式定义一种通用形式,该形式可用于匹配任意兼容的方法。,【,示例,16-10】,下面的程序演示了一个名为,Test,的泛型委托。它有一个类型形参,T,,该委托返回类型,T,,并且接收一个,T,类型的实参。,16.6,泛型接口,除了定义泛型类和泛型方法外,还可以定义泛型接口。泛型接口的定义与泛型类基本相同。泛型接口的声明形式为,:,interface Iinterface,【,示例,16-11】,下面的程序演示了一个名为,Iinterface,的泛型接口。,16.7,泛型类的层次结构,泛型类可以是类的层次结构的一部分。所以,泛型类可以用作基类或者派生类。泛型层次结构与非泛型层次结构的主要不同在于,在泛型层次结构中,泛型基类所需的任意类型实参都必须通过派生类沿着层次结构向上传递。泛型类的层次结构有两种:,1.,使用泛型基类,【,示例,16-12】,下面是一个简单的泛型类层次结构例子,它使用了一个泛型基类。,在该例中,,Test2,类继承了,Test1,泛型类。,Test2,类指定了类型形参,T,并将其传递给,Test1,类。也就是说,所有传递给,Test2,类的类型都会传递给,Test1,类。因此,即使派生类不需要成为泛型类,也必须指定其泛型基类所需的类型形参。当然,派生类也可以根据需要任意添加自己的类型形参。,2.,使用泛型派生类,非泛型类可以用作泛型派生类的基类。,【,示例,16-13】,下面的程序演示了使用泛型派生类的层次结构。,在该例中,,A,类不是泛型类,因此没有指定任何类型实参。所以,尽管,Test,类声明了类型形参,T,,但是,A,类不需要它。即,Test,类以常规方式继承,A,类,而没有应用任何,特殊条件,。,16.8,使用泛型的限制,使用泛型时,要注意一下限制:,extern,修饰符不能用于泛型方法。,属性、运算符、索引器和事件不能泛型化。这些项仍可以用作泛型中,并且可以使用类的泛型类型形参。,指针类型不能用作类型实参。,如果泛型类包含一个,static,字段,那么每一个构造类型都会有该字段的独立副本。这意味着同一个构造函数类型的所有实例都会共享同一个,static,字段。然而,不同的构造类型使用不同的字符副本。因此,,static,字段并不是由所有的构造类型共享。,16,.,9,小结,本章详细地为大家介绍了泛型的相关内容。其中重点是泛型类、类型约束、泛型方法、泛型接口的声明与创建,难点是泛型结构和泛型委托的使用。希望大家多参考示例,仔细分析与理解。下一章将为大家介绍,LINQ-,语言集成查询。,
展开阅读全文