网站编辑怎么做内容分类,网页设计公司金华,深圳网站设计哪家比较好,wordpress 访客插件本章的主要内容如下#xff1a;
● 泛型概述
● 创建泛型类
● 泛型类的特性
● 泛型接口
● 泛型结构
● 泛型方法
目录
1.1 泛型概述
1.1.1 性能
1.1.2 类型安全
1.1.3 二进制代码的重用
1.1.4 代码的扩展
1.1.5 命名…本章的主要内容如下
● 泛型概述
● 创建泛型类
● 泛型类的特性
● 泛型接口
● 泛型结构
● 泛型方法
目录
1.1 泛型概述
1.1.1 性能
1.1.2 类型安全
1.1.3 二进制代码的重用
1.1.4 代码的扩展
1.1.5 命名约定
1.2 创建泛型类
1.3 泛型类的功能
1.3.1 默认值
1.3.2 约束
1.3.3 继承
1.3.4 静态成员
1.4 泛型接口
1.4.1 协变和抗变
1.4.2 泛型接口的协变
1.4.3 泛型接口的抗变
1.5 泛型结构
1.6 泛型方法
1.6.1 带委托的泛型方法 1.1 泛型概述
泛型是C#和.NET的一个重要概念。泛型不仅是C#编程语言的一部分而且与程序集中的L(Intermediate Languag中间语言)代码紧密地集成。有了泛型就可以创建独立于被包含类型的类和方法。我们不必给不同的类型编写功能相同的许多方法或类只创建一个方法或类即可。
另一个减少代码的选项是使用Object类但使用派生自Object类的类型进行传递不是类型安全的。泛型类使用泛型类型并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性如果某个类型不支持泛类编译器就会出现错误。
泛型不仅限于类本章还将介绍用于接口和方法的泛型。用于委托的泛型参见第8章。
泛型不仅存在于C#中其他语言中有类似的概念。例如C模板就与泛型相似。但是C模板和NET泛型之间有一个很大的区别。对于C模板在用特定的类型实例化模板时需要模板的源代码。C十编译器为每个属于特定模板实例的类型生成单独的二进制代码。相反泛型不仅是C#语言的一种结构而且是CLR(公共语言运行库定义的。所以即使泛型类是在C#中定义的也可以在Visual Basic中用一个特定的类型实例化该泛型。
下面介绍泛型的优点和缺点尤其是
● 性能
● 类型安全性
● 二进制代码重用
● 代码的扩展
● 命名约定
1.1.1 性能
泛型的一个主要优点是性能。第10章介绍了System.Collections和System.Collections. Generic命名空间的泛型和非泛型集合类。对值类型使用非泛型集合类在把值类型转换为引用类型和把引用类型转换为值类型时需要进行装箱和拆箱操作。
下面的例子显示了System.Collections命名空间中的ArrayList类。ArrayList存储对象 Add()方法定义为需要把一个对象作为参数所以要装箱一个整数类型。在读取ArrayList中的值时要进行拆箱把对象转换为整数类型。可以使用类型转换运算符把ArrayList集合的第一个元素赋予变量i1在访问int类型的变量i2的foreach语句中也要使用类型转换运算符 装箱和拆箱操作很容易使用但性能损失比较大迭代许多项时尤其如此。
System.Collections.Generic命名空间中的ListT类不使用对象而是在使用时定义类型。在下面的例子中ListT类的泛型类型定义为int所以int类型在JIT编译器动态生成的类中使用不再进行装箱和拆箱操作。 1.1.2 类型安全
泛型的另一个特性是类型安全。与ArrayList类一样如果使用对象可以在这个集合中添加任意类型。下面的例子在ArrayList类型的集合中添加一个整数、一个字符串和一个MyClass类型的对象 如果这个集合使用下面的foreach语句迭代而该foreach语句使用整数元素来迭代编译器就会编译这段代码。但并不是集合中的所有元素都可以转换为int所以会出现一个运行异常 错误应尽早发现。在泛型类ListT中泛型类型T定义了允许使用的类型。有了Listint的定义就只能把整数类型添加到集合中。编译器不会编译这段代码因为Add()方法的参数无效
1.1.3 二进制代码的重用
泛型允许更好地重用二进制代码。泛型类可以定义一次用许多不同的类型实例化。不需要像C模板那样访问源代码。
例如System.Collections.Generic命名空间中的ListT类用一个int、一个字符串和一个MyClass类型实例化
泛型类型可以在一种语言中定义在另一种.NET语言中使用。
1.1.4 代码的扩展
在用不同的类型实例化泛型时会创建多少代码
因为泛型类的定义会放在程序集中所以用某个类型实例化泛型类不会在IL代码中复制这些类。但是在JIT编译器把泛型类编译为内部码时会给每个值类型创建一个新类。引用类型共享同一个内部类的所有实现代码。这是因为引用类型在实例化的泛型类中只需要4字节的内存单元(32位系统)就可以引用一个引用类型。值类型包含在实例化的泛型类的内存中。而每个值类型对内存的要求都不同所以要为每个值类型实例化一个新类。
1.1.5 命名约定
如果在程序中使用泛型区分泛型类型和非泛型类型会有一定的帮助。下面是泛型类型的命名规则
● 泛型类型的名称用字母T作为前缀。
● 如果没有特殊的要求泛型类型允许用任意类替代且只使用了一个泛型类型就可以用字符T作为泛型类型的名称。 ● 如果泛型类型有特定的要求(例如必须实现一个接口或派生于基类)或者使用了两个或多个泛型类型就应给泛型类型使用描述性的名称、 1.2 创建泛型类
首先介绍一个一般的、非泛型的简化链表类它可以包含任意类型的对象以后再把这个类转化为泛型类。
在链表中一个元素引用其后的下一个元素。所以必须创建一个类将对象封装在链表中引用下一个对象。类LinkedListNode包含一个对象value它用构造函数初始化还可以用Value属性读取。另外LinkedListNode类包含对链表中下一个元素和上一个元素的引用这些元素都可以从属性中访问。
LinkedList类包含LinkedListNode类型的first和last字段它们分别标记了链表的头尾。AddLast()方法在链表尾添加一个新元素。首先创建一个LinkedListNode类型的对象。如果链表是空的则first和last字段就设置为该新元素否则就把新元素添加为链表中的最后一个元素。执行GetEnumerator()方法时可以用foreach语句迭代链表。GetEnumerator()方法使用yield语句创建一个枚举器类型。 现在可以给任意类型使用LinkedList类了。在下面的代码中实例化了一个新LinkedList对象添加了两个整数类型和一个字符串类型。整数类型要转换为一个对象所以执行装箱操作如前面所述。在foreach语句中执行拆箱操作。在foreach语句中链表中的元素被强制转换为整数所以对于链表中的第三个元素会发生一个运行异常因为它转换为int时会失败。 下面创建链表的泛型版本。泛型类的定义与一般类类似只是要使用泛型类型声明。之后泛型类型就可以在类中用作一个字段成员或者方法的参数类型。LinkedListNode类用一个泛型类型T声明。字段value的类型是T而不是object。构造函数和Value属性也变为接受和返回T类型的对象。也可以返回和设置泛型类型所以属性Next和Prev的类型是LinkedListNodeT。 1.3 泛型类的功能
在创建泛型类时需要一些其他C#关键字。例如不能把null赋予泛型类型。此时可以使用default关键字。如果泛型类型不需要Object类的功能但需要调用泛型类上的某些特定方法就可以定义约束。
本节讨论如下主题
● 默认值
● 约束
● 继承
● 静态成员 下面开始一个使用泛型文档管理器的示例。文档管理器用于从队列中读写文档。先创建一个新的控制台项目DocumentManager添加类DocumentManagerT。AddDocument()方法将一个文档添加到队列中。如果队列不为空IsDocumentAvailable只读属性就返回true。 1.3.1 默认值
现在给DocumentManagerT类添加一个GetDocument()方法。在这个方法中给类型T指定null。但是不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型而null只能用于引用类型。为了解决这个问题可以使用default关键字。通过default关键字将null赋予引用类型将0赋予值类型。 1.3.2 约束
如果泛型类需要调用泛型类型上的方法就必须添加约束。对于DocumentManagerT文档的所有标题应在DisplayAllDocuments()方法中显示。Document类实现带有Title和Content只读属性的IDocument接口。 要使用DocumentManagerT类显示文档可以将类型T强制转换为IDocument接口以显示标题 问题是如果类型T没有执行IDocument接口这个类型转换就会生成一个运行异常。最好给DocumentManagerTDocument类定义一个约束TDocument类型必须执行IDocument接口。为了在泛型类型的名称中指定该要求将T改为TDocument。where子句指定了执行IDocument接口的要求。 这样就可以编写foreach语句让类型T包含属性Title了。Visual Studio IntelliSense和编译器都会提供这个支持。 在Main()方法中DocumentManagerT类用Document类型实例化而Document类型执行了需要的IDocument接口。接着添加和显示新文档检索其中一个文档
DocumentManager现在可以处理任何执行了IDocument接口的类。
在示例应用程序中介绍了接口约束。泛型还有几种约束类型如表9-1所示。 注意·
在C#中where子句的一个重要限制是不能定义必须由泛型类型执行的运算符。运算符不能在接口中定义。在where子句中只能定义基类、接口和默认构造函数。
1.3.3 继承
前面创建的LinkedListT类执行了IEnumerableT接口 泛型类型可以执行泛型接口也可以派生于一个类。泛型类可以派生于泛型基类 其要求是必须重复接口的泛型类型或者必须指定基类的类型如下所示 于是派生类可以是泛型类或非泛型类。例如可以定义一个抽象的泛型基类它在派生类中用一个具体的类型实现。这允许对特定类型执行特殊的操作
还可以创建一个部分的特殊操作如从Quey中派生StringQuery类只定义一个泛型参数如字符事 TResult。要实例化StringQuery,只需要提供TRequest的类型 1.3.4 静态成员
泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。下面看一个例子其中 StaticDemoT类包含静态字段x: 由于同时对一个string类型和一个int类型使用了StaticDemoT类因此存在两组静态字段 StaticDemostring.x 4; StaticDemoint.x 5; Console.WriteLine(StaticDemostring.x); // writes 4 1.4 泛型接口
使用泛型可以定义接口接口中的方法可以带泛型参数。在链表示例中就执行了IEnumerableT接口它定义了GetEnumerator()方法以返回IEnumeratorout T。NET为不同的情况提供了许多泛型接口例如IComparableT、ICollectionT和IExtensibleObjectT。同一个接口常常存在比较老的非泛型版本例如.NET1.0有基于对象的IComparable接口。IComparableinT基于一个泛型类型 比较老的非泛型接口IComparable需要一个带CompareTo0方法的对象。这需要强制转换为特定的类型例如Person类要使用LastName属性就需要使用CompareToO方法 执行泛型版本时不再需要将object的类型强制转换为Person 1.4.1 协变和抗变
在NET4之前泛型接口是不变的。.NET4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变指对参数和返回值的类型进行转换.例如可以给一个需要Shape参数的方法传送Rectangle参数吗下面用示例说明这些扩展的优点。
在.NET中参数类型是抗变的假定有Shape和Rectangle类Rectangle类派生自Shape基类。声明DisplayO方法是为了接受Shape类型的对象作为其参数 现在可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以Rectangle满足Shape的所有要求编译器接受这个方法调用 方法的返回类型是协变的。当方法返回一个Shape时不能把它赋予Rectangle,因为Shape不一定总是 Rectangle。反过来是可行的如果一个方法像GetRectangleO方法那样返回一个Rectangle,.
在NET Framework4版本之前这种行为方式不适用于泛型。自C#4以后扩展后的语言支持泛型接口和泛型委托的协变和抗变。下面开始定义Shape基类和Rectangle类. 1.4.2 泛型接口的协变
如果泛型类型用out关键字标注泛型接口就是协变的。这也意味着返回类型只能是T。接口IIndex与类型T是协变的并从一个只读索引器中返回这个类型. IIndexT接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle: RectangleCollection.GetRectangleO方法返回一个实现IIndexRectangle接口的RectangleCollection类所以可以把返回值赋予IIndexRectangle类型的变量rectangle。因为接口是协变的所以也可以把返回值赋予 IndexShape类型的变量。Shape不需要Rectangle没有提供的内容。使用shapes变量就可以在for循环中使用接口中的索引器和Cont属性。
1.4.3 泛型接口的抗变
如果泛型类型用关键字标注泛型接口就是抗变的。这样接口只能把泛型类型T用作其方法的输入。 ShapeDisplay类实现DisplayShape,并使用Shape对象作为输入参数 创建ShapeDisplay的一个新实例会返回DisplayShape,并把它赋予shapeDisplay变量.因为DisplayT是抗变的所以可以把结果赋予DisplayRectangle,其中Rectangle派生自Shape。这次接口的方法只能把泛型类型定义为输入而Rectangle满足Shape的所有要求。 1.5 泛型结构
与类相似结构也可以是泛型的。它们非常类似于泛型类只是没有继承特性。本节介绍泛型结构 Nullable-T,它由.NET Framework定义。
NET Framework中的一个泛型结构是NullableT。数据库中的数字和编程语言中的数字有显著不同的特征因为数据库中的数字可以为空而C#中的数字不能为空。32是一个结构而结构实现同值类型所以结构不能为空。这种区别常常令人很头痛映射数据也要多做许多辅助工作。这个问题不仅存在于数据库中也存在于把XML数据映射到.NET类型。
一种解决方案是把数据库和L文件中的数字映射为引用类型因为引用类型可以为空值。但这也会在运行期间带来额外的系统开销。
使用NullableT结构很容易解决这个问题。下面的代码段说明了如何定义NullableT的一个简化版本。结构NullableT定义了一个约束其中的泛型类型T必须是一个结构。把类定义为泛型类型后就没有低系统开销这个优点了而且因为类的对象可以为空所以对类使用NullableT类型是没有意义的。除了NullableT定义的T类型之外唯一的系统开销是has Value布尔字段它确定是设置对应的值还是使之为空。除此之外泛型结构还定义了只读属性Has Value和Vaue,以及一些运算符重载。把NullableT类型强制转换为T类型的运算符重载是显式定义的因为当has Value为false时它会抛出一个异常。强制转换为NullableT类型的运算符重载定义为隐式的因为它总是能成功地转换 在这个例子中NullableT用Nullableint实例化。变量x现在可以用作一个int,进行赋值或使用运算符执行一些计算。这是因为强制转换了NullableT类型的运算符。但是x还可以为空。NullableT的Has Value和Value属性可以检查是否有一个值该值是否可以访问 因为可空类型使用得非常频繁所以C#有一种特殊的语法它用于定义可空类型的变量。定义这类变量时不使用泛型结构的语法而使用“”运算符。在下面的例子中变量x1和x2都是可空的t类型的实例 可空类型可以与null和数字比较如上所示。这里x的值与null比较如果x不是null它就与小于0的值比较 知道了NullableT是如何定义的之后下面就使用可空类型。可空类型还可以与算术运算符一起使用。变量x3是变量x1和x2的和。如果这两个可空变量中任何一个的值是null,它们的和就是null。 注意这里调用的GetNullableType()方法只是一个占位符它对于任何方法都返回一个可空的int。为了进行测试简单起见可以使实现的GetNullableTypeO返回null或返回任意整数。
非可空类型可以转换为可空类型。从非可空类型转换为可空类型时在不需要强制类型转换的地方可以进行隐式转换。这种转换总是成功的 但从可空类型转换为非可空类型可能会失败。如果可空类型的值是null,并且把null值赋予非可空类型就会抛出nvalidOperationException类型的异常。这就是需要类型强制转换运算符进行显式转换的原因 如果不进行显式类型转换还可以使用合并运算符从可空类型转换为非可空类型。合并运算符的语法是“?”,为转换定义了一个默认值以防可空类型的值是null。这里如果x1是nully1的值就是0。 1.6 泛型方法
除了定义泛型类之外还可以定义泛型方法。在泛型方法中泛型类型用方法声明来定义。
SwapT方法把T定义为泛型类型用于两个参数和一个变量temp 把泛型类型赋予方法调用就可以调用泛型方法 但是因为C#编译器会通过调用Swap方法来获取参数的类型所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用
1.6.1 带委托的泛型方法
这个Accumulate(0方法使用两个泛型参数T1和T2,第一个参数T1用于实现EnumerableT1参数的集合第二个参数使用泛型委托FncT1,T2,TResult。其中第2个和第3个泛型参数都是T2类型。需要传递的方法有两个输入参数(T1和T2)和一个T2类型的返回值。 在调用这个方法时需要指定泛型参数类型因为编译器不能自动推断出该类型。对于方法的第1个参数所赋予的accounts集合是EnumerableAccount类型。对于第2个参数使用一个lambda表达式来定义Account和decimal类型的两个参数返回一个小数。对于每一项通过Accumulate02方法调用这个lambda表达式。 不要为这种语法伤脑筋。该示例仅说明了扩展Accumulate方法的可能方式。