百道网
 您现在的位置:图书 > Visual C# 2005程序设计教程
Visual C# 2005程序设计教程


Visual C# 2005程序设计教程

作  者:金雪云,周新伟,王雷 编著

出 版 社:清华大学出版社

丛 书:高等学校计算机应用规划教材

出版时间:2009年02月

定  价:32.00

I S B N :9787302194774

所属分类: 教育学习  >  教材  >  研究生/本科/专科教材  专业科技  >  计算机/网络  >  软件应用与开发    

标  签:语言与开发工具  程序语言与软件开发  计算机与互联网  计算机专业  大学  教材教辅与参考书  高等教育教材  教材  C语言及其相关  

[查看微博评论]

分享到:

TOP内容简介

本书详细介绍了C#程序设计的方方面面,并针对各章知识点附以大量的示例程序。通过本书的学习,读者可以由浅入深,逐步掌握C#程序设计。
  本书共12章,主要介绍了.NET Framework及VisualStudio开发环境、C#语言基础及面向对象机制、C#Windows程序设计、C#Web程序设计、ADO.NET及C#数据库程序设计、C#泛型编程等内容。
  本书难度适中,实例丰富,既适合C#的初学者阅读,也适合有一定开发经验的读者阅读,书中包含的大量实例对应用程序开发人员具有一定的参考价值。本书在各个章节的结尾附有不同类型的练习题,这些习题难易适中,有助于读者对所学知识点进行巩固、掌握,并能启发读者进行深层次的思考。
  本书可作为各大中专院校计算机相关专业的教材或参考用书,也可作为读者的自学材料。

TOP目录

第1章 NET平台与VisualStudio2005开发工具 1
1.1 Microsoft.NET平台 1
1.1.1.NETFramework2.0概述 2
1.1.2.NETFramework类库 3
1.1.3 CommonLanguageRuntime 4
1.1.4 C#语言 4
1.1.5 理解命名空间 5
1.2 VisualStudio2005简介 8
1.2.1 VisualStudio2005开发环境概览 9
1.2.2 菜单栏 10
1.2.3 工具栏 12
1.2.4 属性及解决方案资源管理器面板 13
1.2.5 其他面板 13
1.3 使用VisualStudio2005创建控制台应用程序 15
1.4 使用VisualStudio2005创建WindowsForms应用程序 17
1.5 使用VisualStudio2005创建基于ASP.NET的Web应用程序 19
1.6 其他常用的C#开发工具 20
1.6.1 集成开发环境软件——SharpDevelop 21
1.6.2 通用编辑器——UltraEdit 22
1.7 小结 23
1.8 习题 24

第2章 C#2005语法基础 25
2.1 C#语言概述 25
2.1.1 C#语言的特点 25
2.2 C#基础元素 26
2.2.1 语句 26
2.2.2 标识符与关键字 27
2.3 变量 28
2.3.1 变量的命名 29
2.3.2 变量的声明和赋值 30
2.4 数据类型 30
2.4.1 简单数据类型 30
2.4.2 结构类型 33
2.4.3 引用类型 34
2.4.4 装箱与拆箱 37
2.4.5 数据类型转换 38
2.5 运算符与表达式 42
2.5.1 赋值运算符与表达式 42
2.5.2 关系运算符与表达式 43
2.5.3 逻辑运算符与表达式 44
2.5.4 其他运算符与表达式 44
2.5.5 运算符的优先级 46
2.6 小结 48
2.7 上机练习 48
2.8 习题 48

第3章 程序流程控制 50
3.1 选择结构程序设计 50
3.1.1 if语句 51
3.1.2 switch语句 54
3.2 循环结构程序设计 56
3.2.1 for语句 56
3.2.2 foreach语句 57
3.2.3 while语句 58
3.2.4 do-while语句 59
3.2.5 跳出循环 60
3.3 异常处理结构 62
3.3.1 异常的产生 62
3.3.2 处理异常 64
3.4 小结 67
3.5 上机练习 67
3.6 习题 68

第4章 数组与集合 71
4.1 数组 71
4.1.1 数组的声明 71
4.1.2 一维数组的使用 73
4.1.3 多维数组的使用 76
4.2 集合 77
4.2.1 集合的定义 77
4.2.2 集合的使用 78
4.2.3 常用的系统预定义的集合类 81
4.3 小结 88
4.4 上机练习 88
4.5 习题 88

第5章 C#面向对象程序设计基础 91
5.1 面向对象程序设计概述 91
5.2 类与对象 91
5.2.1 类与对象概述 92
5.2.2 面向对象程序设计的相关概念 92
5.2.3 类的声明与System.Object类 93
5.2.4 对象的声明与类的实例化 95
5.2.5 类成员 95
5.2.6 类成员的访问限制 97
5.2.7 this关键字 98
5.3 类的构造与析构函数 99
5.3.1 构造函数 99
5.3.2 析构函数 101
5.4 小结 102
5.5 上机练习 102
5.6 习题 102

第6章 域、属性与事件 105
6.1 域 105
6.1.1 域的初始化 105
6.1.2 只读域与readonly关键字 106
6.2 属性 107
6.2.1 属性的声明 108
6.2.2 属性的访问 111
6.3 事件 113
6.3.1 委托(Delegate) 113
6.3.2 事件的声明 118
6.3.3 事件的订阅与取消 119
6.4 小结 121
6.5 上机练习 121
6.6 习题 122

第7章 方法 124
7.1 方法的声明 124
7.2 方法的参数 126
7.2.1 值类型参数传递 126
7.2.2 引用类型参数传递 127
7.2.3 输出类型参数传递 129
7.2.4 数组类型参数传递 129
7.3 静态方法 131
7.4 方法的重载 132
7.5 外部方法 135
7.6 操作符重载 136
7.6.1 一元操作符的重载 136
7.6.2 二元操作符的重载 138
7.7 小结 138
7.8 上机练习 139
7.9 习题 139

第8章 继承与多态 142
8.1 什么是继承 142
8.2 使用继承机制 143
8.2.1 基类和派生类 143
8.2.2 base关键字与基类成员的访问 144
8.2.3 方法的继承与virtual、override及new关键字 145
8.2.4 sealed关键字与密封类 149
8.2.5 Abstract关键字与抽象类 150
8.3 多态性 150
8.4 本章小结 151
8.5 上机练习 151
8.6 习题 151

第9章 C#2005泛型编程 155
9.1 C#泛型概述 155
9.1.1 泛型的引入 155
9.1.2 什么是泛型 158
9.1.3 泛型实现 159
9.1.4 泛型方法 159
9.2 泛型约束 161
9.2.1 基类约束 161
9.2.2 接口约束 163
9.2.3 构造函数约束 163
9.2.4 值/引用类型约束 164
9.3 使用泛型 165
9.4 小结 169
9.5 上机练习 169
9.6 习题 170

第10章 Windows窗体应用程序开发 172
10.1 Windows窗体编程 172
10.1.1.NETFramework窗体编程相关基类 173
10.1.2 添加Windows窗体 176
10.1.3 添加控件 178
10.1.4 布局控件 179
10.1.5 设置控件属性 181
10.1.6 响应控件事件 182
10.2 常用控件 184
10.2.1 标签和基于按钮的控件 184
10.2.2 文本框控件 188
10.2.3 列表控件 190
10.2.4 日期时间相关控件 192
10.2.5 TreeView与ListView控件 195
10.2.6 TabControl控件 201
10.2.7 Splitter控件 205
10.2.8 控件排版 206
10.3 菜单设计 207
10.3.1 在VisualStudio2005开发环境中使用菜单 207
10.3.2 MainMenu类 209
10.3.3 MenuItem类 211
10.3.4 ContextMenu类 217
10.3.5 处理菜单事件 219
10.4 工具栏与状态栏设计 220
10.4.1 添加工具栏 220
10.4.2 响应工具栏事件处理 222
10.4.3 添加状态栏 222
10.5 MDI应用程序 224
10.5.1 C#Form类 224
10.5.2 构建MDI应用程序 226
10.6 对话框编程 228
10.6.1 通用对话框与CommonDialog类 229
10.6.2 打开/保存文件对话框 229
10.6.3 字体设置对话框 235
10.6.4 颜色设置对话框(ColorDialog) 238
10.6.5 设置打印对话框 240
10.7 C#GDI+编程 242
10.7.1 GDI+概述 242
10.7.2 Graphics类 243
10.7.3 Pen画笔类 247
10.7.4 Brush画刷类 249
10.7.5 ?Font字体类 250
10.8 小结 252
10.9 上机练习 252
10.10 习题 253

第11章 C#数据库编程与ADO.NET 256
11.1 ADO.NET概述 256
11.1.1 ADO.NET结构 256
11.1.2.NETFramework数据提供程序 258
11.1.3 在代码中使用ADO.NET 259
11.2 数据连接对象Connection 260
11.2.1 Connection对象 260
11.2.2 Connection对象的方法 261
11.2.3 Connection对象的事件 262
11.2.4 创建Connection对象 264
11.2.5 Connection对象的应用 265
11.3 执行数据库命令对象Command 266
11.3.1 Command对象的属性 267
11.3.2 Command对象的方法 267
11.3.3 创建Command对象 269
11.3.4 Command对象的应用 269
11.4 数据读取器对象DataReader 270
11.4.1 DataReader的属性 271
11.4.2 DataReader对象的方法 271
11.4.3 创建DataReader对象 272
11.4.4 DataReader对象的应用 272
11.5 数据适配器对象DataAdapter 274
11.5.1 DataAdapter对象的属性 274
11.5.2 DataAdapter对象的方法 274
11.5.3 DataAdapter对象的事件 276
11.5.4 创建DataAdapter对象 276
11.5.5 使用DataAdapter填充数据集 277
11.6 数据集对象DataSet 277
11.6.1 DataSet内部结构 277
11.6.2 创建DataSet 279
11.6.3 使用DataSet对象访问数据库 279
11.7 使用ADO.NET连接数据源 280
11.7.1 连接ODBC数据源 280
11.7.2 连接OLEDB数据源 282
11.7.3 访问Excel 282
11.7.4 访问文本文件 283
11.7.5 在C#中使用ADO.NET访问数据库 284
11.8 本章小结 288
11.9 上机练习 288
11.1 0习题 289

第12章 C#Web应用程序开发及ASP.NET 292
12.1 WebForm与ASP.NET2.0概述 292
12.1.1 WebForm概述 292
12.1.2 ASP.NET的工作原理 293
12.2 使用ASP.NET2.0创建Web应用程序 293
12.2.1 基于C#创建ASP.NET网站 294
12.2.2 理解Server控件 299
12.2.3 创建和使用主题外观 301
12.3 创建基于VisualC#的数据库Web应用程序 305
12.4 ASP.NET2.0配置管理 312
12.4.1 ASP.NET配置概述 312
12.4.2 ASP.NET配置文件 314
12.4.3 ASP.NET配置方案 316
12.4.4 ASP.NET和IIS配置 318
12.5 小结 319
12.6 上机练习 320
12.7 习题 321

TOP书摘

泛型是2.0版C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的确定推迟到客户端代码声明并实例化该类或方法时。例如,通过使用泛型类型参数T,可以编写其他客户端代码能够使用的类,而不致引入运行时强制转换或装箱操作的成本或风险,避免进行强制类型转换的需求提高类型安全性。这样开发人员可以更轻松地创建泛化的类和方法。本章将对泛型及其应用给予讲解。
  本章重点内容:
● 泛型的概念与实现
● 泛型的方法与约束
● 使用泛型
9.1 C# 泛型概述
  泛型在功能上类似于C++的模板,模板多年前就已存在C++上了,并且在C++上有大量成熟应用。开发人员在编写程序时,经常遇到两个模块的功能非常相似,只是处理的数据类型不同,如一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,针对这种情况,可以分别写多个类似的方法来处理每个数据类型,只是方法的参数类型不同;在C#中也可以定义存储的数据类型为Object类型,这样就可以通过装箱和拆箱操作来变相实现上述需求。同时C#还提供了更适合的泛型机制,专门用来解决这个问题。
9.1.1 泛型的引入
  首先以一个最经典的例子来说明上述遇到的问题,下面代码片段示例了一个存储int类型数据的堆栈:
public class Stack
{
private int count;
private int pointer = 0;
int[] data;
public Stack():this(100)
{}
public Stack(int size)
{
this.count = size;
this.data = new int[this.count];
}
public void Push(int item)
{
if(pointer >= count)
throw new StackOverflowException();
this.data[pointer] = item;
this.pointer++;
}
public int Pop()
{
this.pointer——;
if(this.pointer >= 0)
{
return this.data[this.pointer];
}
else
{
this.ointer = 0;
throw new InvalidOperationException("栈空!");
}
}
}
  上面给出的是最典型的堆栈实现代码,但是,当应用程序需要一个栈来保存string类型数据时,最简单的解决方法即是把上面的代码复制一份,把int改成string。但若应用程序还需要一个堆栈来保持自定义数据类型时这种方法显然不太适合了,这时可以考虑用一个通用的数据类型object来实现这个堆栈,参见如下代码片段。
public class Stack
{
private int count;
private int pointer = 0;
object[] data;
public Stack():this(100)
{}
public Stack(int size)
{
this.count = size;
this.data = new object[this.count];
}
public void Push(object item)
{
if(pointer >= count)
throw new StackOverflowException();
this.data[pointer] = item;
this.pointer++;
}
public object Pop()
{
this.pointer——;
if(this.pointer >= 0)
{
return this.data[this.pointer];
}
else
{
this.ointer = 0;
throw new InvalidOperationException("栈空!");
}
}
}
  这个栈写得很灵活,通过隐式或显式的装箱、拆箱操作,可以接收任何数据类型。但它后面却隐藏了很多的隐患。
  第一个问题是性能。在使用值类型时,必须将它们装箱以便推送和存储它们,并且在将值类型弹出堆栈时将其取消装箱。装箱和拆箱过程都会造成重大的性能损失,而且它还会增加托管堆上的压力,导致更多的垃圾收集工作,而这对于性能而言也非常不利。即使是在使用引用类型而不是值类型时,仍然存在性能损失,这是因为必须从Object向要与之交互的实际类型进行强制类型转换,从而造成强制类型转换开销。
Stack stack = new Stack();
stack.Push("1");
string number = (string)stack.Pop();
  第二个问题是类型安全。因为编译器允许在任何类型和Object之间进行强制类型转换,所以应用程序将丢失编译时类型安全。例如,以下代码可以正确编译,但是在运行时将引发无效强制类型转换异常:
Stack stack = new Stack();
stack.Push(1);
//隐患
string number = (string)stack.Pop();
  为了克服以上问题,在C#2.0中引入了泛型的概念。
9.1.2 什么是泛型
  通过泛型可以定义类型安全类,而不会损害类型安全、性能或工作效率。开发人员只需一次性地将服务器实现为一般服务器,同时可以用任何类型来声明和使用它。为此,需要使用“<”和“>”括号,以便将一般类型参数括起来。例如,可以按如下方式定义和使用一般堆栈:
public class Stack
{
 private T[] data;
 public T Pop(){...}
 public void Push(T item){...}

 public Stack(int i)
 {
  this.data = new T[i];
 }
}
  类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全。这个类的调用方法如下:
//实例化处理int类型数据的类对象
Stack a = new Stack(100);
a.Push(10);
//这一行编译不通过,因为类a只接收int类型的数据
a.Push("8888");
//不需要进行类型转换
int x = a.Pop();

//实例化处理string类型数据的类对象
Stack b = new Stack(100);
//这一行编译不通过,因为类b只接收string类型的数据
b.Push(10);
b.Push("8888");
//无须进行类型转换
string y = b.Pop();
  这个类与使用object实现的类有截然不同的区别,即:
  (1) 它是类型安全的。如果实例化为int类型的栈,就不能处理string及其他类型的数据。
  (2) 无须装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无须装箱和折箱。
  (3) 无须类型转换。
  C#泛型类Stack在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以可以这样理解泛型:泛型类的不同示例化是不同的数据类型,如Stack和Stack是两个完全没有任何关系的类,可以把它比作是类A和类B的关系。
9.1.3 泛型实现
  表面上,C#泛型的语法看起来与C++模板类似,但是编译器实现和支持它们的方式存在重要差异。与C++模板相比,C#泛型可以提供增强的安全性,但是在功能方面也受到某种程度的限制。
  在.NET 2.0中,泛型在IL(中间语言)和CLR本身中具有本机支持。在编译一般C#服务器端代码时,编译器会将其编译为IL,就像其他任何类型一样。但是,IL只包含实际特定类型的参数或占位符。此外,一般服务器的元数据包含一般信息。
  客户端编译器使用该一般元数据来支持类型安全。当客户端提供特定类型而不是一般类型参数时,客户端的编译器将用指定的类型实参来替换服务器元数据中的一般类型参数。这会向客户端的编译器提供类型特定的服务器定义,就好像从未涉及到泛型一样。这样,客户端编译器就可以确保方法参数的正确性,实施类型安全检查。
  如果客户端指定引用类型,则JIT编译器将服务器IL中的一般参数替换为Object,并将其编译为本机代码。在以后的任何针对引用类型而不是一般类型参数的请求中,都将使用该代码。请注意,采用这种方式,JIT编译器只会重新使用实际代码。实例仍然按照它们离开托管堆的大小分配空间,并且没有强制类型转换。
9.1.4 泛型方法
  除了通常的泛型类型外,还可以定义泛型接口及泛型方法等,在此仅给出泛型方法的定义与使用。
  泛型方法是使用类型参数声明的方法。C#泛型机制不支持在除方法外的其他成员(包括属性、事件、索引器、构造器、析构器)的声明上包含类型参数,但这些成员本身可以包含在泛型类型中,并使用泛型类型的类型参数。泛型方法既可以包含在泛型类型中,也可以包含在非泛型类型中。
  下面以一个简单示例说明泛型方法的声明与调用,该示例定义了一个泛型方法Swap,用于实现不同类型的两个变量的交换,其中类型参数T在方法调用时被指定。详细内容请参考代码清单9.1。
代码清单9.1
using System;
using System.Collections.Generic;
using System.Text;

class Program
{
//定义泛型方法
static void Swap(ref T swap1, ref T swap2)
{
T temp;
temp = swap1;
swap1 = swap2;
swap2 = temp;
}
static void Main(string[] args)
{
int a = 2;
int b = 4;
//实例化泛型方法并调用
Swap(ref a, ref b);
Console.WriteLine("交换后a="+ a + ",b=" + b);
string sa = "i";
string sb = "you";
Console.WriteLine("交换前"+ sa + " Love "+sb);
//实例化泛型方法,并调用
Swap(ref sa, ref sb);
Console.WriteLine("交换后" + sa + " Love " + sb);

Console.ReadLine();
}
}
  编译、执行,输出结果为:
交换后a=4,b=2
交换前i Love you
交换后you Love i
  .NET中的泛型机制使开发人员可以重用代码。类型和内部数据可以在不导致代码膨胀的情况下更改,而不管使用的是值类型还是引用类型。开发人员可以一次性地开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它,并且全部具有编译器支持和类型安全。因为一般代码不会强行对值类型进行装箱和取消装箱,或者对引用类型进行向下强制类型转换,所以性能得到显著提高。对于值类型,性能通常会提高200%;对于引用类型,在访问该类型时,可以预期性能最多提高100%(当然,整个应用程序的性能可能会提高,也可能不会提高)。
9.2 泛 型 约 束
  使用C#泛型,编译器会将一般代码编译为IL,而不管客户端将使用什么样的类型实参。因此,一般代码可以尝试使用与客户端使用的特定类型实参不兼容的一般类型参数的方法、属性或成员。这是不可接受的,因为它相当于缺少类型安全。
  C#泛型要求对“所有泛型类型或泛型方法的类型参数”的任何假定,都要基于“显式的约束”,以维护C#所要求的类型安全。“显式约束”并非必需,如果没有指定“显式约束”,泛型类型参数将只能访问System.Object类型中的公有方法。在C#中通过where关键字来指定类型参数必须满足的约束条件,约束的语法格式一般为:
  class class-name where type-param:constraints{}
其中constraints是一个逗号分割的约束列表。
  C#中包含以下几种约束类型:
● h可以使用“基类约束”(base class constraint)来指定某个基类或其派生类必须出现在类型实参中。这种约束是通过指定基类名称来实现的。
● 可以使用“接口约束”(interface constraint)来指定某个类型实参必须实现一个或多个接口。这种约束是通过指定接口名称来实现的。
● 可以要求类型实参必须提供一个无参数的构造函数,这被称为“构造函数约束”(constructor constraint)。它是通过new()指定的。
● 可以通过关键字class或structure指定“引用/值类型约束”(reference type constraint)来限制某个类型实参必须是引用/值类型。
9.2.1 基类约束
  使用基类约束可以指定某个类型实参必须继承的基类,基类约束有两个功能:
  (1) 它允许在泛型类中使用由约束指定的基类所定义的成员。例如,可以调用基类的方法或者使用基类的属性。如果没有基类约束,编译器就无法知道某个类型实参拥有哪些成员。通过提供基类约束,编译器将知道所有的类型实参都拥有由指定基类所定义的成员。
  (2) 确保类型实参支持指定的基类类型参数。这意味着对于任意给定的基类约束,类型实参必须要么是基类本身,要么是派生于该基类的类,如果试图使用没有继承指定基类的类型实参,就会导致编译错误。
  基类约束使用下面形式的where子句:
  where T:base-class-name
  T是类型参数的名称,base-class-name是基类的名称,这里只能指定一个基类。
  下面以一个简单示例来说明派生约束,详细内容请参考代码清单9.2。
代码清单9.2
using System;
using System.Collections.Generic;
using System.Text;
class A
{
public void Func1()
{ }
}
class B
{
public void Func2()
{ }
}

class C where S : A //基类约束
{
    public C(S s)
    {
      //S的变量可以调用Func1方法
      s.Func1();
    }
}

class Program
{
    static void Main(string[] args)
    {
      //实例化A对象myA
      A myA = new A();
      //泛型类C由于存在基类约束,类型参数值只能为A
      C myCA = new C(myA);
      //语法错误
      C myCB = new C(myA);
    }
}
  从代码中可以看到,通过指定泛型的基类约束,可以在类C中调用类A的Func1方法。同时在Main函数中实例化泛型类时,类型参数的值只能为类A或其派生类而不能为其他类型值。
9.2.2 接口约束
  接口约束用于指定某个类型参数必须应用的接口。接口的两个主要功能和基类约束完全一样。
  接口约束的语法格式为:
  where T:interface-name
  interface-name是接口的名称,可以通过使用由逗号分割的列表来同时指定多个接口。可以对泛型同时进行基类约束和接口约束,如果某个约束同时包含基类和接口,则先指定基类列表,再指定接口列表。
9.2.3 构造函数约束
  new()构造函数约束允许开发人员实例化一个泛型类型参数的对象。一般情况下,无法创建一个泛型类型参数的实例。然而,new()约束改变了这种情况,它要求类型参数必须提供一个无参数的构造函数。
  在使用构造函数约束时,可以通过调用该无参构造函数来创建对象。构造函数约束的语法格式如下:
  where T : new()
  使用构造函数约束时应注意两点:
  (1) 它可以与其他约束一起使用,但是必须位于约束列表的末端。
  (2) 构造函数约束仅允许开发人员使用无参构造函数来构造一个对象,即使同时存在其他的构造函数。换句话说,不允许给类型参数的构造函数传递实参。下面以一个示例来说明构造函数约束,详细内容请参考代码清单9.3。
代码清单9.3
class A
{
public A() //无参数构造函数
{ }
}
class B
{
public B(int i) //有参数构造函数
{ }
}
class C where T : new() //构造函数约束
{
T t;
public C()
{
//在派生类中实例化类型参数
t = new T();
}
}
class D
{
public void Func()
{
C
c = new C();
C d = new C();
}
}
  d对象在编译时报错:The type B must have a public parameterless constructor in order to use it as parameter 'T' in the generic type or method C
  在类B中重载一个无参数的构造函数:
public B()
{
……
}
  此时编译成功,即定义构造函数约束情况下约束类必须实现无参数构造函数。
9.2.4 值/引用类型约束
  如果引用类型和值类型之间的差别对于泛型代码非常重要,那么这些约束就非常有用,值/引用类型约束的基本形式为:
  where T : class
  where T : struct
  若同时存在其他约束,class或struct关键字必须位于列表的开头。下面以一个简单示例来说明该类型约束,详细内容请参考如下代码片段:
public struct a{...}
public class b{...}

class c
where t : struct //值类型约束
{
// t在这里面是一个值类型
}
c
c = new c(); // 正确,a是一个值类型
c c = new c(); // 错误,b是一个引用类型
9.3 使用泛型
  泛型提高了代码的重用性,为编程提供了极大的方便,下面通过实例来介绍如何在程序中定义和使用泛型。在该示例中实现了一个自定义类型——Student类,用来描述学生信息,基于示例的原因,该类仅描述了学生姓名及年龄两个特性。示例实现了泛型LinkedList类,并分别实现了int及Student类型实例,用以存储并操作int及Student类型的数据。
  程序实现步骤如下。
  (1) 启动Visual Studio 2005,新建一个控制台应用程序,命名为GenericSamp。
  (2) 修改自动生成的Program.cs文件,在GenericSamp命名空间中添加Student类。
public class Student //定义学生类
{
private string StudentName; //学生姓名
private int StudentAge; //年龄
public Student(string name,int age) //构造函数
{
this.StudentName = name;
this.StudentAge = age;
}
//重载ToString方法,实现学生类的输出
public override string ToString()
{
return this.StudentName+":年龄"+this.StudentAge+"岁";
}
}
  (3) 接着实现LinkedList类的节点类Node,在该节点类中需要使用到泛型,节点的类型由T指代,在实例化时进行指定。Node类中除了一般的属性与方法外,还包括Append方法,该方法接受一个Node类型的参数,方法将把传递进来的Node添加到列表中的最后位置。过程为:首先检测当前Node的next字段,看它是不是null。如果是,那么当前Node就是最后一个Node,将当前Node的next属性指向传递进来的新节点,这样,就把新Node插入到了链表的尾部。
  如果当前Node的next字段不是null,说明当前node不是链表中的最后一个node。因为next字段的类型也是node,所以调用next字段的Append方法(注:递归调用),再一次传递Node参数,这样继续下去,直到找到最后一个Node为止。
//定义节点类
public class Node
{
T data; //T类型的数据域
Node next; //指向域

public Node(T data) //构造方法
{
this.data = data;
this.next = null;
}

public T Data //定义Data属性
{
get { return this.data; }
set { data = value; }
}

public Node Next //定义节点属性
{
get { return this.next; }
set { this.next = value; }
}
//实现添加节点
public void Append(Node newNode) //
{
if (this.next == null)
{
this.next = newNode;
}
else
{
next.Append(newNode);
}
}
//重载ToString方法,输出节点
public override string ToString()
{
string output = data.ToString();

if (next != null)
{
output += ", " + next.ToString();
}
return output;
}
}
  (4) 实现节点类Node后即可定义LinkedList类,LinkedList 类不需要构造函数(使用编译器创建的默认构造函数),但是需要创建一个公共方法Add(),这个方法把data存储到线性链表中。这个方法首先检查表头是不是null,如果是,它将使用data创建节点,并将这个节点作为表头,如果不是null,它将创建一个新的包含data的节点,并调用表头的Append方法,如下面的代码所示:
public class LinkedList
{
Node headNode = null; //头节点
public void Add(T data) //将数据添加到链表中
{
if ( headNode == null )
{
headNode = new Node(data);
}
else
{
headNode.Append(new Node(data));
}
}

//为线性链表创建一个索引器
public T this[int index]
{
get
{
int temp = 0;
Node node = headNode;
while (node != null && temp <= index)
{
if (temp == index)
{
return node.Data;
}
else
{
node = node.Next;
}
temp++;
}
return default(T);
}
}

//本类的ToString方法被重写,用以调用headNode的ToString()方法
public override string ToString()
{
if ( this.headNode != null )
{
return this.headNode.ToString();
}
else
{
return string.Empty;
}
}
}
  (5) 编写主函数,测试LinkedList泛型类的功能,首先实例化一个int类型LinkedList对象intList,向其中添加5个整数并输出该LinkedList对象intList的内容,接着实例化一个Student类型LinkedList对象StudentList,调用Add方法向其中添加四个Student对象并输出StudentList的内容;最后通过索引,分别获取两个对象的第二个节点。
class Program
{
static void Main(string[] args)
{
//实例化一个int类型的LinkedList对象
LinkedList intList = new LinkedList();
for (int i = 0; i < 5; i++) //向其中添加五个节点
{
intList.Add(i);
}
Console.WriteLine("整型List的内容为:");
Console.WriteLine(intList);

//实例化一个Student类型的LinkedList对象
LinkedList StudentList = new LinkedList();
//添加节点
StudentList.Add(new Student("张三",20));
StudentList.Add(new Student("李四",21));
StudentList.Add(new Student("王五",23));
StudentList.Add(new Student("赵六",19));
Console.WriteLine("Student类型List的内容为:");
Console.WriteLine(StudentList);
//获取特定位置节点
Console.WriteLine("第二个整数为:" + intList[1]);
Student s = StudentList [1];
Console.WriteLine("第二个学生为" + s);
Console.ReadLine();
}
}
}
  编译、执行,输出结果如图9-1所示。

图9-1 输出结果
9.4 小 结
  本章介绍了C#中泛型的基本概念、如何定义及使用泛型等内容。C# 泛型是开发工具库中的一个非常有用的工具。它们可以提高性能、类型安全以及代码质量,减少重复性的编程任务,简化总体编程模型,而这一切都是通过优雅的、可读性强的语法完成的。尽管C#泛型的根基是C++模板,但C#通过提供编译时安全和支持将泛型提高到了一个新水平。C#利用了两阶段编译、元数据以及诸如约束和一般方法之类的创新性的概念。毫无疑问,C#的将来版本将继续发展泛型,以便添加新的功能,并且将泛型扩展到诸如数据访问或本地化之类的其他.NET Framework领域。
9.5 上 机 练 习
  (1) 上机实现本章代码清单9.1~9.3所示示例程序。
  (2) 实现一个泛型方法Find,要求该方法实现从某一个类型的数组中查找是否存在与给定元素相等的元素,如果有,输出其位置,否则输出–1。
  (3) 实现链式堆栈泛型类,包括出栈、入栈以及获得栈顶元素等方法,并要求编写测试程序,实例化int类型堆栈及string类型堆栈,测试其功能。
  (4) 实现一个链表类List,包含链表元素的添加、删除、修改及索引功能:
● 实现整型链表类Listint,用来存储并处理整型数据。
● 实现一个Object类型的链表类,用来存储并处理自定义类型数据
● 实现一个泛型链表类
9.6 习 题
一、选择题
  (1) 下列哪些说法是泛型具有的优点( )?
  A:泛型机制提高了代码的质量与重用性。
  B:泛型是类型安全的。
  C:使用泛型有利于提高程序的性能。
  D:C#中泛型机制是通过模板实现的。
  (2) 泛型约束包括以下哪些类型( )?
  A:基类约束。
  B:接口约束。
  C:值/引用类型约束。
  D:构造函数约束。
  (3) 泛型的基类约束具有如下哪些功能( )?
  A:它允许在泛型类中使用指定约束类的成员。
  B:泛型类实例化可以使用任意类型为类型参数赋值。
  C:基类约束下类型参数的值只能是约束类及其派生类。
  D:在与其他类型约束混合使用的情况下,基类约束应被放到最后。
  (4) 下列关于构造函数约束的描述中正确的是( )。
  A:它可以与其他约束一起使用,但是必须位于约束列表的末端。
  B:构造函数约束允许开发人员实例化一个泛型类型的对象。
  C:构造函数约束要求类型参数必须提供一个无参数的构造函数。
  D:Office文档由开发人员创建。
  (5) 下列关于泛型使用的描述中正确的是( )。
  A:使用泛型需要引入System.Collections.Generic命名空间。
  B:泛型类型中可以定义多个类型参数。
  C:可以在泛型类型中定义泛型方法、委托以及事件等。
  D:在程序中可以定义嵌套泛型。
二、填空题
  (1) C#中的泛型类型与C++中的 。
  (2) C#是在 版本中开始支持泛型的。
  (3) 假设有如下堆栈类的定义:
public class Stack
{
 private T[]data;
…….
}
  如何实例化一个堆栈对象处理整型数据 。
  (4) 除了定义泛型类型外还可以定义 。
  (5) 泛型方法中的类型参数在 被指定。
  (6) C#中使用 关键字表示泛型的值类型约束。
  (7) 可以定义泛型类型与 等,但不可以定义 。
三、思考题
  (1) 使用方法的重载和泛型方法有何异同,二者的优缺点各是什么?
  (2) 在一个泛型类中是否可以定义泛型方法,如何实现?

TOP 其它信息

装  帧:平装

页  数:321

版  次:1版

开  本:16开

正文语种:中文

加载页面用时:78.1253