C#三十二 泛型的理解和使用

日常生活中的事物都是有类型的,比如我们说“一个女人”,那么“女”就是这个人的类型。我们可以说“女人都是水做的”,那么听者都知道这是在说“女”这种类型的人。再比如你去肉店买肉,你可以对老板说“我要十斤猪肉”,那么老板一定知道你是在要“猪”这种类型的肉。日常生活中的这些语言都是带有类型的,但是在日常生活中还有一些语言是不带类型的。比如我们经常说“人是贪婪的”,这里的人就没有类型之分,听者都知道是指所有的人;我们也可以在肉店里指着猪肉说“给我来十斤肉”,肉店老板同样知道你要的是猪肉。

程序语言必须能够对现实中的数据进行表述,对于C#语言来讲可以使用数据类型对数据进行精确的描述。事实上这种程序语言被称作强类型语言,在这样的语言当中出现的数据都必须带有数据,这样的语言还有很多,比如C++、Java、Python等。与强类型语言对应的得是弱类型语言,比如VB、JavaScript等,他们没有数据类型概念。从肉店买肉这个例子我们可以看出这两种类型的各自的优缺点。强类型语言显然可以精确的表达逻辑但表达过于罗嗦,无论是肉店老板还是旁边的人听到“我要十斤猪肉”这句话都可以精确的知道你的意思。弱类型语言的特点就是表达简洁但逻辑容易发生混乱,比如你还可以指着猪肉说“来十斤”,很显然你的话只有肉店老板先看懂你的手势才能懂,容易引起逻辑的混乱。

计算机程序是推理性语言,中间某一行逻辑出错都会导致最终的结果出现错误,所以从这个角度出发,显然在买猪肉这个问题上强类型语言获胜。我们再来看关于人的那个表述,对于“人是贪婪的”这句话,是在描述一种通用性的规律。对于这个问题用传统的强类型语言来描述就是“女人是贪婪的,男人是贪婪的”,这样说显然非常啰嗦,这也是强类型语言都存在一个缺陷。比如在程序中经常会用到某些通用的算法,用强类型语言编写这些通用的算法会和上面出现一样的情况,需要每种数据类型都提供一个相同的算法。泛型技术就是用可以用来解决此类问题。

重点:

?      理解泛型的概念

?      泛型的定义及其应用

?      泛型类

预习功课:

?      泛型的概念

?      如何定义泛型及其应用

?      如何使用泛型类

9.1 为什么使用泛型

假如让你用C#编写一个求两个数和的方法,你会怎么做?若求的两个数是整数,可以定义如下方法:

int Add(int a,int b)

{ return a+b;  }

若求的是两个double型的数的和,可以定义如下方法:

static  double Add(double a,double b)

{   return a+b;    }

若是字符串型的数值进行相加,那么你就可以定义如下方法:

static  double Add(string a,string b)

{

return double.Parse(a)+double.Parse(b);

}

假如有一天程序需要升级,你需要其他数据类型求和的算法,日不char、long、decimal等,那你怎么办?继续重载吗?还是想一个更好更通用的方法?我们可能会想到使用object类,于是你写了下面这个通用的算法:

staticobject Add(object a,object b)

{

//decimal为最大的数值类型,所以使用它

return decimal.Parse(a)+decimal.Parse(b);

}

static voidMain(string[]args)

{

decimal r1=(decimal)Add(3,3);

decimal r2=(decimal)Add(3.3,3.3);

decimal r3=(decimal)Add("3.3","3.3");

Console.WriteLine("{0},{1},{2}",r1,r2,r3) ;

}

staticobject Add(object a,object b)

{

returnConvert.ToDecimal(a)+Convert.ToDecimal(b);

}

运行结果:

6,6.6,6.6

这里用到的技术就是装箱和拆箱,Add方法首先将所有数据类型的数据进行装箱,这样就统一了它们的类型,然后再进行类型转换和计算,计算结果再拆箱就是要求的结果。实际上就是“泛型”思想的一个应用,这里用一个通用方法解决了几乎任何数值类型两个数的求和操作。所以可以说,对于这个求和算法来讲是通用的、泛型的(不需要特定数据类型)。

但是我们从上面的代码页可以看到问题,就是它执行了频繁的装箱和拆箱操作,我们知道这些操作是非常损耗性能的。另外,装箱和拆箱的代码也显得比较“难看”

因为每次都要进行强类型转换,有没有更好的方式让我们编写这种通用算法呢?于是,C#从2.0版本开始引入了泛型技术,泛型能够给我们带来的两个明显好处是---代码清晰和减少了装箱、拆箱。

9.2 C#泛型简介

利用泛型解决交换两数的泛型方法的例子:

using System;

class Program

{

static void Main(string[]args)

{

int i=1,j=2;

Console.WriteLine("交换前:{0},{1}",i,j);

Swap<int>(ref I,ref j); //交换两个数

Console.WriteLine("交换后:{0}",i,j);

}

//交换两个数的泛型算法

static void Swap<T>(ref T a,ref T b)

{

T temp=a ;

a=b ;

b=temp ;

}

}

运行结果:

交换前:1,2

交换后:2,1

这个交换算法不仅支持任何数字类型,它还支持你在程序中能用到得任何类型。注意,泛型不属于任何命名空间,准确的讲,泛型是一种编译技术。在书写算法的时候,泛型技术允许我们使用一种类型占位符(或称之为类型参数,这里使用的占位符是“T”)作为类型的标识符,而不需要指定特定类型。

当我们在调用这个算法的时候,编译器使用指定的类型代替类型占位符建立一个针对这种类型的算法。这就是泛型技术,它允许你编写算法的时候不指定具体类型,但调用的时候一定要指定具体类型,编写算法的时候使用“<>”来指定类型占位符,调用的时候一般也使用“<>”来指定具体的数据类型。

上面这个例子中的Swap<T>,指定了这个泛型方法的占位符是“T”,指定后我们就可以认为有了这么一个数据类型,该类型就是T类型,然后这个T类型既可以作为参数的数据类型又可以作为方法的返回值类型,还可以在方法内部作为局部变量的数据类型。当我们通过Swap<int>(ref i,ref j)来调用这个泛型方法时,在编译时Swap方法中所有出现“T”的地方都会被“int”类型所代替,也就相当于我们建立了

int型的交换方法,如:

static void Swap(ref int a,ref int b)

{

int temp=a;

a=b;

b=temp;

}

l      代码重用

泛型最突出优点就是可以代码重用。从上面举的交换算法的例子你也可以看出节省了多少代码。对于一个程序员来讲,写的好的算法是很重要的财富,例如我们一直在使用各种类库,这些类库实际上就是一些优秀的程序员封装的,我们直接调用就是一个代码重用的过程。

l      类型安全

类型安全的含义是类型之间的操作必须是兼容的,反之就是类型不安全。类型不安全的代码会在运行时出现异常,比如两个数相加的算法,Convert.ToDecimal(a),a是object类型,a可以是数值“3.3”,a也可以是普通字符串“hello”,如果a是后者那么执行类型转换时必定会出异常,所以说使用Convert.ToDecimal(a)是类型不安全的做法,同样那个求和的方法也是类型不安全的方法。泛型本质上还是强类型的,如果你使用一个不兼容的类型来调用泛型算法,编译器是会报错的,所以说泛型是类型安全的。

l      性能更佳

相比装箱和拆箱,泛型效率更高一些。因装箱时系统需要分配内存,而拆箱时需要类型转换,这两个操作都是极其耗费性能的。特别是在执行一些大数据量的算法时(比如排序、搜索等)装箱和拆箱性能损耗尤其严重,因此,在C#中提倡使用泛型。

9.3 泛型定义及其应用

使用泛型可以定义泛型方法、泛型类、泛型接口等。在这些泛型结构的定义中,泛型类型参数(或叫占位符)是必须指定的,类型参数所包含的类型就是我们定义的泛型类型,我们可以一次性定义多个泛型类型,如泛型方法Swap<T,U,Z>三个泛型类型。类型参数一般放在所定义的类、方法、接口等标识符后面,并且包含在“<>”里面。

泛型类型名称的写法也有一定的规则:

l       泛型类型名称必须是由字母、数字、下划线组成,并且必须以字符或下划线开头。比如_T、T、TC都是有效的泛型类型名称。

l       务必使用有意义泛型类型名称,除非单个字母名称完全可以让人了解它表示的含义,如T.

l       当类型参数里只有单个泛型类型时,考虑使用T作为泛型类型名,如class Note<T>。

l       提倡作为泛型类型名的前缀,如Tkey,TValue。

前面举例子的时候,一般使用了泛型类型T,但从本质上讲我们可以使用满足上面要求的任何单词。实际上,泛型类型名和类名或接口名的定义规则基本一样。

9.4 泛型结构体

结构是值类型,通常可以定义结构类型来表示一些简单的对象。比如,我们前面接触的系统结构体Point、DateTime等。但是,这些结构体通常都存储一种类型的数据,我们可以定义一个泛型结构体,它将可以保存任何数据,定义规则:

struct 结构名 <泛型类型列表>

{

结构体;

}

要注意泛型类型标识符的定义只能放在结构名的后面,下面我们定义了一个

Point类型的泛型结构体,此时该结构体的X、Y可以保存任何数值类型的坐标数据。代码如下:

classProgram

{

//定义泛型结构体和泛型类型T

struct Point<T>

{

public T X;

public T Y;

}

//测试泛型结构体

static voidMain(string[]args)

{

//给T类型指定数据类型为int型

Point<int> a =newPoint<int>();

    1. X=1;

a.   Y=2;

Console.WriteLine("{0},{1}",a.X,a.Y);

}

}

运行结果:

1,2

9.5          泛型类

 

泛型类封装不属于特定具体数据类型的数据或操作。泛型类最常见的就是泛型集合类,如链表、哈希表、堆栈、队列、树等。对于集合的操作,如从集合中添加、移除、排序等操作大体上都以相同方式进行的,与所存储数据类型无关,即可使用泛型技术。

在泛型类中使用的数据类型,可以是泛型类型也可以是普通的。一般规则是,类中使用的泛型类型越多,代码就会变得越灵活,重用性就越好。但是要注意,类中如果有太多的泛型类型也会使其他开发人员难以阅读或理解该类。要定义类的泛型类型也是在类名后面通过"<>"定义,类的其他元素除了方法外都不能定义自己的泛型类型,但可以使用该类定义的泛型类型。泛型类定义规则如下:

class 类名<泛型类型列表>

{

//类体

}

//示例代码:

usingSystem;

classProgram

{

//定义泛型类和泛型类型T

private class Node<T>

{

private T data;

public Node(T t)

{

data=t;

}

public T Data

{

get{return data;}

set{data=value;}

}

}

static void Main()

{

Node<int>node=newNode<int>(10000);

Console.WriteLine("数据:{0}",node.Data);

Node<string>snode=newNode<string>("壹万");

Console.WriteLine("数据:{0}",snode.Data);

}

}

运行结果:

数据:10000

数据:壹万

如前所述,类中的成员有很多,如字段、属性、方法、事件、索引器等,其中除了方法之外,其他的类成员都不能自定义的泛型类型,只能使用定义类的时候定义的泛型类型或系统数据类型:

classStudent<T,U>

{

private T name;      //姓名

private U[]score;    //各个科目的分数数组

private int ucode;   //编号使用系统数据类型

public U this[int n]  //返回一个分数

{

get{return score[n];

}

}

类中的方法可以是泛型的,泛型方法的定义规则如下:

访问修饰符 返回类型  方法名<泛型类型列表>(方法参数列表)

如:

public voidShow<T>(T  a){}

此泛型方法的使用时要给T指定一个实际的数据类型,如:

Show<string>("hello");

其中方法的泛型类型列表中定义的泛型类型可以出现在方法的任何位置,包括返回值、参数、方法内,当然也可以不出现,比如下面这些都是合法的:

public TGet<T>(T a) {return default(T) ;}

public intGet<T a)  {return 0 ;}

public TGet<T>(int a)  {return default(T);}

这上面用了default关键字,这个关键字可以取当前类型的默认初始值,这个关键字对于引用类型会返回null,对于数值类型会返回零。

另外,类中也可以出现泛型的重载方法,如:

voidDoWork(){}

voidDoWork<T>(){}

voidDoWork<T,U>(){}

由于方法是在类中,所以泛型方法中的数据类型又三种情况,一种是类的泛型类型,一种是泛型方法自身的泛型类型,另外还可以是系统数据类型。泛型方法和非泛型方法或属性、索引器可以互相调用。如:

classStudent<U>

{

private U id;

private string name;

public void ShowHello()

{

this.Show<string>("hello");  //调用泛型方法

}

public void ShowId()

{

this.Show<U>(id);

}

private void Show<S>(S msg)

{

Console.WriteLine(msg);

}

}

类的泛型类型只能用于本类,方法的泛型类型只能用于本方法。不管谁定义的泛型,一旦定义了泛型类型,你可以就当泛型类型是一个真实的类型来用了。

9.6          典型的泛型类

.Net框架类库中,System.Collections.Generic和System.Collections.ObjectModel命名空间中,分别定义了大量的泛型类和泛型接口,这些泛型类多为集合类,因为泛型最大的应用正体现于再集合中对于不同类型对象的管理。

下表列出了,.Net框架中常用的泛型类和泛型接口:


泛型类


说明


List<T>


对应于ArrayList集合类,可以动态调整集合容量,通过索引方式访问对象,支持排序、搜索和其他常见操作。


SortedList<TKey,TValue>


对应于SortedList集合类,表示Key/Value对集合,类似于SortedDictionary<TKey,TValue>集合类,而SortedList在内存上更有优势。


Queue<T>


对应于Queue集合类,是一种先进先出的集合类,常应用于顺序存储处理。


Stack<T>


对应于Stack集合类,是一种后进先出的集合类。


Collection<T>


对应于CollectionBase集合类,是用于自定义泛型集合的基类,提供了受保护的方法来实现定制泛型集合的行为Collection<T>的实例是可修改的。


Dictionary<TKey,TValue>


对应于Hashtable集合类,表示Key/Value对的集合类,Key必须是唯一的,其元素类型既不是Key的类型,也不是Value的类型,而是KeyValuePair类型。

时间: 2024-11-04 10:28:18

C#三十二 泛型的理解和使用的相关文章

第三十二章

道恒无名,朴虽小,而天下弗敢臣.侯王若能守之,万物将自宾.天地相合,以俞甘露,民莫之令而自均焉.始制有名,名亦既有,夫亦将知止,知止所以不殆.譬道之在天下也,犹小谷之与江海也. 第三十二章1 如何让大家都来顺服你? 各位朋友大家好,今天我们接着来聊<道德经>.今天我们不唱歌了,昨天放了一首我唱的歌,这唱歌在我这儿就是一个养生运动.因为唱歌的时候你要调呼吸,这时候是锻炼肺.我之前写过两篇文章,专门讲唱歌的,我们家有一位邻居.一位朋友,这肺间质性病变,很严重的肺病,结果人家天天唱歌,现在恢复的特别

【WPF学习】第三十二章 执行命令

原文:[WPF学习]第三十二章 执行命令 前面章节已经对命令进行了深入分析,分析了基类和接口以及WPF提供的命令库.但尚未例举任何使用这些命令的例子. 如前所述,RoutedUICommand类没有任何硬编码的功能,而是只表达命令,为触发命令,需要有命令源(也可使用代码).为响应命令,需要有命令绑定,命令绑定将执行转发给普遍的事件处理程序. 一.命令源 命令库中的命令始终可用.触发他们的最简单的方法是将它们关联到实现了ICommandSource接口的控件,其中包括继承自ButtonBase类的

工作那些事(三十二)由孙悟空的两个故事谈个人与团队

故事一: 话说唐太宗为了节省开支,西天取经项目需要裁员,接到通知的唐僧骤然头大,不知如何是好. 有人说: 先把猴子开了,因为不服从管理,再把沙僧开了,因为没有主见,再把猪开了, 因为猪肉比较贵,直接杀了卖钱,再把自已开了,因为没本事去,小白龙留下,因为有后台. 但是: 猴子是不能开的,猴子是团队中的精英,也就是大牛. 小白龙是不能开的,他是唐僧的座驾,是他身份的象征. 猪也是不能开的,对团队的氛围有重要作用. 最后 只能开掉沙僧 虽然他做事很多. 重要的是四种人: 1 唐僧:钦定的项目经理,虽然

马哥学习笔记三十二——计算机及操作系统原理

缓存方式: 直接映射 N路关联 缓存策略: write through:通写 write back:回写 进程类别: 交互式进程(IO密集型) 批处理进程(CPU密集型) 实时进程(Real-time) CPU: 时间片长,优先级低IO:时间片短,优先级高 Linux优先级:priority 实时优先级: 1-99,数字越小,优先级越低 静态优先级:100-139,数据越小,优先级越高 实时优先级比静态优先级高 nice值:调整静态优先级   -20,19:100,139   0:120 ps

[原创]ActionScript3游戏中的图像编程(连载三十二)

2.2.5 投影距离的模拟 Photoshop投影样式面板的下一个属性是距离,它也存在于Flash的投影滤镜选项中.两者初始值一致,经笔者测试,两者在效果实现和数值意义方面基本一致.Flash不需要对默认参数进行更改. 下一项是扩展,乍一看,在Flash中并没有找到对应项.但仔细观察,在Photoshop投影样式的基础选项里,除了alpha以外,就只剩该属性用了百分比. [原创]ActionScript3游戏中的图像编程(连载三十二),布布扣,bubuko.com

ASP 三十二条精华代码 (1)

ASP 三十二条精华代码 (1) 2009-08-10 09:53:03  www.hackbase.com  来源:互联网 1. oncontextmenu="window.event.returnvalue=false" 将彻底屏蔽鼠标右键 <table border oncontextmenu=return(false)><td>no</table> 可用于Table 2. <body onselectstart="return

【Unity 3D】学习笔记三十二:游戏元素——游戏光源

游戏光源 在3D游戏中,光源是一个非常具有特色的游戏组件.用来提升游戏画面质感的.如果没有加入光源,游戏场景可能就会显得很昏暗.在unity中提供了三种不同的光源类型:点光源,聚光灯,平行光. 点光源 顾名思义,点光源是从一个点向周围散发出光的光源,就像电灯一样.创建点光源在hierarchy视图中点击create--point light: 创建完以后,点击点光源对象,在右侧inspector视图中可以看到点光源的所有信息: type:光源的类型.有point(点光源),directional

CCNA实验三十二 ISDN &nbsp;

CCNA实验三十二 ISDN 环境:Windows XP .Boson NetSim7.02 目的:简单认识ISDN和并掌握基本配置 说明: ISDN综合业务数字网(Integrated Services Digital Network,ISDN)是一个数字电话网络国际标准,是一种典型的电路交换网络系统.它通过普通的铜缆以更高的速率和质量传输语音和数据.ISDN的承载业务类型按照业务的数据率分为两级. 1.基本速率接口(BRI:BasicRateInterface),该速率由两个承载信道和一个数

三十二、Java图形化界面设计——布局管理器之CardLayout(卡片布局)

摘自 http://blog.csdn.net/liujun13579/article/details/7773945 三十二.Java图形化界面设计--布局管理器之CardLayout(卡片布局) 卡片布局能够让多个组件共享同一个显示空间,共享空间的组件之间的关系就像一叠牌,组件叠在一起,初始时显示该空间中第一个添加的组件,通过CardLayout类提供的方法可以切换该空间中显示的组件. 1.  CardLayout类的常用构造函数及方法 2.  使用CardLayout类提供的方法可以切换显