2024年1月5日发(作者:)

MSDN C# 编程指南

2021年01月5 北京

MSDN C#编程指南

1. C# 语言和 .NET Framework 介绍 .................................................................................................. 6

2. C#与 C++ 比较 .................................................................................................................................... 7

3. C# 程序的通用结构 .............................................................................................................................. 9

4. 命令行参数 ........................................................................................................................................... 10

5. 装箱和取消装箱 ................................................................................................................................... 12

6. 如何:转换十六进制字符串 ............................................................................................................... 14

7. 使用字符串 ........................................................................................................................................... 16

8. 如何:使用正那么表达式搜索字符串 ............................................................................................... 20

9. Lambda 表达式 .................................................................................................................................. 22

10. 匿名方法 ....................................................................................................................................... 25

11. 转换运算符 ................................................................................................................................... 27

12. 使用转换运算符 ........................................................................................................................... 27

13. 如何:在结构之间实现用户定义的转换 ................................................................................... 29

14. 如何:使用运算符重载创立复数类 ........................................................................................... 31

15. 继承 ............................................................................................................................................... 32

16. 抽象类、密封类及类成员 ........................................................................................................... 32

17. 使用 Override 和 New 关键字进行版本控制 ...................................................................... 33

18. 了解何时使用 Override 和 New 关键字 .............................................................................. 35

19. 如何:重写 ToString 方法 ....................................................................................................... 37

20. 接口 ............................................................................................................................................... 38

21. 显式接口实现 ............................................................................................................................... 39

22. 如何:显式实现接口成员 ........................................................................................................... 40

23. 如何:使用继承显式实现接口成员 ........................................................................................... 42

24. 成员 ............................................................................................................................................... 44

25. 方法 ............................................................................................................................................... 44

26. 传递值类型参数 ........................................................................................................................... 47

27. 传递引用类型参数 ....................................................................................................................... 48

28. 使用构造函数 ............................................................................................................................... 51

29. 实例构造函数 ............................................................................................................................... 53

30. 私有构造函数 ............................................................................................................................... 56

31. 静态构造函数 ............................................................................................................................... 57

32. 如何:编写复制构造函数 ........................................................................................................... 58

33. 析构函数 ....................................................................................................................................... 59

34. 对象和集合初始值设定项 ........................................................................................................... 61

35. 如何:在不调用构造函数的情况下初始化对象 ....................................................................... 63

36. 如何:使用集合初始值设定项初始化字典 ............................................................................... 63

37. 字段 ............................................................................................................................................... 64

38. 常量 ............................................................................................................................................... 65

39. 嵌套类型 ....................................................................................................................................... 65

40. 访问修饰符 ................................................................................................................................... 66

学习文档 仅供参考

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60.

61.

62.

63.

64.

65.

66.

67.

68.

69.

70.

71.

72.

73.

74.

75.

76.

77.

78.

79.

80.

81.

82.

83.

84.

85.

分部类和方法 ............................................................................................................................... 67

静态类和静态类成员 ................................................................................................................... 71

如何:了解向方法传递结构和向方法传递类引用之间的区别 ............................................... 74

匿名类型 ....................................................................................................................................... 75

如何:在查询中返回元素属性的子集 ....................................................................................... 76

如何:实现和调用自定义扩展方法 ........................................................................................... 80

如何:为枚举创立新方法 ........................................................................................................... 82

隐式类型的局部变量 ................................................................................................................... 83

如何:在查询表达式中使用隐式类型的局部变量和数组 ....................................................... 85

属性 ............................................................................................................................................... 86

使用属性 ....................................................................................................................................... 87

接口属性 ....................................................................................................................................... 93

非对称访问器可访问性 ............................................................................................................... 95

如何:声明和使用读/写属性 ...................................................................................................... 99

自动实现的属性 ......................................................................................................................... 101

如何:使用自动实现的属性实现轻量类 ................................................................................. 101

索引器 ......................................................................................................................................... 101

使用索引器 ................................................................................................................................. 102

接口中的索引器 ......................................................................................................................... 106

属性和索引器之间的比较 ......................................................................................................... 108

委托 ............................................................................................................................................. 108

使用委托 ..................................................................................................................................... 109

带有命名方法的委托与带有匿名方法的委托 ......................................................................... 111

何时使用委托而不使用接口 ..................................................................................................... 113

委托中的协变和逆变 ................................................................................................................. 113

如何:合并委托〔多路播送委托〕 ......................................................................................... 114

如何:声明、实例化和使用委托 ............................................................................................. 116

事件 ............................................................................................................................................. 119

如何:订阅和取消订阅事件 ..................................................................................................... 119

如何:发布符合 .NET Framework 准那么的事件.............................................................. 121

如何:引发派生类中的基类事件 ............................................................................................. 123

如何:实现接口事件 ................................................................................................................. 127

如何:使用字典存储事件实例 ................................................................................................. 130

泛型 ............................................................................................................................................. 133

泛型介绍 ..................................................................................................................................... 133

泛型的优点 ................................................................................................................................. 135

泛型类型参数 ............................................................................................................................. 136

类型参数的约束 ......................................................................................................................... 137

泛型类 ......................................................................................................................................... 141

泛型接口 ..................................................................................................................................... 142

泛型方法 ..................................................................................................................................... 147

泛型和数组 ................................................................................................................................. 148

泛型委托 ..................................................................................................................................... 149

泛型代码中的默认关键字 ......................................................................................................... 150

C++ 模板和 C# 泛型之间的区别 .......................................................................................... 151

学习文档 仅供参考

86.

87.

88.

89.

90.

91.

92.

93.

94.

95.

96.

97.

98.

99.

100.

101.

102.

103.

104.

105.

106.

107.

108.

109.

110.

111.

112.

113.

114.

115.

116.

117.

118.

119.

120.

121.

122.

123.

124.

125.

126.

127.

128.

129.

130.

运行库中的泛型 ......................................................................................................................... 151

.NET Framework 类库中的泛型 ........................................................................................... 152

泛型和反射 ................................................................................................................................. 152

泛型和属性 ................................................................................................................................. 154

泛型类型中的变化 ..................................................................................................................... 154

LINQ 查询表达式 ..................................................................................................................... 163

查询表达式根底知识 ................................................................................................................. 164

如何:在 C# 中编写 LINQ 查询 .......................................................................................... 170

迭代器 ......................................................................................................................................... 172

使用迭代器 ................................................................................................................................. 172

如何:为整数列表创立迭代器块 ............................................................................................. 174

如何:为泛型列表创立迭代器块 ............................................................................................. 175

命名空间 ..................................................................................................................................... 177

使用命名空间 ............................................................................................................................. 178

如何:使用命名空间别名限定符 ............................................................................................. 180

如何:使用 My 命名空间 ....................................................................................................... 182

可以为 null 的类型 .................................................................................................................. 183

使用可以为 null 的类型 .......................................................................................................... 184

装箱可以为 null 的类型 .......................................................................................................... 186

如何:标识可以为 null 的类型 .............................................................................................. 187

如何:从 bool? 平安地强制转换为 bool ............................................................................ 188

不平安代码和指针 ..................................................................................................................... 188

固定大小的缓冲区 ..................................................................................................................... 189

如何:使用 Windows ReadFile 函数 .................................................................................. 190

指针类型 ..................................................................................................................................... 192

指针转换 ..................................................................................................................................... 194

如何:使用指针复制字节数组 ................................................................................................. 195

XML 文档注释 ........................................................................................................................... 196

建议的文档注释标记 ................................................................................................................. 196

处理 XML 文件 ......................................................................................................................... 197

文档标记的分隔符 ..................................................................................................................... 198

如何:使用 XML 文档功能 ..................................................................................................... 200

应用程序域 ................................................................................................................................. 203

在另一个应用程序域中执行代码 ............................................................................................. 203

如何:创立和使用应用程序域 ................................................................................................. 205

程序集和全局程序集缓存 ......................................................................................................... 205

友元程序集 ................................................................................................................................. 205

如何:确定文件是否为程序集 ................................................................................................. 208

如何:加载和卸载程序集 ......................................................................................................... 209

如何:与其他应用程序共享程序集 ......................................................................................... 209

属性 ............................................................................................................................................. 209

使用属性 ..................................................................................................................................... 210

创立自定义属性 ......................................................................................................................... 210

消除属性目标的歧义性 ............................................................................................................. 211

使用反射访问属性 ..................................................................................................................... 213

学习文档 仅供参考

131.

132.

133.

134.

135.

136.

137.

138.

139.

140.

141.

142.

143.

144.

145.

146.

147.

148.

149.

150.

151.

152.

153.

154.

155.

如何:使用属性创立 C/C++ 联合 ......................................................................................... 215

集合类 ......................................................................................................................................... 216

如何:使用 foreach 访问集合类........................................................................................... 216

异常和异常处理 ......................................................................................................................... 218

使用异常 ..................................................................................................................................... 219

异常处理 ..................................................................................................................................... 221

创立和引发异常 ......................................................................................................................... 223

编译器生成的异常 ..................................................................................................................... 225

如何:使用 try/catch 处理异常 ............................................................................................ 226

如何:使用 finally 执行清理代码 ......................................................................................... 227

如何:捕捉非 CLS 异常 ......................................................................................................... 228

互操作性 ..................................................................................................................................... 229

互操作性概述 ............................................................................................................................. 229

如何:使用平台调用播放波形文件 ......................................................................................... 230

COM 类例如 .............................................................................................................................. 232

线程处理 ..................................................................................................................................... 233

使用线程处理 ............................................................................................................................. 233

线程同步 ..................................................................................................................................... 234

如何:创立和终止线程 ............................................................................................................. 236

如何:对制造者线程和使用者线程进行同步 ......................................................................... 239

如何:使用线程池 ..................................................................................................................... 245

性能 ............................................................................................................................................. 248

反射 ............................................................................................................................................. 248

如何:创立和使用 C# DLL ..................................................................................................... 249

平安性 ......................................................................................................................................... 250

学习文档 仅供参考

1. C# 语言和 .NET Framework 介绍

C# 语言

C# 语法表现力强,而且简单易学。C# 的大括号语法使任何熟悉 C、C++ 或 Java 的人都可以立即上手。了解上述任何一种语言的开发人员通常在很短的时间内就可以开始使用 C# 高效地进行工作。C# 语法简化了 C++ 的诸多复杂性,并提供了很多强大的功能,例如可为 null 的值类型、枚举、委托、lambda 表达式和直接内存访问,这些都是 Java 所不具备的。C# 支持泛型方法和类型,从而提供了更出色的类型平安和性能。C# 还提供了迭代器,允许集合类的实施者定义自定义的迭代行为,以便容易被客户端代码使用。在 C# 3.0 中,语言集成查询 (LINQ) 表达式使强类型查询成为了一流的语言构造。

作为一种面向对象的语言,C# 支持封装、继承和多态性的概念。所有的变量和方法,包括 Main 方法〔应用程序的入口点〕,都封装在类定义中。类可能直接从一个父类继承,但它可以实现任意数量的接口。重写父类中的虚方法的各种方法要求 override 关键字作为一种防止意外重定义的方式。在

C# 中,结构类似于一个轻量类;它是一种堆栈分配的类型,可以实现接口,但不支持继承。

除了这些根本的面向对象的原理之外,C# 还通过几种创新的语言构造简化了软件组件的开发,这些结构包括:

封装的方法签名〔称为“委托〞〕,它实现了类型平安的事件通知。

属性 (Property),充当私有成员变量的访问器。

属性 (Attribute),提供关于运行时类型的声明性元数据。

内联 XML 文档注释。

语言集成查询 (LINQ),提供了跨各种数据源的内置查询功能。

在 C# 中,如果必须与其他 Windows 软件〔如 COM 对象或本机 Win32 DLL〕交互,那么可以通过一个称为“互操作〞的过程来实现。互操作使 C# 程序能够完本钱机 C++ 应用程序可以完成的几乎任何任务。在直接内存访问必不可少的情况下,C# 甚至支持指针和“不平安〞代码的概念。

C# 的生成过程比 C 和 C++ 简单,比 Java 更为灵活。没有单独的头文件,也不要求按照特定顺序声明方法和类型。C# 源文件可以定义任意数量的类、结构、接口和事件。

NET Framework 平台体系结构

C# 程序在 .NET Framework 上运行,它是 Windows 的一个不可或缺的组件,包括一个称为公共语言运行库 (CLR) 的虚拟执行系统和一组统一的类库。CLR 是 Microsoft 的公共语言根底结构 (CLI)

的商业实现。CLI 是一种国际标准,是用于创立语言和库在其中无缝协同工作的执行和开发环境的根底。

用 C# 编写的源代码被编译为一种符合 CLI 标准的中间语言 (IL)。IL 代码与资源〔例如位图和字符串〕一起作为一种称为程序集的可执行文件存储在磁盘上,通常具有的扩展名为 .exe 或 .dll。程序集包含清单,它提供有关程序集的类型、版本、区域性和平安要求等信息。

执行 C# 程序时,程序集将加载到 CLR 中,这可能会根据清单中的信息执行不同的操作。然后,如果符合平安要求,CLR 就会执行实时 (JIT) 编译以将 IL 代码转换为本机机器指令。CLR 还提供与自动垃圾回收、异常处理和资源管理有关的其他效劳。由 CLR 执行的代码有时称为“托管代码〞,它与编译为面向特定系统的本机机器语言的“非托管代码〞相对应。以下列图阐释了 C# 源代码文件、.NET Framework 类库、程序集和 CLR 的编译时与运行时的关系。

学习文档 仅供参考

语言互操作性是 .NET Framework 的一项主要功能。因为由 C# 编译器生成的 IL 代码符合公共类型标准 (CTS),因此从 C# 生成的 IL 代码可以与从 Visual Basic、Visual C++、Visual J# 的 .NET 版本或者其他 20 多种符合 CTS 的语言中的任何一种生成的代码进行交互。单一程序集可能包含用不同 .NET 语言编写的多个模块,并且类型可以相互引用,就像它们是用同一种语言编写的。

除了运行时效劳之外,.NET Framework 还包含一个由 4000 多个类组成的内容详尽的库,这些类被组织为命名空间,为从文件输入和输出、字符串操作、XML 分析到 Windows 窗体控件的所有内容提供了各种有用的功能。典型的 C# 应用程序使用 .NET Framework 类库广泛地处理常见的“日常〞任务。

2. C#与 C++ 比较

功能

继承:在 C++ 中,类和结构实际上是相同的,而在 C# 中,它们很不一样。C# 类可以实现任意数量的接口,但只能从一个基类继承。而且,C# 结构不支持继承,也不支持显式默认构造函数〔默认情况下提供一个〕。

参考主题

接口

struct

数组 数组:在 C++ 中,数组只是一个指针。在 C# 中,数组是包含方法和属性的对象。例如,可通过 Length 属性查询数组的大小。C# 数组还使用索引器〔验证用于访问数组的索引器

各个索引〕。声明 C# 数组的语法不同于声明 C++ 数组的语法:在 C# 中,“[]〞标记出现在数组类型之后,而非变量之后。

布尔值:在 C++ 中,bool 类型实质上是一个整数。在 C# 中,不存在 bool 类型与bool

其他类型之间的相互转换。

学习文档 仅供参考

long 类型:long 类型在 C# 中为 64 位,而在 C++ 中为 32 位。

long

传递参数:在 C++ 中,除非显式通过指针或引用传递,否那么所有变量都通过值传递。结构

在 C# 中,除非显式通过具有 ref 或 out 参数修饰符的引用传递,否那么类通过引用传递,而结构通过值传递。

switch 语句:与 C++ 中的 switch 语句不同,C# 不支持从一个 case 标签贯穿到switch

另一个 case 标签。

委托:C# 委托大致类似于 C++ 中的函数指针,是类型平安和可靠的。

delegate

类 ref out

基类方法:C# 支持用于调用派生类中重写基类成员的 base 关键字。而且,在 C# 中,base

使用 override 关键字重写虚拟或抽象方法是显式的。

请参见 override 的例如

方法隐藏:C++ 通过继承支持方法的隐式“隐藏〞。在 C# 中,必须使用 new 修饰符new

来显式隐藏继承的成员。

预处理器指令用于条件编译。C# 中不使用头文件。

异常处理:无论是否引发异常,C# 都提供 finally 关键字以提供应执行的代码。

C# 预处理器指令

try-finally

try-catch-finally

C# 运算符:C# 支持其他运算符,如 is 和 typeof。它还引入了某些逻辑运算符的不& 运算符 | 运算符

同功能。

^ 运算符 is

typeof

typedef 关键字。在 C++ 中,typedef 用于为已声明的类型创立更短或更方便的名称。using 指令〔C# 参考〕

在 C# 中,using 指令提供此功能。

extern 关键字:在 C++ 中,extern 用于导入类型。在 C# 中,extern 用于为使extern

用同一程序集的不同版本创立别名。

static 关键字:在 C++ 中,static 既可用于声明类级实体,也可用于声明特定于某模static

块的类型。在 C# 中,static 仅用于声明类级实体。

C# 中的 Main 方法和 C++ 中的 main 函数的声明方式不同。在 C# 中,它是大写Main() 和命令行参数

的,并且始终是 static 的。此外,在 C# 中,对处理命令行参数的支持要可靠得多。

在 C# 中,只有在 unsafe 模式下才允许使用指针。

在 C# 中以不同的方式执行重载运算符。

字符串:在 C++ 中,字符串只是字符的数组。在 C# 中,字符串是支持可靠搜索方法的对象。

unsafe

C# 运算符

字符串

String

学习文档 仅供参考

foreach 关键字使您可以循环访问数组和集合。

foreach, in

全局:在 C# 中,不支持全局方法和全局变量。方法和变量必须包含在 class 或 struct

C# 程序的常规结构

之内。

#define 预处理指令:在 C++ 中,#define 指令通常用于声明常量值。在 C# 中,static〔C# 参考〕

#define 指令不可用于此目的。在 C# 中,最好将常量定义为枚举类型〔仅限整数值〕const〔C# 参考〕

或者定义为类或结构的静态成员。如果具有多个像这样的常量,可以考虑创立一个单独的enum〔C# 参考〕

“Constants〞类来保存这些常量。

导入类型:在 C++ 中,多个模块公用的类型放置在头文件中。在 C# 中,可通过元数据获取此信息。

C# 中的局部变量在初始化前不能使用。

内存管理:C++ 语言不提供垃圾回收功能;在进程终止前,未显式释放的内存仍保持已分配的状态。C# 语言提供垃圾回收功能。

析构函数:C# 具有用于确定地释放非托管资源的不同语法。 析构函数

using 语句〔C# 参考〕

构造函数:与 C++ 类似,如果在 C# 中不提供类构造函数,那么为您自动生成一个默认构造函数。该默认构造函数将所有字段初始化为它们的默认值。

C# 不支持位域。

C# 的输入/输出效劳和格式设置依赖于 .NET Framework 的运行时库。

实例构造函数

默认值表

C++ 位字段

C# 语言教程

格式化数值结果表

在 C# 中,方法参数不能有默认值。如果要获得同样的效果,请使用方法重载。

在 C# 中,为类型参数化提供泛型类型和泛型方法的方式与 C++ 模板类似,尽管存在显著的差异。

as 关键字与标准强制转换类似,不同之处在于:如果转换失败,那么返回值为 null,而不是引发异常。这与使用 C++ 中的 static_cast〔与 dynamic_cast 不同,它不执行运行时检查,因此失败时不引发异常〕相似。

as〔C# 参考〕

编译器错误 CS0241

C# 中的泛型

using

元数据概述

方法

垃圾回收

3. C# 程序的通用结构

C# 程序可由一个或多个文件组成。每个文件都可以包含零个或零个以上的命名空间。一个命名空间除了可包含其他命名空间外,还可包含类、结构、接口、枚举、委托等类型。以下是 C# 程序的主干,它包含所有这些元素。

// A skeleton of a C# program

using System;

学习文档 仅供参考

namespace YourNamespace

{

class YourClass

{

}

struct YourStruct

{

}

interface IYourInterface

{

}

delegate int YourDelegate();

enum YourEnum

{

}

namespace YourNestedNamespace

{

struct YourStruct

{

}

}

class YourMainClass

{

static void Main(string[] args)

{

//Your program

}

}

}

4. 命令行参数

Main 方法可以使用参数,在这种情况下它采用以下形式之一:

static int Main(string[] args)

static void Main(string[] args)

Main 方法的参数是表示命令行参数的 String 数组。通常通过测试 Length 属性来检查参数是否存在,例如:

if ( == 0)

{

ine("Please enter a numeric argument.");

return 1;

}还可以使用 Convert 类或 Parse 方法将字符串参数转换为数值类型。例如,下面的语句使用 Int64

类上的 Parse 方法将字符串转换为 long 型数字:

long num = (args[0]);

也可以使用别名为 Int64 的 C# 类型 long:

long num = (args[0]);

学习文档 仅供参考

还可以使用 Convert 类的方法 ToInt64 完成同样的工作:

long num = 64(s);

有关更多信息,请参见 Parse 和 Convert。

例如

在此例如中,程序在运行时采用一个参数,将该参数转换为整数,并计算该数的阶乘。如果没有提供参数,那么程序发出一条消息来解释程序的正确用法。

// arguments: 3

public class Functions

{

public static long Factorial(int n)

{

if (n < 0) { return -1; } //error result - undefined

if (n > 256) { return -2; } //error result - input is too big

if (n == 0) { return 1; }

// Calculate the factorial iteratively rather than recursively:

long tempResult = 1;

for (int i = 1; i <= n; i++)

{

tempResult *= i;

}

return tempResult;

}

}

class MainClass

{

static int Main(string[] args)

{

// Test if input arguments were supplied:

if ( == 0)

{

ine("Please enter a numeric argument.");

ine("Usage: Factorial ");

return 1;

}

try

{

// Convert the input arguments to numbers:

int num = (args[0]);

ine("The Factorial of {0} is {1}.", num, ial(num));

return 0;

}

catch (Exception)

{

ine("Please enter a numeric argument.");

学习文档 仅供参考

ine("Usage: Factorial ");

return 1;

}

}

}

输出

The Factorial of 3 is 6.

注释

下面是该程序的两个运行例如〔假定程序名为 〕。

运行例如 #1:

输入下面的命令行:

Factorial 10

您将获得下面的结果:

The Factorial of 10 is 3628800.

运行例如 #2:

输入下面的命令行:

Factorial

您将获得下面的结果:

Please enter a numeric argument.

Usage: Factorial

5. 装箱和取消装箱

装箱和取消装箱使值类型能够被视为对象。对值类型装箱将把该值类型打包到 Object 引用类型的一个实例中。这使得值类型可以存储于垃圾回收堆中。取消装箱将从对象中提取值类型。在此例如中,整型变量 i 被“装箱〞并赋值给对象 o。

int i = 123;

object o = (object)i; // boxing

然后,可以对对象 o 取消装箱并将其赋值给整型变量 i:

o = 123;

i = (int)o; // unboxing

性能

相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个新对象。次之,取消装箱所需的强制转换也需要进行大量的计算。有关更多信息,请参见性能。

装箱

装箱用于在垃圾回收堆中存储值类型。装箱是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。

请看以下值类型变量的声明:

int i = 123;

以下语句对变量 i 隐式应用装箱操作:

object o = i; // implicit boxing

此语句的结果是在堆栈上创立对象引用 o,而在堆上那么引用 int 类型的值。该值是赋给变量 i 的值类型值的一个副本。以下列图说明了两个变量 i 和 o 之间的差异。

学习文档 仅供参考

装箱转换

装箱转换

还可以像下面的例如一样显式执行装箱,不过显式装箱从来不是必需的:

int i = 123;

object o = (object)i; // explicit boxing

说明

此例如使用装箱将整数变量 i 转换为对象 o。这样,存储在变量 i 中的值就从 123 更改为 456。该例如说明原始值类型和装箱的对象使用不同的内存位置,因此能够存储不同的值。

例如

class TestBoxing

{

static void Main()

{

int i = 123;

object o = i; // implicit boxing

i = 456; // change the contents of i

ine("The value-type value = {0}", i);

ine("The object-type value = {0}", o);

}

}输出

The value-type value = 456

The object-type value = 123

取消装箱

取消装箱是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:

➢ 检查对象实例,以确保它是给定值类型的装箱值。

➢ 将该值从实例复制到值类型变量中。

以下语句同时说明了装箱和取消装箱操作:

int i = 123; // a value type

object o = i; // boxing

int j = (int)o; // unboxing

学习文档 仅供参考

以下列图演示了上述语句的结果。

取消装箱转换

取消装箱转换

要在运行时成功取消装箱值类型,被取消装箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创立的。尝试取消装箱 null 或对不兼容值类型的引用会导致 InvalidCastException。说明

下面的例如演示无效的取消装箱及引发的 InvalidCastException。使用 try 和 catch,在发生错误时显示错误信息。例如

class TestUnboxing

{

static void Main()

{

int i = 123;

object o = i; // implicit boxing

try

{

int j = (short)o; // attempt to unbox

ine("Unboxing OK.");

}

catch (dCastException e)

{

ine("{0} Error: Incorrect unboxing.", e);

}

}

}

输出

Specified cast is not valid. Error: Incorrect unboxing.

如果将以下语句:

int j = (short) o;

更改为:

int j = (int) o;

将执行转换,并将得到以下输出: Unboxing OK.

6. 如何:转换十六进制字符串

学习文档 仅供参考

这些例如演示如何转换十六进制字符串。第一个例如演示如何获取字符串中的每一个字符的十六进制值。第二个例如分析十六进制值的字符串并输出对应于每个十六进制值的字符。第三个例如演示用于将十六进制字符串值分析为整数的其他方法。

例如

此例如输出 string 中的每个字符的十六进制值。首先,它将 string 分析为字符数组,然后对每个字符调用 ToInt32(Char) 以获取相应的数字值。最后,在 string 中将数字的格式设置为十六进制表示形式。

string input = "Hello World!";

char[] values = Array();

foreach (char c in values)

{

// Get the integral value of the character.

int value = 32(c);

// Convert the decimal value to a hexadecimal value in string form.

string hex = ("{0:X}", value);

ine("Hexadecimal value of {0} is {1}", c, hex);

}

Hexadecimal value of H is 48

Hexadecimal value of e is 65

Hexadecimal value of l is 6C

Hexadecimal value of l is 6C

Hexadecimal value of o is 6F

Hexadecimal value of is 20

Hexadecimal value of W is 57

Hexadecimal value of o is 6F

Hexadecimal value of r is 72

Hexadecimal value of l is 6C

Hexadecimal value of d is 64

Hexadecimal value of ! is 21

此例如分析十六进制值的 string 并输出对应于每个十六进制值的字符。首先,它调用

Split(array[]()[]) 方法以获取每个十六进制值作为数组中的单个 string。然后调用

ToInt32(String, Int32) 以将十六进制转换为表示为 int 的十进制值。例如中演示了用于获取对应于该字符代码的字符的两种不同方法。第一种方法是使用 ConvertFromUtf32(Int32),它将对应于整型参数的字符作为 string 返回。第二种方法是将 int 显式转换为 char。

string hexValues = "48 65 6C 6C 6F 20 57 6F 72 6C 64 21";

string[] hexValuesSplit = (' ');

foreach (String hex in hexValuesSplit)

{

// Convert the number expressed in base-16 to an integer.

int value = 32(hex, 16);

// Get the character corresponding to the integral value.

string stringValue = tFromUtf32(value);

char charValue = (char)value;

学习文档 仅供参考

ine("hexadecimal value = {0}, int value = {1}, char value = {2} or {3}", hex, value,

stringValue, charValue);

}

hexadecimal value = 48, int value = 72, char value = H or H

hexadecimal value = 65, int value = 101, char value = e or e

hexadecimal value = 6C, int value = 108, char value = l or l

hexadecimal value = 6C, int value = 108, char value = l or l

hexadecimal value = 6F, int value = 111, char value = o or o

hexadecimal value = 20, int value = 32, char value = or

hexadecimal value = 57, int value = 87, char value = W or W

hexadecimal value = 6F, int value = 111, char value = o or o

hexadecimal value = 72, int value = 114, char value = r or r

hexadecimal value = 6C, int value = 108, char value = l or l

hexadecimal value = 64, int value = 100, char value = d or d

hexadecimal value = 21, int value = 33, char value = ! or !

此例如演示通过调用 Parse(String, NumberStyles) 方法将十六进制 string 转换为整数的另一方法。

string hexString = "8E2";

int num = (hexString, ber);

ine(num);

2274

7. 使用字符串

C# 字符串是使用 string 关键字声明的一个字符数组。字符串是使用引号声明的,如下面的例如所示:

string s = "Hello, World!";

可以提取子字符串并将字符串连接在一起,如下面的例如所示:

string s1 = "orange";

string s2 = "red";

s1 += s2;

ine(s1); // outputs "orangered"

s1 = ing(2, 5);

ine(s1); // outputs "anger"

字符串对象是不可变的:即它们创立之后就无法更改。对字符串进行操作的方法实际上返回的是新的字符串对象。在前面的例如中,将 s1 和 s2 的内容连接起来以构成一个字符串时,包含 "orange" 和

"red" 的两个字符串均保持不变。+= 运算符会创立一个包含组合内容的新字符串。结果是 s1 现在完全引用不同的字符串。只包含 "orange" 的字符串仍然存在,但连接 s1 后将不再被引用。

注意:

创立字符串引用时要谨慎从事。如果您创立了一个字符串引用,然后“修改〞了该字符串,那么该引用会继续指向原始对象,而不是修改字符串时创立的新对象。下面的代码阐释了这个危险:

string s1 = "Hello";

string s2 = s1;

s1 += " and goodbye.";

ine(s2); //outputs "Hello"

因为修改字符串的操作涉及创立新字符串对象,出于性能方面的考虑,大量的串联或所涉及其他字符串操作应通过 StringBuilder 类来执行,如以下例如所示:

Builder sb = new Builder();

学习文档 仅供参考

("one ");

("two ");

("three");

string str = ng();

ine(str);

// Outputs: one two three

将在“使用 Stringbuilder〞一节中对 StringBuilder 类进行讨论。

使用字符串

转义符

字符串中可以包含转义符,如“n〞〔新行〕和“t〞〔制表符〕。行:

string hello = "HellonWorld!";

等同于:

Hello

World!

如果希望包含反斜杠,那么它前面必须还有另一个反斜杠。下面的字符串:

string filePath = "My Documents";

实际上等同于:

My Documents

原义字符串:@ 符号

@ 符号会告知字符串构造函数忽略转义符和分行符。因此,以下两个字符串是完全相同的:

string p1 = "My DocumentsMy Files";

string p2 = @"My DocumentsMy Files";

在原义字符串中,用另一个双引号字符转义双引号字符,如以下例如所示:

string s = @"You say ""goodbye"" and I say ""hello""";

访问各个字符

可以使用 SubString() 和 Replace() 等方法访问字符串中所包含的单个字符。

string s3 = "Visual C# Express";

ine(ing(7, 2)); // outputs "C#"

ine(e("C#", "Basic")); // outputs "Visual Basic Express"

也可以将字符复制到字符数组,如以下例如所示:

string s4 = "Hello, World";

char[] arr = Array(0, );

foreach (char c in arr)

{

(c); // outputs "Hello, World"

}

可以用索引访问字符串中的各个字符,如以下例如所示:

string s5 = "Printing backwards";

for (int i = 0; i < ; i++)

{

(s5[ - i - 1]); // outputs "sdrawkcab gnitnirP"

}

更改大小写

假设要将字符串中的字母更改为大写或小写,可以使用 ToUpper() 或 ToLower(),如以下例如所示:

string s6 = "Battle of Hastings, 1066";

学习文档 仅供参考

ine(r()); // outputs "BATTLE OF HASTINGS 1066"

ine(r()); // outputs "battle of hastings 1066"

比较

比较两个字符串的最简单方法是使用 == 和 != 运算符,执行区分大小写的比较。

string color1 = "red";

string color2 = "green";

string color3 = "red";

if (color1 == color3)

{

ine("Equal");

}

if (color1 != color2)

{

ine("Not equal");

}

字符串对象也有一个 CompareTo() 方法,它根据一个字符串小于 (<)、等于 (==) 还是大于 (>) 另一个字符串而返回一个整数值。比较字符串时使用 Unicode 值,并且小写字母的值小于大写字母的值。有关比较字符串的规那么的更多信息,请参见 CompareTo()()()。

// Enter different values for string1 and string2 to

// experiement with behavior of CompareTo

string string1 = "ABC";

string string2 = "abc";

int result = eTo(string2);

if (result > 0)

{

ine("{0} is greater than {1}", string1, string2);

}

else if (result == 0)

{

ine("{0} is equal to {1}", string1, string2);

}

else if (result < 0)

{

ine("{0} is less than {1}", string1, string2);

}

// Outputs: ABC is less than abc

假设要在一个字符串中搜索另一个字符串,可以使用 IndexOf()。如果未找到搜索字符串,IndexOf() 返回 -1;否那么,返回它出现的第一个位置的索引〔从零开始〕。

string s9 = "Battle of Hastings, 1066";

ine(f("Hastings")); // outputs 10

ine(f("1967")); // outputs -1

将字符串拆分为子字符串

将字符串拆分为多个子字符串〔例如将一个句子拆分成各个单词〕是常见的编程任务。Split() 方法使用分隔符〔如空格字符〕char 数组,并返回一个子字符串数组。可以使用 foreach 访问此数组,如以下例如所示:

学习文档 仅供参考

char[] delimit = new char[] { ' ' };

string s10 = "The cat sat on the mat.";

foreach (string substr in (delimit))

{

ine(substr);

}

此代码将在单独的行上输出每个单词,如以下例如所示:

The

cat

sat

on

the

mat.

Null 字符串和空字符串

空字符串是不包含字符的 System..::.String 对象的实例。在各种编程方案中经常会使用空字符串表示空白文本字段。可以对空字符串调用方法,因为它们是有效的 System..::.String 对象。空字符串可按如下方式初始化:

string s = "";

相反,null 字符串并不引用 System..::.String 对象的实例,任何对 null 字符串调用方法的尝试都会生成 NullReferenceException。但是,可以在串联和比较操作中将 null 字符串与其他字符串一起使用。下面的例如阐释了引用 null 字符串导致引发异常的情形以及并不导致引发异常的情形:

string str = "hello";

string nullStr = null;

string emptyStr = "";

string tempStr = str + nullStr; // tempStr = "hello"

bool b = (emptyStr == nullStr);// b = false;

emptyStr + nullStr = ""; // creates a new empty string

int len = ; // throws NullReferenceException

使用 StringBuilder

StringBuilder 类创立了一个字符串缓冲区,用于在程序执行大量字符串操作时提供更好的性能。StringBuilder 字符串还使您能够重新分配个别字符〔内置字符串数据类型所不支持的字符〕。例如,此代码在不创立新字符串的情况下更改了一个字符串的内容:

Builder sb = new Builder("Rat: the ideal pet");

sb[0] = 'C';

ine(ng());

ne();

//Outputs Cat: the ideal pet

在本例如中,StringBuilder 对象用于从一组数值类型中创立字符串:

class TestStringBuilder

{

static void Main()

{

Builder sb = new Builder();

// Create a string composed of numbers 0 - 9

for (int i = 0; i < 10; i++)

学习文档 仅供参考

{

(ng());

}

ine(sb); // displays

// Copy one character of the string (not possible with a )

sb[0] = sb[9];

ine(sb); // displays 9123456789

}

8. 如何:使用正那么表达式搜索字符串

可以使用 rExpressions..::.Regex 类搜索字符串。这些搜索有的非常简单,有的复杂到需要完全使用正那么表达式。以下是使用 Regex 类搜索字符串的两个例如。有关更多信息,请参见 .NET Framework 正那么表达式。

例如

以下代码是一个控制台应用程序,用于对数组中的字符串执行简单的不区分大小写的搜索。给定要搜索的字符串和包含搜索模式的字符串后,静态方法 Regex..::.IsMatch 将执行搜索。在本例中,使用第三个参数指示忽略大小写。有关更多信息,请参见

rExpressions..::.RegexOptions。

class TestRegularExpressions

{

static void Main()

{

string[] sentences =

{

"cow over the moon",

"Betsy the Cow",

"cowering in the corner",

"no match here"

};

string sPattern = "cow";

foreach (string s in sentences)

{

("{0,24}", s);

if (h(s, sPattern,

Case))

{

ine(" (match for '{0}' found)", sPattern);

}

else

{

ine();

}

}

}

}

学习文档 仅供参考

cow over the moon (match for 'cow' found)

Betsy the Cow (match for 'cow' found)

cowering in the corner (match for 'cow' found)

no match here

以下代码是一个控制台应用程序,此程序使用正那么表达式验证数组中每个字符串的格式。验证要求每个字符串具有 号码的形式,即用短划线将数字分成三组,前两组各包含三个数字,第三组包含四个数字。这是使用正那么表达式 ^d{3}-d{3}-d{4}$ 完成的。有关更多信息,请参见正那么表达式语言元素。

class TestRegularExpressionValidation

{

static void Main()

{

string[] numbers =

{

"123-456-7890",

"444-234-22450",

"690-203-6578",

"146-893-232",

"146-839-2322",

"4007-295-1111",

"407-295-1111",

"407-2-5555",

};

string sPattern = "^d{3}-d{3}-d{4}$";

foreach (string s in numbers)

{

("{0,14}", s);

if (h(s, sPattern))

{

ine(" - valid");

}

else

{

ine(" - invalid");

}

}

}

}

123-456-7890 - valid

444-234-22450 - invalid

690-203-6578 - valid

146-893-232 - invalid

146-839-2322 - valid

4007-295-1111 - invalid

407-295-1111 - valid

学习文档 仅供参考

407-2-5555 – invalid

9. Lambda 表达式

“Lambda 表达式〞是一个匿名函数,它可以包含表达式和语句,并且可用于创立委托或表达式目录树类型。

所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to〞。该 Lambda 运算符的左边是输入参数〔如果有〕,右边包含表达式或语句块。Lambda 表达式 x => x * x 读作“x goes to

x times x〞。可以将此表达式分配给委托类型,如下所示:

delegate int del(int i);

del myDelegate = x => x * x;

int j = myDelegate(5); //j = 25

创立表达式目录树类型:

using sions;

// ...

Expression = x => x * x;

=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。

Lambda 用在基于方法的 LINQ 查询中,作为诸如 Where 和 Where(IQueryable, String,

array[]()[]) 等标准查询运算符方法的参数。

使用基于方法的语法在 Enumerable 类中调用 Where 方法时〔像在 LINQ to Objects 和 LINQ to

XML 中那样〕,参数是委托类型 Func<(Of <(T, TResult>)>)。使用 Lambda 表达式创立委托最为方便。举例来说,当您在 Queryable 类中调用相同的方法时〔像在 LINQ to SQL 中那样〕,那么参数类型是 Expression,其中 Func 是包含至多五个输入参数的任何 Func 委托。同样,Lambda

表达式只是一种用于构造表达式目录树的非常简练的方式。尽管事实上通过 Lambda 创立的对象的类型是不同的,但 Lambda 使得 Where 调用看起来类似。

在前面的例如中,请注意委托签名具有一个 int 类型的隐式类型输入参数,并返回 int。可以将

Lambda 表达式转换为该类型的委托,因为该表达式也具有一个输入参数 (x),以及一个编译器可隐式转换为 int 类型的返回值。〔以下几节中将对类型推理进行详细讨论。〕 使用输入参数 5 调用委托时,它将返回结果 25。

在 is 或 as 运算符的左侧不允许使用 Lambda。

适用于匿名方法的所有限制也适用于 Lambda 表达式。有关更多信息,请参见匿名方法。

Lambda 表达式

表达式在右边的 Lambda 表达式称为“Lambda 表达式〞。 Lambda 表达式在构造表达式目录树时广泛使用。Lambda 表达式返回表达式的结果,并采用以下根本形式:

(input parameters) => expression

只有在 Lambda 有一个输入参数时,括号才是可选的;否那么括号是必需的。两个或更多输入参数由括在括号中的逗号分隔:

(x, y) => x == y

有时,编译器难于或无法推断输入类型。如果出现这种情况,您可以按以下例如中所示方式显式指定类型:

(int x, string s) => > x

使用空括号指定零个输入参数:

() => SomeMethod()

在上一个例如中,请注意 Lambda 表达式的主体可以包含方法调用。但是,如果要创立将在另一个学习文档 仅供参考

域〔比方 SQL Server〕中使用的表达式目录树,那么不应在 Lambda 表达式中使用方法调用。方法在 .NET 公共语言运行库上下文的外部将没有意义。

Lambda 语句

Lambda 语句与 Lambda 表达式类似,只是语句括在大括号中:

(input parameters) => {statement;}

Lambda 语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。

delegate void TestDelegate(string s);

TestDelegate myDel = n => { string s = n + " " + "World"; ine(s); };

myDel("Hello");

像匿名方法一样,Lambda 语句无法用于创立表达式目录树。

带有标准查询运算符的 Lambda

许多标准查询运算符都具有输入参数,其类型是泛型委托的 Func<(Of <(T, TResult>)>) 系列的其中之一。Func<(Of <(T, TResult>)>) 委托使用类型参数来定义输入参数的数量和类型,以及委托的返回类型。Func 委托对于封装应用于一组源数据中每个元素的用户定义表达式非常有用。例如,假设有以下委托类型:

public delegate TResult Func(TArg0 arg0)

可以将委托实例化为 Func myFunc,其中 int 是输入参数,bool 是返回值。返回值始终在最后一个类型参数中指定。Func 定义包含两个输入参数〔int 和 string〕且返回类型为 bool 的委托。在调用下面的 Func 委托时,该委托将返回 true 或 false 以指示输入参数是否等于 5:

Func myFunc = x => x == 5;

bool result = myFunc(4); // returns false of course

当参数类型为 Expression 时,您也可以提供 Lambda 表达式,例如在

ble 内定义的标准查询运算符中。如果指定 Expression 参数,Lambda

将编译为表达式目录树。

此处显示了一个标准查询运算符,Count 方法:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

int oddNumbers = (n => n % 2 == 1);

编译器可以推断输入参数的类型,或者您也可以显式指定该类型。这个特别的 Lambda 表达式将计算整数 (n) 的数量,这些整数除以 2 时余数为 1。

以下方法将生成一个序列,其中包含数字数组中出现在“9〞之前的所有元素,因为“9〞是序列中不满足条件的第一个数字:

var firstNumbersLessThan6 = ile(n => n < 6);

此例如演示如何通过将输入参数括在括号中来指定多个输入参数。该方法将返回数字数组中的所有元素,直至遇到一个值小于其位置的数字为止。不要将 Lambda 运算符 (=>) 与大于等于运算符 (>=)

混淆。

var firstSmallNumbers = ile((n, index) => n >= index);

Lambda 中的类型推理

在编写 Lambda 时,通常不必为输入参数指定类型,因为编译器可以基于 Lambda 主体、根底委托类型以及 C# 3.0 语言标准中描述的其他因素推断类型。对于大多数标准查询运算符,第一个输入是源序列中的元素的类型。因此,如果要查询 IEnumerable,那么输入变量将被推断为

Customer 对象,这意味着您可以访问其方法和属性:

学习文档 仅供参考

(c => == "London");

Lambda 的一般规那么如下:

Lambda 包含的参数数量必须与委托类型包含的参数数量相同。

Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。

Lambda 的返回值〔如果有〕必须能够隐式转换为委托的返回类型。

请注意,Lambda 表达式本身没有类型,因为通用类型系统没有“Lambda 表达式〞这一内部概念。但是,有时会不正式地论及 Lambda 表达式的“类型〞。在这些情况下,类型是指委托类型或 Lambda

表达式所转换为的 Expression 类型。

Lambda 表达式中的变量范围

Lambda 可以引用“外部变量〞,这些变量位于在其中定义 Lambda 的封闭方法或类型的范围内。将会存储通过这种方法捕获的变量以供在 Lambda 表达式中使用,即使变量将以其他方式超出范围或被作为垃圾回收。必须明确地分配外部变量,然后才能在 Lambda 表达式中使用该变量。下面的例如演示这些规那么:

delegate bool D();

delegate bool D2(int i);

class Test

{

D del;

D2 del2;

public void TestMethod(int input)

{

int j = 0;

// Initialize the delegates with lambda expressions.

// Note access to 2 outer variables.

// del will be invoked within this method.

del = () => { j = 10; return j > input; };

// del2 will be invoked after TestMethod goes out of scope.

del2 = (x) => {return x == j; };

// Demonstrate value of j:

// Output: j = 0

// The delegate has not been invoked yet.

ine("j = {0}", j);

// Invoke the delegate.

bool boolResult = del();

// Output: j = 10 b = True

ine("j = {0}. b = {1}", j, boolResult);

}

static void Main()

{

Test test = new Test();

thod(5);

学习文档 仅供参考

// Prove that del2 still has a copy of

// local variable j from TestMethod.

bool result = 2(10);

// Output: True

ine(result);

y();

}

}

以下规那么适用于 Lambda 表达式中的变量范围:

捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。

在外部方法中看不到 Lambda 表达式内引入的变量。

Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数。

Lambda 表达式中的返回语句不会导致封闭方法返回。

Lambda 表达式不能包含其目标位于所包含匿名函数主体外部或内部的 goto 语句、break 语句或

continue 语句。

10. 匿名方法

在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法,而在 C#

3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式。不过,本主题中有关匿名方法的信息同样也适用于 Lambda 表达式。有一种情况下,匿名方法提供了 Lambda 表达式中所没有的功能。匿名方法使您能够省略参数列表,这意味着可以将匿名方法转换为带有各种签名的委托。这对于 Lambda 表达式来说是不可能的。有关 lambda 表达式的更多特定信息,请参见

Lambda 表达式。

要将代码块传递为委托参数,创立匿名方法那么是唯一的方法。这里是两个例如:

// Create a handler for a click event

+= delegate( o, rgs e)

{ ("Click!"); };

// Create a delegate instance

delegate void Del(int x);

// Instantiate the delegate using an anonymous method

Del d = delegate(int k) { /* ... */ };

通过使用匿名方法,由于您不必创立单独的方法,因此减少了实例化委托所需的编码系统开销。

例如,如果创立方法所需的系统开销是不必要的,那么指定代码块〔而不是委托〕可能非常有用。启动新线程即是一个很好的例如。无需为委托创立更多方法,线程类即可创立一个线程并且包含该线程执行的代码。

void StartThread()

{

t1 = new

(delegate()

{

("Hello, ");

ine("World!");

});

();

学习文档 仅供参考

}

备注

匿名方法的参数的范围是“匿名方法块〞。

如果目标在块外部,那么,在匿名方法块内使用跳转语句〔如 goto、break 或 continue〕是错误的。如果目标在块内部,在匿名方法块外部使用跳转语句〔如 goto、break 或 continue〕也是错误的。

如果局部变量和参数的范围包含匿名方法声明,那么该局部变量和参数称为该匿名方法的“外部〞变量。例如,下面代码段中的 n 即是一个外部变量:

int n = 0;

Del d = delegate() { ine("Copy #:{0}", ++n); };

与局部变量不同,捕获变量的生命周期一直持续到引用该匿名方法的委托符合垃圾回收的条件为止。对 n 的引用是在创立该委托时捕获的。

匿名方法不能访问外部范围的 ref 或 out 参数。

在“匿名方法块〞中不能访问任何不平安代码。

在 is 运算符的左侧不允许使用匿名方法。

例如

下面的例如演示实例化委托的两种方法:

使委托与匿名方法关联。

使委托与命名方法 (DoWork) 关联。

两种方法都会在调用委托时显示一条消息。

// Declare a delegate

delegate void Printer(string s);

class TestClass

{

static void Main()

{

// Instatiate the delegate type using an anonymous method:

Printer p = delegate(string j)

{

ine(j);

};

// Results from the anonymous delegate call:

p("The delegate using the anonymous method is called.");

// The delegate instantiation using a named method "DoWork":

p = new Printer();

// Results from the old style delegate call:

p("The delegate using the named method is called.");

}

// The method associated with the named delegate:

static void DoWork(string k)

{

ine(k);

}

}

输出

学习文档 仅供参考

The delegate using the anonymous method is called.

The delegate using the named method is called.

11. 转换运算符

C# 允许程序员在类或结构上声明转换,以便类或结构与其他类或结构或者根本类型进行相互转换。转换的定义方法类似于运算符,并根据它们所转换到的类型命名。要转换的类型参数或转换结果的类型必须是〔不能两者同时都是〕包含类型。

class SampleClass

{

public static explicit operator SampleClass(int i)

{

SampleClass temp = new SampleClass();

// code to convert from int

return temp;

}

}

转换运算符概述

转换运算符具有以下特点:

声明为 implicit 的转换在需要时自动进行。

声明为 explicit 的转换需要调用强制转换。

所有转换都必须是 static 转换。

12. 使用转换运算符

转换运算符可以是 explicit,也可以是 implicit。隐式转换运算符更容易使用,但是如果您希望运算符的用户能够意识到正在进行转换,那么显式运算符很有用。此主题演示了这两种类型。

例如 1

说明

这是显式转换运算符的一个例如。此运算符将类型 Byte 转换为称为 Digit 的值类型。由于不是所有字节都可以转换为数字,因此转换是显式的,这意味着必须使用强制转换,如 Main 方法所示。

代码

struct Digit

{

byte value;

public Digit(byte value) //constructor

{

if (value > 9)

{

throw new ntException();

}

= value;

}

public static explicit operator Digit(byte b) // explicit byte to digit conversion operator

学习文档 仅供参考

{

Digit d = new Digit(b); // explicit conversion

ine("conversion occurred");

return d;

}

}

class TestExplicitConversion

{

static void Main()

{

try

{

byte b = 3;

Digit d = (Digit)b; // explicit conversion

}

catch (ion e)

{

ine("{0} Exception caught.", e);

}

}

}

输出 1

conversion occurred

例如 2

说明

此例如通过定义用来撤消前一个例如所执行的操作的转换运算符,来演示隐式转换运算符:它将名为

Digit 的值类转换为整数 Byte 类型。由于任何数字都可以转换为 Byte,因此没有必要一定让用户知道进行的转换。

代码

struct Digit

{

byte value;

public Digit(byte value) //constructor

{

if (value > 9)

{

throw new ntException();

}

= value;

}

public static implicit operator byte(Digit d) // implicit digit to byte conversion operator

{

ine("conversion occurred");

学习文档 仅供参考

return ; // implicit conversion

}

}

class TestImplicitConversion

{

static void Main()

{

Digit d = new Digit(3);

byte b = d; // implicit conversion -- no cast needed

}

}

输出 2

conversion occurred

13. 如何:在结构之间实现用户定义的转换

本例如定义 RomanNumeral 和 BinaryNumeral 两个结构,并演示二者之间的转换。

例如

struct RomanNumeral

{

private int value;

public RomanNumeral(int value) //constructor

{

= value;

}

static public implicit operator RomanNumeral(int value)

{

return new RomanNumeral(value);

}

static public implicit operator RomanNumeral(BinaryNumeral binary)

{

return new RomanNumeral((int)binary);

}

static public explicit operator int(RomanNumeral roman)

{

return ;

}

static public implicit operator string(RomanNumeral roman)

{

return ("Conversion not yet implemented");

}

}

struct BinaryNumeral

{

private int value;

public BinaryNumeral(int value) //constructor

学习文档 仅供参考

{

= value;

}

static public implicit operator BinaryNumeral(int value)

{

return new BinaryNumeral(value);

}

static public explicit operator int(BinaryNumeral binary)

{

return ();

}

static public implicit operator string(BinaryNumeral binary)

{

return ("Conversion not yet implemented");

}

}

class TestConversions

{

static void Main()

{

RomanNumeral roman;

BinaryNumeral binary;

roman = 10;

// Perform a conversion from a RomanNumeral to a BinaryNumeral:

binary = (BinaryNumeral)(int)roman;

// Perform a conversion from a BinaryNumeral to a RomanNumeral:

// No cast is required:

roman = binary;

ine((int)binary);

ine(binary);

}

}

10

Conversion not yet implemented

可靠编程

在上面的例如中,语句:

binary = (BinaryNumeral)(int)roman;

执行从 RomanNumeral 到 BinaryNumeral 的转换。由于没有从 RomanNumeral 到

BinaryNumeral 的直接转换,所以使用一个转换将 RomanNumeral 转换为 int,并使用另一个转换将 int 转换为 BinaryNumeral。

另外,语句

roman = binary;

执行从 BinaryNumeral 到 RomanNumeral 的转换。由于 RomanNumeral 定义了从

BinaryNumeral 的隐式转换,所以不需要转换。

学习文档 仅供参考

14. 如何:使用运算符重载创立复数类

本例如展示如何使用运算符重载创立定义复数加法的复数类 Complex。本程序使用 ToString 方法的重载显示数字的虚部和实部以及加法结果。

例如

public struct Complex

{

public int real;

public int imaginary;

public Complex(int real, int imaginary) //constructor

{

= real;

ary = imaginary;

}

// Declare which operator to overload (+),

// the types that can be added (two Complex objects),

// and the return type (Complex):

public static Complex operator +(Complex c1, Complex c2)

{

return new Complex( + , ary + ary);

}

// Override the ToString() method to display a complex number in the traditional format:

public override string ToString()

{

return (("{0} + {1}i", real, imaginary));

}

}

class TestComplex

{

static void Main()

{

Complex num1 = new Complex(2, 3);

Complex num2 = new Complex(3, 4);

// Add two Complex objects through the overloaded plus operator:

Complex sum = num1 + num2;

// Print the numbers and the sum using the overriden ToString method:

ine("First complex number: {0}", num1);

ine("Second complex number: {0}", num2);

ine("The sum of the two numbers: {0}", sum);

}

}

First complex number: 2 + 3i

Second complex number: 3 + 4i

The sum of the two numbers: 5 + 7i

学习文档 仅供参考

15. 继承

类可以从其他类中继承。这是通过以下方式实现的:在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类〔即基类〕。例如:

public class A

{

public A() { }

}

public class B : A

{

public B() { }

}

新类〔即派生类〕将获取基类的所有非私有数据和行为以及新类为自己定义的所有其他数据或行为。因此,新类具有两个有效类型:新类的类型和它继承的类的类型。

在上面的例如中,类 B 既是有效的 B,又是有效的 A。访问 B 对象时,可以使用强制转换操作将其转换为 A 对象。强制转换不会更改 B 对象,但您的 B 对象视图将限制为 A 的数据和行为。将 B

强制转换为 A 后,可以将该 A 重新强制转换为 B。并非 A 的所有实例都可强制转换为 B,只有实际上是 B 的实例的那些实例才可以强制转换为 B。如果将类 B 作为 B 类型访问,那么可以同时获得类 A 和类 B 的数据和行为。对象可以表示多个类型的能力称为多态性。有关更多信息,请参见多态性。有关强制转换的更多信息,请参见强制转换。

结构不能从其他结构或类中继承。类和结构都可以从一个或多个接口中继承。有关更多信息,请参见接口

16. 抽象类、密封类及类成员

使用 abstract 关键字可以创立仅用于继承用途的类和类成员,即定义派生的非抽象类的功能。使用

sealed 关键字可以防止继承以前标记为 virtual 的类或某些类成员。有关更多信息,请参见如何:定义抽象属性。

抽象类和类成员

可以将类声明为抽象类。方法是在类定义中将关键字 abstract 置于关键字 class 的前面。例如:

public abstract class A

{

// Class members here.

}

抽象类不能实例化。抽象类的用途是提供多个派生类可共享的基类的公共定义。例如,类库可以定义一个作为其多个函数的参数的抽象类,并要求程序员使用该库通过创立派生类来提供自己的类实现。

抽象类也可以定义抽象方法。方法是将关键字 abstract 添加到方法的返回类型的前面。例如:

public abstract class A

{

public abstract void DoWork(int i);

}

抽象方法没有实现,所以方法定义后面是分号,而不是常规的方法块。抽象类的派生类必须实现所有抽象方法。当抽象类从基类继承虚方法时,抽象类可以使用抽象方法重写该虚方法。例如:

// compile with: /target:library

学习文档 仅供参考

public class D

{

public virtual void DoWork(int i)

{

// Original implementation.

}

}

public abstract class E : D

{

public abstract override void DoWork(int i);

}

public class F : E

{

public override void DoWork(int i)

{

// New implementation.

}

}

如果将虚方法声明为抽象方法,那么它对于从抽象类继承的所有类而言仍然是虚的。继承抽象方法的类无法访问该方法的原始实现。在前面的例如中,类 F 上的 DoWork 无法调用类 D 上的 DoWork。在此情况下,抽象类可以强制派生类为虚方法提供新的方法实现。

密封类和类成员

可以将类声明为密封类。方法是在类定义中将关键字 sealed 置于关键字 class 的前面。例如:

public sealed class D

{

// Class members here.

}

密封类不能用作基类。因此,它也不能是抽象类。密封类主要用于防止派生。由于密封类从不用作基类,所以有些运行时优化可以使对密封类成员的调用略快。

在对基类的虚成员进行重写的派生类上的类成员、方法、字段、属性或事件可以将该成员声明为密封成员。在用于以后的派生类时,这将取消成员的虚效果。方法是在类成员声明中将 sealed 关键字置于 override 关键字的前面。例如:

public class D : C

{

public sealed override void DoWork() { }

}

17. 使用 Override 和 New 关键字进行版本控制

C# 语言经过专门设计,以便不同库中的基类与派生类之间的版本控制可以不断向前开展,同时保持向后兼容。这具有多方面的意义。例如,这意味着在基类中引入与派生类中的某个成员具有相同名称学习文档 仅供参考

的新成员在 C# 中是完全支持的,不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法,还是一个隐藏具有类似名称的继承方法的新方法。

在 C# 中,派生类可以包含与基类方法同名的方法。

➢ 基类方法必须定义为 virtual。

➢ 如果派生类中的方法前面没有 new 或 override 关键字,那么编译器将发出警告,该方法将有如存在 new 关键字一样执行操作。

➢ 如果派生类中的方法前面带有 new 关键字,那么该方法被定义为独立于基类中的方法。

➢ 如果派生类中的方法前面带有 override 关键字,那么派生类的对象将调用该方法,而不是调用基类方法。

➢ 可以从派生类中使用 base 关键字调用基类方法。

➢ override、virtual 和 new 关键字还可以用于属性、索引器和事件中。

默认情况下,C# 方法为非虚方法。如果某个方法被声明为虚方法,那么继承该方法的任何类都可以实现它自己的版本。假设要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果

override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。有关更多信息,请参见编译器警告〔等级 2〕CS0108。

为了在实践中演示上述情况,我们暂时假定公司 A 创立了一个名为 GraphicsClass 的类,您的程序将使用此类。GraphicsClass 如下所示:

class GraphicsClass

{

public virtual void DrawLine() { }

public virtual void DrawPoint() { }

}

您的公司使用此类,并且您在添加新方法时将其用来派生自己的类:

class YourDerivedGraphicsClass : GraphicsClass

{

public void DrawRectangle() { }

}

您的应用程序运行正常,直到公司 A 发布了 GraphicsClass 的新版本,类似于下面的代码:

class GraphicsClass

{

public virtual void DrawLine() { }

public virtual void DrawPoint() { }

public virtual void DrawRectangle() { }

}

现在,GraphicsClass 的新版本中包含一个名为 DrawRectangle 的方法。开始时,没有出现任何问题。新版本仍然与旧版本保持二进制兼容。已经部署的任何软件都将继续正常工作,即使新类已安装到这些软件所在的电脑系统上。在您的派生类中,对方法 DrawRectangle 的任何现有调用将继续引用您的版本。

但是,一旦您使用 GraphicsClass 的新版本重新编译应用程序,就会收到来自编译器的警告。有关更多信息,请参见编译器警告〔等级 2〕CS0108。

此警告提示您必须考虑希望 DrawRectangle 方法在应用程序中的工作方式。

学习文档 仅供参考

如果您希望自己的方法重写新的基类方法,请使用 override 关键字:

class YourDerivedGraphicsClass : GraphicsClass

{

public override void DrawRectangle() { }

}

override 关键字可确保派生自 YourDerivedGraphicsClass 的任何对象都将使用 DrawRectangle

的派生类版本。派生自 YourDerivedGraphicsClass 的对象仍可以使用基关键字访问

DrawRectangle 的基类版本:

ctangle();

如果您不希望自己的方法重写新的基类方法,那么需要注意以下事项。为了防止这两个方法之间发生混淆,可以重命名您的方法。这可能很消耗时间且容易出错,而且在某些情况下并不可行。但是,如果您的工程相对较小,那么可以使用 Visual Studio 的重构选项来重命名方法。有关更多信息,请参见重构类和类型。

或者,也可以通过在派生类定义中使用关键字 new 来防止出现该警告:

class YourDerivedGraphicsClass : GraphicsClass

{

public new void DrawRectangle() { }

}

使用 new 关键字可告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。

重写和方法选择

当在类中指定方法时,如果有多个方法与调用兼容〔例如,存在两种同名的方法,并且其参数与传递的参数兼容〕,那么 C# 编译器将选择最正确方法进行调用。下面的方法将是兼容的:

public class Derived : Base

{

public override void DoWork(int param) { }

public void DoWork(double param) { }

}

在 Derived 的一个实例中调用 DoWork 时,C# 编译器将首先尝试使该调用与最初在 Derived 上声明的 DoWork 版本兼容。重写方法不被视为是在类上进行声明的,而是在基类上声明的方法的新实现。仅当 C# 编译器无法将方法调用与 Derived 上的原始方法匹配时,它才尝试将该调用与具有相同名称和兼容参数的重写方法匹配。例如:

int val = 5;

Derived d = new Derived();

(val); // Calls DoWork(double).

由于变量 val 可以隐式转换为 double 类型,因此 C# 编译器将调用 DoWork(double),而不是

DoWork(int)。有两种方法可以防止此情况。首先,防止将新方法声明为与虚方法同名。其次,可以通过将 Derived 的实例强制转换为 Base 来使 C# 编译器搜索基类方法列表,从而使其调用虚方法。由于是虚方法,因此将调用 Derived 上的 DoWork(int) 的实现。例如:

((Base)d).DoWork(val); // Calls DoWork(int) on Derived.

18. 了解何时使用 Override 和 New 关键字

C# 允许派生类中的方法与基类中的方法具有相同的名称,只要您非常明确应如何处理新方法。下面的例如演示 new 和 override 关键字的使用。

首先声明三个类:一个名为 Car 的基类以及从该基类派生的两个类 ConvertibleCar 和 Minivan。基学习文档 仅供参考

类包含一个可将有关汽车的描述发送到控制台的方法 (DescribeCar)。派生类方法也包含一个名为

DescribeCar 的方法,该方法显示派生类的独特属性。这些方法还调用基类的 DescribeCar 方法来演示从 Car 类继承属性的方式。

为了强调区别,ConvertibleCar 类使用 new 关键字来定义,而 Minivan 类使用 override 来定义。

// Define the base class

class Car

{

public virtual void DescribeCar()

{

ine("Four wheels and an engine.");

}

}

// Define the derived classes

class ConvertibleCar : Car

{

public new virtual void DescribeCar()

{

beCar();

ine("A roof that opens up.");

}

}

class Minivan : Car

{

public override void DescribeCar()

{

beCar();

ine("Carries seven people.");

}

}

现在可以编写一些代码来声明这些类的实例,并调用它们的方法以便对象能够描述其自身:

public static void TestCars1()

{

Car car1 = new Car();

beCar();

ine("----------");

ConvertibleCar car2 = new ConvertibleCar();

beCar();

ine("----------");

Minivan car3 = new Minivan();

beCar();

ine("----------");

}

正如预期的那样,输出类似如下所示:

Four wheels and an engine.

----------

学习文档 仅供参考

Four wheels and an engine.

A roof that opens up.

----------

Four wheels and an engine.

Carries seven people.

----------

但是,在该代码接下来的一节中,我们声明了一个从 Car 基类派生的对象的数组。此数组能够存储

Car、ConvertibleCar 和 Minivan 对象。该数组的声明类似如下所示:

public static void TestCars2()

{

Car[] cars = new Car[3];

cars[0] = new Car();

cars[1] = new ConvertibleCar();

cars[2] = new Minivan();

}

然后可以使用一个 foreach 循环来访问该数组中包含的每个 Car 对象,并调用 DescribeCar 方法,如下所示:

foreach (Car vehicle in cars)

{

ine("Car object: " + e());

beCar();

ine("----------");

}

此循环的输出如下所示:

Car object:

Four wheels and an engine.

----------

Car object: tibleCar

Four wheels and an engine.

----------

Car object: n

Four wheels and an engine.

Carries seven people.

----------

注意,ConvertibleCar 说明与您的预期不同。由于使用了 new 关键字来定义此方法,所调用的不是派生类方法,而是基类方法。Minivan 对象正确地调用重写方法,并产生预期的结果。

假设要强制一个规那么,要求从 Car 派生的所有类都必须实现 DescribeCar 方法,那么应创立一个新的基类,将方法 DescribeCar 定义为 abstract。抽象方法不包含任何代码,仅包含方法签名。从此基类派生的任何类都必须提供 DescribeCar 的实现。有关更多信息,请参见抽象。

19. 如何:重写 ToString 方法

C# 中的每个对象都继承 ToString 方法,此方法返回该对象的字符串表示形式。例如,所有 int 类型的变量都有一个 ToString 方法,此方法可让这些变量将其内容作为字符串返回:

int x = 42;

学习文档 仅供参考

string strx = ng();

ine(strx);

创立自定义类或结构时,应该重写 ToString 方法,以便向客户端代码提供类型信息。

平安说明:

当您决定通过此方法提供的信息的类型时,应考虑您的类或结构是否会被不受信任的代码使用。请务必确保您没有提供任何会被恶意代码利用的信息。

在类或结构中重写 OnString 方法

通过下面的修饰符和返回类型声明 ToString 方法:

public override string ToString(){}

实现该方法,使其返回一个字符串。

下面的例如不仅返回类的名称,还返回特定于该类的某个实例的数据。请注意,它还会在 age 变量上使用 ToString 方法,将 int 转换为可输出的字符串。

class Person

{

string name;

int age;

SampleObject(string name, int age)

{

= name;

= age;

}

public override string ToString()

{

string s = ng();

return "Person: " + name + " " + s;

}

}

20. 接口

接口是使用 interface 关键字定义的。例如:

interface IComparable

{

int CompareTo(object obj);

}

接口描述的是可属于任何类或结构的一组相关功能。接口可由方法、属性、事件、索引器或这四种成员类型的任意组合构成。接口不能包含字段。接口成员一定是公共的。

类和结构可以按照类继承基类或结构的类似方式继承接口,但有两个例外:

类或结构可继承多个接口。

类或结构继承接口时,仅继承方法名称和签名,因为接口本身不包含实现。例如:

public class Minivan : Car, IComparable

{

public int CompareTo(object obj)

{

//implementation of CompareTo

return 0; //if the Minivans are equal

学习文档 仅供参考

}

}

假设要实现接口成员,类中的对应成员必须是公共的、非静态的,并且与接口成员具有相同的名称和签名。类的属性和索引器可以为接口上定义的属性或索引器定义额外的访问器。例如,接口可以声明一个带有 get 访问器的属性,而实现该接口的类可以声明同时带有 get 和 set 访问器的同一属性。但是,如果属性或索引器使用显式实现,那么访问器必须匹配。

接口和接口成员是抽象的;接口不提供默认实现。有关更多信息,请参见抽象类、密封类和类成员。

IComparable 接口向对象的用户宣布该对象可以将自身与同一类型的其他对象进行比较,而接口的用户不需要知道相关的实现方式。

接口可以继承其他接口。类可以通过其继承的基类或接口屡次继承某个接口。在这种情况下,如果将该接口声明为新类的一局部,那么该类只能实现该接口一次。如果没有将继承的接口声明为新类的一局部,其实现将由声明它的基类提供。基类可以使用虚拟成员实现接口成员;在这种情况下,继承接口的类可通过重写虚拟成员来更改接口行为。有关虚拟成员的更多信息,请参见多态性。

接口概述

➢ 接口具有以下属性:

➢ 接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。

➢ 不能直接实例化接口。

➢ 接口可以包含事件、索引器、方法和属性。

➢ 接口不包含方法的实现。

➢ 类和结构可从多个接口继承。

➢ 接口自身可从多个接口继承。

21. 显式接口实现

如果类实现两个接口,并且这两个接口包含具有相同签名的成员,那么在类中实现该成员将导致两个接口都使用该成员作为它们的实现。例如:

interface IControl

{

void Paint();

}

interface ISurface

{

void Paint();

}

class SampleClass : IControl, ISurface

{

// Both and call this method.

public void Paint()

{

}

}

然而,如果两个接口成员执行不同的函数,那么这可能会导致其中一个接口的实现不正确或两个接口的实现都不正确。可以显式地实现接口成员 -- 即创立一个仅通过该接口调用并且特定于该接口的类成员。这是使用接口名称和一个句点命名该类成员来实现的。例如:

public class SampleClass : IControl, ISurface

{

学习文档 仅供参考

void ()

{

ine("");

}

void ()

{

ine("");

}

}

类成员 只能通过 IControl 接口使用, 只能通过 ISurface 使用。两个方法实现都是别离的,都不可以直接在类中使用。例如:

SampleClass obj = new SampleClass();

//(); // Compiler error.

IControl c = (IControl)obj;

(); // Calls on SampleClass.

ISurface s = (ISurface)obj;

(); // Calls on SampleClass.

显式实现还用于解决两个接口分别声明具有相同名称的不同成员〔如属性和方法〕的情况:

interface ILeft

{

int P { get;}

}

interface IRight

{

int P();

}

为了同时实现两个接口,类必须对属性 P 和/或方法 P 使用显式实现以防止编译器错误。例如:

class Middle : ILeft, IRight

{

public int P() { return 0; }

int ILeft.P { get { return 0; } }

}

22. 如何:显式实现接口成员

本例如声明一个 接口 IDimensions 和一个类 Box,该类显式实现接口成员 getLength 和

getWidth。通过接口实例 dimensions 访问这些成员。

例如

interface IDimensions

{

float getLength();

float getWidth();

}

class Box : IDimensions

{

学习文档 仅供参考

float lengthInches;

float widthInches;

Box(float length, float width)

{

lengthInches = length;

widthInches = width;

}

// Explicit interface member implementation:

float gth()

{

return lengthInches;

}

// Explicit interface member implementation:

float th()

{

return widthInches;

}

static void Main()

{

// Declare a class instance box1:

Box box1 = new Box(30.0f, 20.0f);

// Declare an interface instance dimensions:

IDimensions dimensions = (IDimensions)box1;

// The following commented lines would produce compilation

// errors because they try to access an explicitly implemented

// interface member from a class instance:

//ine("Length: {0}", gth());

//ine("Width: {0}", th());

// Print out the dimensions of the box by calling the methods

// from an instance of the interface:

ine("Length: {0}", gth());

ine("Width: {0}", th());

}

}

Length: 30

Width: 20

可靠编程

请注意 Main 方法中以下代码行被注释掉,因为它们将产生编译错误。显式实现的接口成员不能从类实例访问:

//ine("Length: {0}", gth());

//ine("Width: {0}", th());

学习文档 仅供参考

还请注意,Main 方法中的以下代码行成功输出框的尺寸,因为这些方法是从接口实例调用的:

ine("Length: {0}", gth());

ine("Width: {0}", th());

23. 如何:使用继承显式实现接口成员

显式接口实现还允许程序员实现具有相同成员名称的两个接口,并为每个接口成员各提供一个实现。本例如同时以公制单位和英制单位显示框的尺寸。Box 类实现 IEnglishDimensions 和

IMetricDimensions 两个接口,它们表示不同的度量系统。两个接口有相同的成员名 Length 和

Width。

例如

// Declare the English units interface:

interface IEnglishDimensions

{

float Length();

float Width();

}

// Declare the metric units interface:

interface IMetricDimensions

{

float Length();

float Width();

}

// Declare the Box class that implements the two interfaces:

// IEnglishDimensions and IMetricDimensions:

class Box : IEnglishDimensions, IMetricDimensions

{

float lengthInches;

float widthInches;

public Box(float length, float width)

{

lengthInches = length;

widthInches = width;

}

// Explicitly implement the members of IEnglishDimensions:

float ()

{

return lengthInches;

}

float ()

{

return widthInches;

}

// Explicitly implement the members of IMetricDimensions:

float ()

{

学习文档 仅供参考

return lengthInches * 2.54f;

}

float ()

{

return widthInches * 2.54f;

}

static void Main()

{

// Declare a class instance box1:

Box box1 = new Box(30.0f, 20.0f);

// Declare an instance of the English units interface:

IEnglishDimensions eDimensions = (IEnglishDimensions)box1;

// Declare an instance of the metric units interface:

IMetricDimensions mDimensions = (IMetricDimensions)box1;

// Print dimensions in English units:

ine("Length(in): {0}", ());

ine("Width (in): {0}", ());

// Print dimensions in metric units:

ine("Length(cm): {0}", ());

ine("Width (cm): {0}", ());

}

}

Length(in): 30

Width (in): 20

Length(cm): 76.2

Width (cm): 50.8

可靠编程

如果希望默认度量采用英制单位,请正常实现 Length 和 Width 这两个方法,IMetricDimensions 接口显式实现 Length 和 Width 方法:

// Normal implementation:

public float Length()

{

return lengthInches;

}

public float Width()

{

return widthInches;

}

// Explicit implementation:

float ()

{

return lengthInches * 2.54f;

}

float ()

{

学习文档 仅供参考

并从

return widthInches * 2.54f;

}

这种情况下,可以从类实例访问英制单位,而从接口实例访问公制单位:

public static void Test()

{

Box box1 = new Box(30.0f, 20.0f);

IMetricDimensions mDimensions = (IMetricDimensions)box1;

ine("Length(in): {0}", ());

ine("Width (in): {0}", ());

ine("Length(cm): {0}", ());

ine("Width (cm): {0}", ());

}

24. 成员

类和结构具有表示其数据和行为的成员。下表列出了这些成员:

成员 说明

字段 字段是被视为类的一局部的对象的实例,通常用于保存类数据。例如,日历类可能具有一个包含当前日期的字段。

属性 属性是类中可以像类中的字段一样访问的方法。属性可以为类字段提供保护,以防止字段在对象不知道的情况下被更改。

方法 方法定义类可以执行的操作。方法可以接受提供输入数据的参数,并且可以通过参数返回输出数据。方法还可以不使用参数而直接返回值。

事件 事件向其他对象提供有关发生的事情〔如单击按钮或成功完成某个方法〕的通知。事件是使用委托定义和触发的。有关更多信息,请参见事件和委托。

运算符 运算符是对操作数执行运算的术语或符号,如 +、*、< 等。可以重新定义运算符,以便可以对自定义数据类型执行运算。有关更多信息,请参见可重载运算符。

索引器 使用索引器可以用类似于数组的方式为对象建立索引。

构造函数 构造函数是在第一次创立对象时调用的方法。它们通常用于初始化对象的数据。

析构函数 析构函数是当对象即将从内存中移除时由运行库执行引擎调用的方法。它们通常用来确保任何必须释放的资源都得到适当的处理。

嵌套类型 嵌套类型是在类或结构中声明的类型,通常用于描述仅由包含它们的类型所使用的对象。

25. 方法

“方法〞是包含一系列语句的代码块。在 C# 中,每个执行指令都是在方法的上下文中执行的。本主题讨论命名方法。文档中的其他地方讨论了名为“匿名函数〞的另一种方法。

学习文档 仅供参考

方法是通过指定访问级别、返回值、方法名称和任何方法参数在类或结构中声明的。这些局部统称为方法的“签名〞。 方法参数括在括号中,并用逗号隔开。空括号表示方法不需要参数。下面的类包含三个方法:

class Motorcycle

{

public void StartEngine() { }

public void AddGas(int gallons) { }

public int Drive(int miles, int speed) { return 0; }

}

在对象上调用方法类似于访问字段。在对象名称之后,依次添加句点、方法名称和括号。参数在括号内列出,并用逗号隔开。因此,可以按以下例如中的方式调用 Motorcycle 类的方法:

Motorcycle moto = new Motorcycle();

ngine();

(15);

(5, 20);

方法参数

如前面的例如中所示,如果要将参数传递给方法,只需在调用方法时在括号内提供这些参数即可。对于被调用的方法,传入的变量称为“参数〞。

方法所接收的参数也是在一组括号中提供的,但必须指定每个参数的类型和名称。该名称不必与参数相同。例如:

public static void PassesInteger()

{

int fortyFour = 44;

TakesInteger(fortyFour);

}

static void TakesInteger(int i)

{

i = 33;

}

在这里,一个名为 PassesInteger 的方法向一个名为 TakesInteger 的方法传递参数。在

PassesInteger 内,该参数被命名为 fortyFour,但在 TakeInteger 中,它是名为 i 的参数。此参数只存在于 TakesInteger 方法中。其他任意多个变量都可以命名为 i,并且它们可以是任何类型,只要它们不是在此方法内部声明的参数或变量即可。

注意,TakesInteger 将新值赋给所提供的参数。有人可能认为,在 TakeInteger 返回后,此更改会反映在 PassesInteger 方法中,但实际上变量 fortyFour 中的值将保持不变。这是因为 int 是“值类型〞。默认情况下,将值类型传递给方法时,传递的是副本而不是对象本身。由于它们是副本,因此对参数所做的更改都不会在调用方法内反映出来。之所以叫做值类型,是因为传递的是对象的副本而不是对象本身。传递的是值,而不是同一个对象。

有关传递值类型的更多信息,请参见传递值类型参数。有关 C# 中的值类型的列表,请参见 值类型表〔C# 参考〕。

这与“引用类型〞不同,后者是按引用传递的。将基于引用类型的对象传递到方法时,不会创立对象的副本。而是创立并传递对用作方法参数的对象的引用。因此,通过此引用所进行的更改将反映在调用方法中。引用类型是通过使用 class 关键字创立的,如下面的例如中所示:

public class SampleRefType

{

学习文档 仅供参考

public int value;

}

现在,如果将基于此类型的对象传递给方法,那么会通过引用传递该对象。例如:

public static void TestRefType()

{

SampleRefType rt = new SampleRefType();

= 44;

ModifyObject(rt);

ine();

}

static void ModifyObject(SampleRefType obj)

{

= 33;

}

此例如的效果本质上与前一例如相同。但是,由于使用的是引用类型,因此 ModifyObject 所做的更改反映在 TestRefType 方法中创立的对象中。因此,TestRefType 方法将显示值 33。

有关更多信息,请参见传递引用类型参数和引用类型〔C# 参考〕。

返回值

方法可以向调用方返回值。如果返回类型〔方法名称前列出的类型〕不是 void,那么方法可以使用

return 关键字来返回值。如果语句中 return 关键字的后面是与返回类型匹配的值,那么该语句将该值返回给方法调用方。return 关键字还会停止方法的执行。如果返回类型为 void,那么可使用没有值的 return 语句来停止方法的执行。如果没有 return 关键字,方法执行到代码块末尾时即会停止。具有非 void 返回类型的方法才能使用 return 关键字返回值。例如,下面的两个方法使用 return 关键字来返回整数:

class SimpleMath

{

public int AddTwoNumbers(int number1, int number2)

{

return number1 + number2;

}

public int SquareANumber(int number)

{

return number * number;

}

}

假设要使用从方法返回的值,调用方法可以在本来使用同一类型的值就已足够的任何位置使用方法调用本身。还可以将返回值赋给变量: 例如,下面的两个代码例如可实现相同的目的:

int result = Numbers(1, 2);

ANumber(result);

ANumber(Numbers(1, 2));

使用中间变量〔本例中为 result〕来存储值。这有助于增强代码的可读性,如果要屡次使用该值,那么可能必须使用中间变量。

注意:

为进行方法重载,方法的返回类型不是方法签名的一局部。但是,在确定委托和委托所指向方法之间的兼容性时,返回类型是方法签名的一局部。

学习文档 仅供参考

26. 传递值类型参数

值类型变量直接包含其数据,这与引用类型变量不同,后者包含对其数据的引用。因此,向方法传递值类型变量意味着向方法传递变量的一个副本。方法内发生的对参数的更改对该变量中存储的原始数据无任何影响。如果希望所调用的方法更改参数的值,必须使用 ref 或 out 关键字通过引用传递该参数。为了简单起见,下面的例如使用 ref。

例如:通过值传递值类型

下面的例如演示通过值传递值类型参数。通过值将变量 n 传递给方法 SquareIt。方法内发生的任何更改对变量的原始值无任何影响。

class PassingValByVal

{

static void SquareIt(int x)

// The parameter x is passed by value.

// Changes to x will not affect the original value of x.

{

x *= x;

ine("The value inside the method: {0}", x);

}

static void Main()

{

int n = 5;

ine("The value before calling the method: {0}", n);

SquareIt(n); // Passing the variable by value.

ine("The value after calling the method: {0}", n);

}

}

输出

The value before calling the method: 5

The value inside the method: 25

The value after calling the method: 5

代码讨论

变量 n 为值类型,包含其数据〔值为 5〕。当调用 SquareIt 时,n 的内容被复制到参数 x 中,在方法内将该参数求平方。但在 Main 中,n 的值在调用 SquareIt 方法前后是相同的。实际上,方法内发生的更改只影响局部变量 x。

例如:通过引用传递值类型

下面的例如除使用 ref 关键字传递参数以外,其余与上一例如相同。参数的值在调用方法后发生更改。

class PassingValByRef

{

static void SquareIt(ref int x)

// The parameter x is passed by reference.

// Changes to x will affect the original value of x.

{

x *= x;

ine("The value inside the method: {0}", x);

}

学习文档 仅供参考

static void Main()

{

int n = 5;

ine("The value before calling the method: {0}", n);

SquareIt(ref n); // Passing the variable by reference.

ine("The value after calling the method: {0}", n);

}

}

输出

The value before calling the method: 5

The value inside the method: 25

The value after calling the method: 25

代码讨论

本例如中,传递的不是 n 的值,而是对 n 的引用。参数 x 不是 int 类型,它是对 int 的引用〔本例中为对 n 的引用〕。因此,当在方法内对 x 求平方时,实际被求平方的是 x 所引用的项:n。

例如:交换值类型

更改所传递参数的值的常见例如是 Swap 方法,在该方法中传递 x 和 y 两个变量,然后使方法交换它们的内容。必须通过引用向 Swap 方法传递参数;否那么,方法内所处理的将是参数的本地副本。以下是使用引用参数的 Swap 方法的例如:

static void SwapByRef(ref int x, ref int y)

{

int temp = x;

x = y;

y = temp;

}

调用该方法时,请在调用中使用 ref 关键字,如下所示:

static void Main()

{

int i = 2, j = 3;

ine("i = {0} j = {1}" , i, j);

SwapByRef (ref i, ref j);

ine("i = {0} j = {1}" , i, j);

}

输出

i = 2 j = 3

i = 3 j = 2

27. 传递引用类型参数

引用类型的变量不直接包含其数据;它包含的是对其数据的引用。当通过值传递引用类型的参数时,有可能更改引用所指向的数据,如某类成员的值。但是无法更改引用本身的值;也就是说,不能使用相同的引用为新类分配内存并使之在块外保持。假设要这样做,应使用 ref 或 out 关键字传递参数。为了简单起见,下面的例如使用 ref。

例如:通过值传递引用类型

下面的例如演示通过值向 Change 方法传递引用类型的参数 arr。由于该参数是对 arr 的引用,所以有可能更改数组元素的值。但是,试图将参数重新分配到不同的内存位置时,该操作仅在方法内有学习文档 仅供参考

效,并不影响原始变量 arr。

class PassingRefByVal

{

static void Change(int[] pArray)

{

pArray[0] = 888; // This change affects the original element.

pArray = new int[5] {-3, -1, -2, -3, -4}; // This change is local.

ine("Inside the method, the first element is: {0}", pArray[0]);

}

static void Main()

{

int[] arr = {1, 4, 5};

ine("Inside Main, before calling the method, the first element is:

{0}", arr [0]);

Change(arr);

ine("Inside Main, after calling the method, the first element is: {0}",

arr [0]);

}

}

输出

Inside Main, before calling the method, the first element is: 1

Inside the method, the first element is: -3

Inside Main, after calling the method, the first element is: 888

代码讨论

在上个例如中,数组 arr 为引用类型,在未使用 ref 参数的情况下传递给方法。在此情况下,将向方法传递指向 arr 的引用的一个副本。输出显示方法有可能更改数组元素的内容,在这种情况下,从

1 改为 888。但是,在 Change 方法内使用 new 运算符来分配新的内存局部,将使变量 pArray 引用新的数组。因此,这之后的任何更改都不会影响原始数组 arr〔它是在 Main 内创立的〕。实际上,本例如中创立了两个数组,一个在 Main 内,一个在 Change 方法内。

例如:通过引用传递引用类型

本例如除在方法头和调用中使用 ref 关键字以外,其余与上个例如相同。方法内发生的任何更改都会影响调用程序中的原始变量。

class PassingRefByRef

{

static void Change(ref int[] pArray)

{

// Both of the following changes will affect the original variables:

pArray[0] = 888;

pArray = new int[5] {-3, -1, -2, -3, -4};

ine("Inside the method, the first element is: {0}", pArray[0]);

}

static void Main()

{

int[] arr = {1, 4, 5};

ine("Inside Main, before calling the method, the first element is:

学习文档 仅供参考

本文发布于:2024-01-05,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:方法类型使用参数字符串

发布评论

评论列表(有0条评论)
    福州电脑网_福州电脑维修_福州电脑之家_福州iThome

    福州电脑网_福州电脑维修_福州电脑之家_福州iThome

    福州电脑维修网(fzithome.com)专业的电脑维修,笔记本维修,上门维修各种电脑,笔记本,平板等,快速上门.电脑知识频道内容覆盖:计算机资讯,电脑基础应用知识,各种电脑故障维修学习,电脑外设产品维修维护,病毒,软件,硬件,常识.