《C#高级编程(第六版)》泛型学习笔记(一):泛型优点和特性 (转载)
原文出处:http://www.cnblogs.com/xun126/archive/2011/01/13/1933838.html
泛型是CLR 2.0的一个新特性,在CLR 1.0中,要创建一个灵活的类或方法,但该类或方法在编译期间不知道使用什么类,就得以Object类为基础。而Object在编译期间没有类型安全性,因此必须进行强制类型转换,同时,给值类型使用Object类会有性能损失。泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性:如果某个类型不支持泛型类,编译器就会报错。
一、泛型有以下几个优点:
1)性能
对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。装箱和拆箱的操作很容易实现,但是性能损失较大。假如使用泛型,就可以避免装箱和拆箱操作。
1 ArrayList list=new ArrayList(); 2 list.Add(20); //装箱,list存放的是object类型元素,须将值类型转化为引用类型 3 int i=(int)list[0]; //拆箱,list[0]的类型是object,要赋值就得把引用类型转化为值类型
如果换成泛型编程,就不会有装箱和拆箱的性能损失。
2)类型安全
与ArrayList类一样,如果使用对象,可以在这个集合中添加任意类型。
如果使用非泛型编程,如下代码,就有可能在某些情况下会发生异常。


如果该用泛型编程,则可以避免这种异常,让编译器检查出错误。
1 List<int> list=new List<int>(); 2 list.Add(20); 3 lsit.Add("string"); //编译时报错,只能报整数类型添加到集合中 4 list.Add(new MyClass()); //同上
3)二进制代码重用
泛型可以定义一次,用许多不同的类型实例化,不需要像C++模板那样访问源代码。泛型可以在一种语言中定义,在另一种.NET语言中使用。
4)代码的扩展
因为泛型类的定义会放在程序集中,所以用某个类型实例化泛型泛型类不会在IL代码中复制这些类。但是,在JIT编译器把泛型类编译为内部代码时,会给每个值类型创建一个新类。引用类型共享同一个内部类的所有实现代码。这是因为引用类型在实例化的泛型类中只需要4字节的内存单元(32位系统),就可以引用一个引用类型。值类型包含在实例化的泛型类的内存中。而每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类。
二、泛型类的特性
1)默认值
在给类型T初始化时,要注意不能把null赋予泛型类型。因为泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。
1 public T GetDoucumet() 2 { 3 T doc=default(T); 4 lock(this) 5 { 6 doc=documentQueue.Dequeue(); 7 } 8 return doc; 9 }
补充:default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,根据泛型类型是引用类型还是值类型,default关键字用于将泛型类型初始化为null或0。
2)约束
如果泛型类需要调用泛型类型上的方法,就必须添加约束。
1 public class DocumentManager<T> 2 { 3 private readonly Queue<T> documentQueue=new Queue<T>(); 4 5 public void AddDocument(T doc) 6 { 7 lock(this) 8 { 9 documentQueue.Enqueue(doc); 10 } 11 } 12 13 public bool IsDocumentAvailable 14 { 15 get 16 { 17 return documentQueue.Count>0; 18 } 19 } 20 } 21 22 public interface IDocument 23 { 24 string Title{get;set;} 25 string Content{get;set;} 26 } 27 28 public class Document:IDocument 29 { 30 public Document() 31 { 32 } 33 34 public Document(string title,string content) 35 { 36 this.title=title; 37 this.content=content; 38 } 39 40 public string Title{get;set;} 41 public string Content{get;set;} 42 }
如果使用DocumentManager<T>类显示文档,可以将类型T强制转换为IDocument接口
1 public void DisplayAllDocument() 2 { 3 foreach(T doc in documentQueue) 4 { 5 Console.WriteLine(((IDocument)doc).Title); 6 } 7 }
假如类型T没有执行IDocument接口,这个类型转换就会生成一个异常,因此需给DocumentManager<T>类定义一个约束:T必须执行IDocument接口,为了在泛型类型的名称中指定该要求,将T改为TDocument。wherer子句指定了执行IDocument接口的要求。
1 public class DocumentManager<TDocument>where TDocument:IDocument 2 { 3 .... 4 }
这样编写foreach语句就可以让类型T包含Title属性。
1 public void DisplayAllDocument() 2 { 3 foreach(TDocument doc in documentQueue) 4 { 5 Console.WriteLine(doc.Title); 6 } 7 }
在Main()方法中,DocumentManager<T>类用Document类型来实例化,而Document类型执行了需要的IDocument接口。
1 static void Main() 2 { 3 DocumentManager<Document> dm=new DocumentManager<Document>(); 4 dm.AddDocument(new Document("Title A","A")); 5 dm.AddDocument(new Document("Title B","B")); 6 dm.DisplayAllDocument();
除此之外,泛型还有几种约束类型。如下:
1)where T:struct 使用结构约束。类型T必须是值类型
2)where T:class 类约束指定,类型T必须是引用类型
3)where T:IFoo 指定类型T必须执行接口IFoo
4)where T:Foo 指定类型T必须派生于基类Foo
5)where T:new() 构造函数约束,指定类型T必须有一个默认构造函数
6)where T:U 类型T1派生于泛型类型T2。该约束也成为裸类型约束。
注意:使用泛型类型还可以合并多个约束。where T:IFoo,new()约束和MyClass<T>声明指定,类型T必须执行IFoo接口,且必须有一个默认构造函数。
1 public class MyClass<T>where t:IFoo,new() 2 { 3 ... 4 }
3)继承
泛型类型可以执行泛型接口,也可以派生于一个类。泛型类可以派生于泛型基类:
1 public class Base<T> 2 { 3 4 } 5 6 public class Derived<T>:Base<T> 7 { 8 9 }
要求必须重复接口的泛型类型,或者必须指定基类的类型。
1 public class Base<T> 2 { 3 4 } 5 6 public class Derived<T>:Base<string> 7 { 8 9 }
所以,派生类可以是泛型类或非泛型类。如可以定义一个抽象的泛型基类,它在派生类中用一个具体的类型实现。
1 public abstract class Calc<T> 2 { 3 public abstract T Add(T x,T y); 4 public abstract T Sub(T x,T y); 5 } 6 7 public class SimpleCalc:Calc<int> 8 { 9 public override int Add(int x,int y) 10 { 11 return x+y; 12 } 13 14 public override int Sub(int x,int y) 15 { 16 return x-y; 17 } 18 }
4)静态成员
泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。
1 public class StaticDemo<T> 2 { 3 public static int x; 4 }
对一个string类型和一个int类型使用了StaticDemo<T>类,所以存在两组静态字段:
1 StaticDemo<string>.x=4; 2 StaticDemo<int>.x=5; 3 Console.WrileLine(StaticDemo<string>.x); //将会输出4
《C#高级编程(第六版)》泛型学习笔记(一):泛型优点和特性 (转载)的更多相关文章
- C#高级编程 (第六版) 学习 第七章:委托和事件
第七章 委托和事件 回调(callback)函数是Windows编程的一个重要方面,实际上是方法调用的指针,也称为函数指针. .Net以委托的形式实现了函数指针的概念,.Net的委托是类型安全的. 委 ...
- C#高级编程 (第六版) 学习 第六章:运算符和类型强制转换
第六章 运算符和类型强制转换 1,运算符 类别 运算符 算术运算符 + - * / % 逻辑运算符 & | ^ ~ && || ! 字符串连接运算符 + 增量和减量运算符 ++ ...
- C#高级编程 (第六版) 学习 第一章:.Net体系结构
第一章 .Net体系结构 1,公共语言运行库(Common Language Runtime, CLR) .Net Framework的核心是其运行库的执行环境,称为公共语言运行库,或.Net运行库. ...
- C#高级编程(第六版)学习:第三十一章:Windows窗体
第三十一章 Windows窗体 创建Windows窗体应用程序 在文本编辑器中输入: /* * form.cs * a simple windows form * */ using System; u ...
- C#高级编程 (第六版) 学习 第五章:数组
第五章 数组 1,简单数组 声明:int[] myArray; 初始化:myArray = new int[4]; 为数组分配内存. 还可以用如下的方法: int[] myArray = new in ...
- C#高级编程 (第六版) 学习 第四章:继承
第四章 继承 1,继承的类型 实现继承: 一个类派生于一个基类型,拥有该基类型所有成员字段和函数. 接口继承 一个类型只继承了函数的签名,没有继承任何实现代码. 2,实现继承 class MyDe ...
- C#高级编程 (第六版) 学习 第三章:对象和类型
第三章 对象和类型 1,类和结构 类存储在托管堆上 结构存储在堆栈上 2,类成员 类中的数据和函数称为类成员 数据成员 数据成员包括了字段.常量和事件 函数成员 方法:与某个类相关的函数,可以 ...
- C#高级编程 (第六版) 学习 第二章:C#基础
第二章 基础 1,helloworld示例: helloworld.cs using System; using System.Collections.Generic; using System.Li ...
- 《UNIX环境高级编程 第2版》读书笔记
CH1-2:基础知识.标准化 1 文件和目录 文件名:不能含/(分隔路径)和null(终止路径),255字符. 目录处理:opendir() readdir() closedir() 更改工作目录:c ...
- 《JavaScript高级程序设计 第3版》-学习笔记-3
P84-P137页, 这一章看的真久,这个月事太多了.有些内容在代码注释里没提出来了 1.JS强大的数组类型,元素类型任意,提供了非常多的操作数组的方法和属性 /* 数组类型 */ //stack v ...
随机推荐
- python学习之day5,装饰器,生成器,迭代器,json,pickle
1.装饰器 import os import time def auth(type): def timeer(func): def inner(*args,**kwargs): start = tim ...
- Asp.Net MVC<三> : ASP.NET MVC 基本原理及项目创建
MVC之前的那点事儿系列 解读ASP.NET 5 & MVC6系列 MVC模拟(摘自ASP.NET MVC5框架揭秘) Asp.net中,通过HttpModule的形式定义拦截器,也就是路由表 ...
- 由Memcached升级到 Couchbase的 Java 客户端的过程记录(二)
Shiro提供了类似于Spring的Cache抽象,即Shiro本身不实现Cache,但是对Cache进行了又抽象,方便更换不同的底层Cache实现. shiro对缓存的支持 shiro并没有实现缓存 ...
- 冰冻三尺非一日之寒--rabbitMQ,redis
第11章 1.rabbitMQ 2. redis 一.rabbitMQ: 人们写了有好多好多的开源的MQ服务器.其中大多数都是写出来用来解决特定问题的.它们不关心上面跑的是什么类型的消息,设计思想 ...
- 使用Shell创建GitHub仓库
Github的代码仓库分为2种类型: 用户自己的代码仓库 组织的代码仓库 下面就使用Shell脚本创建这2种类型的代码仓库,脚本如下 创建用户自己的代码仓库 #!/bin/bash USER_NAME ...
- CIDR详解和ip最长地址前缀匹配
1.CIDR是什么 无类域间路由(CIDR)编址方案 摒弃传统的基于类的地址分配方式,允许使用任意长度的地址前缀,有效提高地址空间的利用率. 就是一个ip加一个网络掩码,不过这个掩码不是之前只有3个值 ...
- 获取CPU信息
1 查看手机CPU信息 cmd——adb shell——cd /proc------cat cpuinfo 2 获取cpu的是arm指令集,armv7指令集.还是neon指令集 /** * * [获取 ...
- 自学 Java 怎么入门
自学 Java 怎么入门? 595赞同反对,不会显示你的姓名 给你推荐一个写得非常用心的Java基础教程:java-basic | 天码营 这个教程将Java的入门基础知识贯穿在一个实例中,逐 ...
- 第二轮冲刺-Runner站立会议03
今天做了什么:查看gridview与baseadapter适配器 明天准备做什么:继续gridview与baseadapter适配器 遇到的困难:暂无
- OpenGL Common Mistakes
https://www.opengl.org/wiki/Common_Mistakes Do not use constructors/destructors to initialize/destro ...