封面

书名

C # 语言规范

摘要

本文描述 C# 编程语言的语法、语义和设计。

作者

Scott Wiltamuth 和 Anders Hejlsberg

备注

2005年9月18日 星期日（中秋节）

精简整理人员
Eyas Studio
Gavin(QQ:3242425)、Lowkey(QQ:8362678)
Http://Www.CsharpWeb.Com


目录.991

Powered By Gavin http://www.CSharpWeb.com

目录

C# 语言规范. 1

目录 2

1. 介绍. 17

1.1 开始. 18

1.2 类型. 19

1.2.1 预定义类型. 22

1.2.2 转换. 24

1.2.3 数组类型. 25

1.2.4 类型系统统一化. 28

1.3 变量和参数. 29

1.4 自动内存管理. 34

1.5 表达式. 37

1.6 语句. 38

1.7 类. 42

1.7.1 常数. 45

1.7.2 字段. 46

1.7.3 方法. 48

1.7.4 属性. 50

1.7.5 事件. 51

1.7.6 运算符. 53

1.7.7 索引器. 55

1.7.8 实例构造函数. 57

1.7.9 析构函数. 59

1.7.10 静态构造函数. 60

1.7.11 继承. 61

1.8 结构. 63

1.9 接口. 64

1.10 委托. 66

1.11 枚举. 67

1.12 命名空间和程序集. 68

1.13 版本控制. 70

1.14 属性. 73

2. 词法结构. 75

2.1 程序. 76

2.2 语法. 77

2.2.1 文法表示法. 78

2.2.2 词法文法. 79

2.2.3 句法文法. 80

2.3 词法分析. 81

2.3.1 行结束符. 82

2.3.2 空白. 83

2.3.3 注释. 84

2.4 标记. 86

2.4.1 Unicode 字符转义序列. 87

2.4.2 标识符. 88

2.4.3 关键字. 90

2.4.4 文本. 91

2.4.4.1 布尔值. 92

2.4.4.2 整数. 93

2.4.4.3 实数. 95

2.4.4.4 字符. 96

2.4.4.5 字符串. 97

2.4.4.6 空文本. 99

2.4.5 运算符和标点符号. 100

2.5 预处理指令. 101

2.5.1 条件编译符号. 103

2.5.2 预处理表达式. 104

2.5.3 声明指令. 105

2.5.4 条件编译指令. 107

2.5.5 诊断指令. 110

2.5.6 区域指令. 111

2.5.7 行指令. 112

3. 基本概念. 113

3.1 应用程序启动. 114

3.2 应用程序终止. 115

3.3 声明. 116

3.4 成员. 119

3.4.1 命名空间成员. 120

3.4.2 结构成员. 121

3.4.3 枚举成员. 122

3.4.4 类成员. 123

3.4.5 接口成员. 124

3.4.6 数组成员. 125

3.4.7 委托成员. 126

3.5 成员访问. 127

3.5.1 已声明可访问性. 128

3.5.2 可访问域. 129

3.5.3 实例成员的受保护访问. 132

3.5.4 可访问性约束. 133

3.6 签名和重载. 134

3.7 范围. 135

3.7.1 名称隐藏. 138

3.7.1.1 通过嵌套隐藏. 139

3.7.1.2 通过继承隐藏. 140

3.8 命名空间和类型名称. 142

3.8.1 完全限定名. 144

3.9 自动内存管理. 145

3.10 执行顺序. 148

4. 类型. 149

4.1 值类型. 150

4.1.1 System.ValueType 类型. 151

4.1.2 默认构造函数. 152

4.1.3 结构类型. 153

4.1.4 简单类型. 154

4.1.5 整型. 155

4.1.6 浮点型. 156

4.1.7 decimal 类型. 157

4.1.8 bool 类型. 158

4.1.9 枚举类型. 159

4.2 引用类型. 160

4.2.1 类类型. 161

4.2.2 对象类型. 162

4.2.3 string 类型. 163

4.2.4 接口类型. 164

4.2.5 数组类型. 165

4.2.6 委托类型. 166

4.3 装箱和取消装箱. 167

4.3.1 装箱转换. 168

4.3.2 取消装箱转换. 170

5. 变量. 171

5.1 变量类别. 172

5.1.1 静态变量. 173

5.1.2 实例变量. 174

5.1.2.1 类中的实例变量. 175

5.1.2.2 结构中的实例变量. 176

5.1.3 数组元素. 177

5.1.4 值参数. 178

5.1.5 引用参数. 179

5.1.6 输出参数. 180

5.1.7 局部变量. 181

5.2 默认值. 182

5.3 明确赋值. 183

5.3.1 初始已赋值变量. 184

5.3.2 初始未赋值变量. 185

5.3.3 确定明确赋值的细则. 186

5.3.3.1 一般语句规则. 187

5.3.3.2 块语句、checked 和 unchecked 语句. 188

5.3.3.3 表达式语句. 189

5.3.3.4 声明语句. 190

5.3.3.5 if 语句. 191

5.3.3.6 switch 语句. 192

5.3.3.7 while 语句. 193

5.3.3.8 do 语句. 194

5.3.3.9 for 语句. 195

5.3.3.10 break、continue 和 goto 语句. 196

5.3.3.11 throw 语句. 197

5.3.3.12 return 语句. 198

5.3.3.13 try-catch 语句. 199

5.3.3.14 try-finally 语句. 200

5.3.3.15 try-catch-finally 语句. 201

5.3.3.16 foreach 语句. 203

5.3.3.17 using 语句. 204

5.3.3.18 lock 语句. 205

5.3.3.19 简单表达式的一般规则. 206

5.3.3.20 带有嵌入表达式的表达式的一般规则. 207

5.3.3.21 调用表达式和对象创建表达式. 208

5.3.3.22 简单赋值表达式. 209

5.3.3.23 && 表达式. 210

5.3.3.24 || 表达式. 211

5.3.3.25 ! 表达式. 212

5.3.3.26 ?: 表达式. 213

5.4 变量引用. 214

5.5 变量引用的原子性. 215

6. 转换. 216

6.1 隐式转换. 217

6.1.1 标识转换. 218

6.1.2 隐式数值转换. 219

6.1.3 隐式枚举转换. 220

6.1.4 隐式引用转换. 221

6.1.5 装箱转换. 222

6.1.6 隐式常数表达式转换. 223

6.1.7 用户定义的隐式转换. 224

6.2 显式转换. 225

6.2.1 显式数值转换. 226

6.2.2 显式枚举转换. 228

6.2.3 显式引用转换. 229

6.2.4 取消装箱转换. 230

6.2.5 用户定义的显式转换. 231

6.3 标准转换. 232

6.3.1 标准隐式转换. 233

6.3.2 标准显式转换. 234

6.4 用户定义的转换. 235

6.4.1 允许的用户定义转换. 236

6.4.2 用户定义转换的计算. 237

6.4.3 用户定义的隐式转换. 238

6.4.4 用户定义的显式转换. 239

7. 表达式. 240

7.1 表达式的分类. 241

7.1.1 表达式的值. 242

7.2 运算符. 243

7.2.1 运算符的优先级和顺序关联性. 244

7.2.2 运算符重载. 245

7.2.3 一元运算符重载决策. 246

7.2.4 二元运算符重载决策. 247

7.2.5 候选用户定义运算符. 248

7.2.6 数值提升. 249

7.2.6.1 一元数值提升. 250

7.2.6.2 二元数值提升. 251

7.3 成员查找. 252

7.3.1 基类型. 253

7.4 函数成员. 254

7.4.1 参数列表. 257

7.4.2 重载决策. 260

7.4.2.1 适用的函数成员. 261

7.4.2.2 更好的函数成员. 262

7.4.2.3 更好的转换. 263

7.4.3 函数成员调用. 264

7.4.3.1 已装箱实例上的调用. 265

7.5 基本表达式. 266

7.5.1 文本. 267

7.5.2 简称. 268

7.5.2.1 块中的固定含义. 269

7.5.3 带括号的表达式. 271

7.5.4 成员访问. 272

7.5.4.1 相同的简称和类型名称. 274

7.5.5 调用表达式. 275

7.5.5.1 方法调用. 276

7.5.5.2 委托调用. 277

7.5.6 元素访问. 278

7.5.6.1 数组访问. 279

7.5.6.2 索引器访问. 280

7.5.7 This 访问. 281

7.5.8 Base 访问. 282

7.5.9 后缀增量和后缀减量运算符. 283

7.5.10 new 运算符. 284

7.5.10.1 对象创建表达式. 285

7.5.10.2 数组创建表达式. 286

7.5.10.3 委托创建表达式. 288

7.5.11 typeof 运算符. 290

7.5.12 checked 和 unchecked 运算符. 291

7.6 一元运算符. 294

7.6.1 一元加运算符. 295

7.6.2 一元减运算符. 296

7.6.3 逻辑否定运算符. 297

7.6.4 按位求补运算符. 298

7.6.5 前缀增量和减量运算符. 299

7.6.6 强制转换表达式. 300

7.7 算术运算符. 301

7.7.1 乘法运算符. 302

7.7.2 除法运算符. 303

7.7.3 余数运算符. 304

7.7.4 加法运算符. 305

7.7.5 减法运算符. 307

7.8 移位运算符. 309

7.9 关系和类型测试运算符. 311

7.9.1 整数比较运算符. 312

7.9.2 浮点比较运算符. 313

7.9.3 小数比较运算符. 314

7.9.4 布尔相等运算符. 315

7.9.5 枚举比较运算符. 316

7.9.6 引用类型相等运算符. 317

7.9.7 字符串相等运算符. 319

7.9.8 委托相等运算符. 320

7.9.9 is 运算符. 321

7.9.10 as 运算符. 322

7.10 逻辑运算符. 323

7.10.1 整数逻辑运算符. 324

7.10.2 枚举逻辑运算符. 325

7.10.3 布尔逻辑运算符. 326

7.11 条件逻辑运算符. 327

7.11.1 布尔条件逻辑运算符. 328

7.11.2 用户定义的条件逻辑运算符. 329

7.12 条件运算符. 330

7.13 赋值运算符. 331

7.13.1 简单赋值. 332

7.13.2 复合赋值. 335

7.13.3 事件赋值. 336

7.14 表达式. 337

7.15 常数表达式. 338

7.16 布尔表达式. 339

8. 语句. 340

8.1 结束点和可到达性. 341

8.2 块. 343

8.2.1 语句列表. 344

8.3 空语句. 345

8.4 标记语句. 346

8.5 声明语句. 347

8.5.1 局部变量声明. 348

8.5.2 局部常数声明. 349

8.6 表达式语句. 350

8.7 选择语句. 351

8.7.1 if 语句. 352

8.7.2 switch 语句. 353

8.8 迭代语句. 357

8.8.1 while 语句. 358

8.8.2 do 语句. 359

8.8.3 for 语句. 360

8.8.4 foreach 语句. 361

8.9 跳转语句. 364

8.9.1 break 语句. 366

8.9.2 continue 语句. 367

8.9.3 goto 语句. 368

8.9.4 return 语句. 370

8.9.5 throw 语句. 371

8.10 try 语句. 372

8.11 checked 和 unchecked 语句. 375

8.12 lock 语句. 376

8.13 using 语句. 377

9. 命名空间. 380

9.1 编译单元. 381

9.2 命名空间声明. 382

9.3 using 指令. 384

9.3.1 using 别名指令. 385

9.3.2 using 命名空间指令. 388

9.4 命名空间成员. 391

9.5 类型声明. 392

10. 类. 393

10.1 类声明. 394

10.1.1 类修饰符. 395

10.1.1.1 抽象类. 396

10.1.1.2 密封类. 397

10.1.2 类基规范. 398

10.1.2.1 基类. 399

10.1.2.2 接口实现. 401

10.1.3 类体. 402

10.2 类成员. 403

10.2.1 继承. 405

10.2.2 new 修饰符. 406

10.2.3 访问修饰符. 407

10.2.4 构成类型. 408

10.2.5 静态和实例成员. 409

10.2.6 嵌套类型. 411

10.2.6.1 完全限定名. 412

10.2.6.2 声明可访问性. 413

10.2.6.3 隐藏. 414

10.2.6.4 this 访问. 415

10.2.6.5 对包含类型的私有和受保护成员的访问. 416

10.2.7 保留成员名称. 418

10.2.7.1 为属性保留的成员名称. 419

10.2.7.2 为事件保留的成员名称. 421

10.2.7.3 为索引器保留的成员名称. 422

10.2.7.4 为析构函数保留的成员名称. 423

10.3 常数. 424

10.4 字段. 426

10.4.1 静态和实例字段. 428

10.4.2 只读字段. 429

10.4.2.1 对常数使用静态只读字段. 430

10.4.2.2 常数和静态只读字段的版本控制. 431

10.4.3 易失字段. 432

10.4.4 字段初始化. 434

10.4.5 变量初始值设定项. 435

10.4.5.1 静态字段初始化. 437

10.4.5.2 实例字段初始化. 439

10.5 方法. 440

10.5.1 方法参数. 442

10.5.1.1 值参数. 443

10.5.1.2 引用参数. 444

10.5.1.3 输出参数. 446

10.5.1.4 参数数组. 447

10.5.2 静态和实例方法. 450

10.5.3 虚拟方法. 451

10.5.4 重写方法. 454

10.5.5 密封方法. 456

10.5.6 抽象方法. 457

10.5.7 外部方法. 459

10.5.8 方法体. 460

10.5.9 方法重载. 461

10.6 属性. 462

10.6.1 静态和实例属性. 463

10.6.2 访问器. 464

10.6.3 虚拟、密封、重写和抽象访问器. 470

10.7 事件. 472

10.7.1 类似字段的事件. 474

10.7.2 事件访问器. 476

10.7.3 静态和实例事件. 478

10.7.4 虚拟、密封、重写和抽象访问器. 479

10.8 索引器. 480

10.8.1 索引器重载. 484

10.9 运算符. 485

10.9.1 一元运算符. 487

10.9.2 二元运算符. 488

10.9.3 转换运算符. 489

10.10 实例构造函数. 491

10.10.1 构造函数初始值设定项. 492

10.10.2 实例变量初始值设定项. 493

10.10.3 构造函数执行. 494

10.10.4 默认构造函数. 497

10.10.5 私有构造函数. 498

10.10.6 可选的实例构造函数参数. 499

10.11 静态构造函数. 500

10.12 析构函数. 503

11. 结构. 505

11.1 结构声明. 506

11.1.1 结构修饰符. 507

11.1.2 结构接口. 508

11.1.3 结构体. 509

11.2 结构成员. 510

11.3 类和结构的区别. 511

11.3.1 值语义. 512

11.3.2 继承. 513

11.3.3 赋值. 514

11.3.4 默认值. 515

11.3.5 装箱和取消装箱. 516

11.3.6 this 的意义. 517

11.3.7 字段初始值设定项. 518

11.3.8 构造函数. 519

11.3.9 析构函数. 521

11.4 结构示例. 522

11.4.1 数据库整数类型. 523

11.4.2 数据库布尔类型. 526

12. 数组. 529

12.1 数组类型. 530

12.1.1 System.Array 类型. 531

12.2 数组创建. 532

12.3 数组元素访问. 533

12.4 数组成员. 534

12.5 数组协方差. 535

12.6 数组初始值设定项. 536

13. 接口. 538

13.1 接口声明. 539

13.1.1 接口修饰符. 540

13.1.2 基接口. 541

13.1.3 接口体. 542

13.2 接口成员. 543

13.2.1 接口方法. 544

13.2.2 接口属性. 545

13.2.3 接口事件. 546

13.2.4 接口索引器. 547

13.2.5 接口成员访问. 548

13.3 完全限定接口成员名. 551

13.4 接口实现. 552

13.4.1 显式接口成员实现. 553

13.4.2 接口映射. 556

13.4.3 接口实现继承. 560

13.4.4 接口重新实现. 562

13.4.5 抽象类和接口. 564

14. 枚举. 565

14.1 枚举声明. 566

14.2 枚举修饰符. 567

14.3 枚举成员. 568

14.4 System.Enum 类型. 571

14.5 枚举值和运算. 572

15. 委托. 573

15.1 委托声明. 574

15.2 委托实例化. 576

15.3 委托调用. 577

16. 异常. 580

16.1 导致异常的原因. 581

16.2 System.Exception 类. 582

16.3 异常的处理方式. 583

16.4 公共异常类. 584

17. 属性. 585

17.1 属性类. 586

17.1.1 属性用法. 587

17.1.2 定位和命名参数. 589

17.1.3 属性参数类型. 590

17.2 属性专用化. 591

17.3 属性实例. 596

17.3.1 属性的编译. 597

17.3.2 属性实例的运行时检索. 598

17.4 保留属性. 599

17.4.1 AttributeUsage 属性. 600

17.4.2 Conditional 属性. 601

17.4.3 Obsolete 属性. 604

17.5 交互操作的属性. 606

17.5.1 与 COM 和 Win32 组件的交互操作. 607

17.5.2 与其他 .NET 语言的交互操作. 608

17.5.2.1 IndexerName 属性. 609

A. 不安全代码. 610

A.1 不安全上下文. 611

A.2 指针类型. 614

A.3 固定和可移动变量. 617

A.4 指针转换. 618

A.5 表达式中的指针. 620

A.5.1 指针间接寻址. 621

A.5.2 指针成员访问. 622

A.5.3 指针元素访问. 624

A.5.4 address-of 运算符. 625

A.5.5 指针增加和减小. 626

A.5.6 指针算法. 627

A.5.7 指针比较. 629

A.5.8 sizeof 运算符. 630

A.6 固定语句. 631

A.7 堆栈分配. 635

A.8 动态内存分配. 637

B. 文档注释. 640

B.1 介绍. 641

B.2 建议的标记. 642

B.2.1 <c> 643

B.2.2 <code> 644

B.2.3 <example> 645

B.2.4 <exception> 646

B.2.5 <include> 647

B.2.6 <list> 648

B.2.7 <para> 650

B.2.8 <param> 651

B.2.9 <paramref> 652

B.2.10 <permission> 653

B.2.11 <remarks> 654

B.2.12 <returns> 655

B.2.13 <see> 656

B.2.14 <seealso> 657

B.2.15 <summary> 658

B.2.16 <value> 659

B.3 处理文档文件. 660

B.3.1 ID 字符串格式. 661

B.3.2 ID 字符串示例. 662

B.4 示例. 667

B.4.1 C# 源代码. 668

B.4.2 产生的 XML 672

C. 语法. 676

D. 参考书目. 707


1.介绍.156248

Powered By Gavin http://www.CSharpWeb.com

1. 介绍

C#（读作“C sharp”）是一种简单、现代、面向对象且类型安全的编程语言。C 和 C++ 程序员能很快熟悉它。C# 同时具备“应用程序快速开发”(RAD) 语言的高效率和 C++ 固有的强大能力。

Visual C# .NET 是 Microsoft 的 C# 开发工具。它包括交互式开发环境、可视化设计器（用于生成 Windows 和 Web 应用程序）、编译器和调试器。Visual C# .NET 是 Visual Studio .NET 的产品套件的组成部分，该产品套件还包括 Visual Basic .NET、Visual C++ .NET 和 JScript 脚本语言。所有这些语言都支持对 Microsoft .NET Framework 的访问，该框架包括一个公共执行引擎和一个丰富的类库。.NET Framework 定义了一个“公共语言规范”(CLS)，这是一种通用规范，它确保遵循该规范的语言与类库之间实现无缝的互操作性。对 C# 开发人员而言，这意味着即使 C# 是一种新语言，但仍可恰如其他成熟工具（如 Visual Basic .NET 和 Visual C++ .NET）一样，使用相同的、功能丰富的类库。C# 本身不包含类库。

本章的其余部分描述该语言的基本功能。虽然后面的章节会详尽地（有时甚至以数学方式）对规则和例外情况进行描述，但本章的描述力求简洁明了，即便因此而影响完整性。这样做是为了向读者提供关于该语言的概况，以方便读者编写早期的程序和阅读后面的章节。


1.1开始.157791

1.1 开始

经典性“hello, world”程序可以写为：

using System;

class Hello

{

static void Main() {

Console.WriteLine("hello, world");

}

}

C# 程序的源代码通常存储在一个或多个以 .cs 为文件扩展名的文本文件（如 hello.cs）中。可以通过 Visual Studio .NET 所提供的命令行编译器，使用以下命令行指令来编译此程序：

csc hello.cs

它产生一个名为 hello.exe 的应用程序。当此应用程序运行时，它产生的输出是：

hello, world

仔细观察此程序可以发现：

· “using System;”指令引用一个名为 System 的命名空间，它由 Microsoft .NET Framework 类库定义。此命名空间包含 Main 方法中引用的 Console 类。命名空间提供了一种分层方法来组织一个或多个程序中的各种元素。用“using”指令指定一个命名空间后，该命名空间中的所有成员均可直接被引用。所以，在“hello, world”程序中，可直接使用 Console.WriteLine（而不必使用 System.Console.WriteLine）。

· Main 方法是 Hello 类的成员。它具有 static 修饰符，因此 Main 方法是相对于类 Hello 本身而不是相对于此类的实例。

· 应用程序的入口点（即当程序开始运行时首先被调用的方法）总是名为 Main 的静态方法。

· “hello, world”输出依靠类库实现。C# 语言本身不提供类库，它使用公共的类库（Visual Basic .NET 和 Visual C++ .NET 也使用它）。

对 C 和 C++ 开发人员而言，值得注意的是一些“没有”出现在“hello, world”程序中的东西。

· 该程序中的 Main 方法不是全局的。C# 不支持全局级别的方法和变量；这类元素总是包含在类型声明（如类声明和结构声明）中。

· 该程序没有使用“::”运算符和“->”运算符。在 C# 中，“::”根本不是运算符，而“->”运算符仅在一小部分程序中使用，即那些涉及不安全代码的程序（第 A 节）。分隔符“.”在复合名称中使用，如 Console.WriteLine。

· 该程序没有包含前向声明。C# 中声明出现的顺序并不重要，所以不需要作前向声明。

· 该程序没有使用 #include 导入程序文本。程序间的依赖项通过符号而不是文本来控制。这样就消除了由多种语言编写的应用程序之间的障碍。例如，Console 类不需要用 C# 编写。


1.2类型.163595

1.2 类型

C# 支持两种类型：“值类型”和“引用类型”。值类型包括简单类型（如 char、int 和 float）、枚举类型和结构类型。引用类型包括类 (Class) 类型、接口类型、委托类型和数组类型。

值类型与引用类型的区别在于值类型的变量直接包含其数据，而引用类型的变量则存储对象引用。对于引用类型，两个变量可能引用同一个对象，因此对一个变量的操作可能影响另一个变量所引用的对象。对于值类型，每个变量都有自己的数据副本，对一个变量的操作不可能影响另一个变量。

示例

using System;

class Class1

{

public int Value = 0;

}

class Test

{

static void Main() {

int val1 = 0;

int val2 = val1;

val2 = 123;

Class1 ref1 = new Class1();

Class1 ref2 = ref1;

ref2.Value = 123;

Console.WriteLine("Values: {0}, {1}", val1, val2);

Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value);

}

}

显示了这种区别。运行该程序，可见下列输出：

Values: 0, 123

Refs: 123, 123

给局部变量 val1 赋值不会影响局部变量 val2，这是因为两个局部变量都是值类型（int 类型），每个局部变量都保存着各自的数据。相反，赋值 ref2.Value = 123; 则会影响到 ref2，因为 ref1 和 ref2 所引用的其实是同一个对象。

应对代码行

Console.WriteLine("Values: {0}, {1}", val1, val2);

Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value);

做进一步解释，因为方法 Console.WriteLine 的某些字符串格式化行为较复杂，它所需的参数数目是可变的。第一个参数是字符串，它可能包含类似 {0} 和 {1} 这样编了号的占位符。每个占位符都引用一个尾随参数：{0} 引用第二个参数，{1} 引用第三个参数，依此类推。在将输出发送到控制台之前，每个占位符都会被替换成它所引用的参数的值，并按规定的格式显示。

开发人员可以通过枚举声明和结构声明定义新的值类型，并可通过类声明、接口声明和委托声明定义新的引用类型。示例

using System;

public enum Color

{

Red, Blue, Green

}

public struct Point

{

public int x, y;

}

public interface IBase

{

void F();

}

public interface IDerived: IBase

{

void G();

}

public class A

{

protected virtual void H() {

Console.WriteLine("A.H");

}

}

public class B: A, IDerived

{

public void F() {

Console.WriteLine("B.F, implementation of IDerived.F");

}

public void G() {

Console.WriteLine("B.G, implementation of IDerived.G");

}

override protected void H() {

Console.WriteLine("B.H, override of A.H");

}

}

public delegate void EmptyDelegate();

列举了每种类型声明的示例。后面几节将阐明关于类型声明的细节。


1.2.1预定义类型.170579

1.2.1 预定义类型

C# 提供了一组预定义的类型，这些类型中的大多数是 C 和 C++ 开发人员所熟悉的。

预定义的类型中，属于引用类型的有 object 和 string 两类。object 类型是所有其他类型的最终基类型。string 类型用于表示 Unicode 字符串值。string 类型的值是不可变的。

预定义的值类型包括有符号整型、无符号整型、浮点型以及 bool、char 和 decimal 等类型。属有符号整型的有 sbyte、short、int 和 long；属无符号整型的有 byte、ushort、uint 和 ulong；属浮点型的有 float 和 double。

bool 类型用于表示布尔值，即仅有真、假两个值。设置 bool 类型可使编写的代码较易于实现自我文档化，并且有助于消除很常见的 C++ 编码错误，即开发人员在本应使用“==”的地方错误地使用了“=”。以下列代码为例：

int i = ...;

F(i);

if (i = 0) // Bug: the test should be (i == 0)

G();

如果在 C# 中编译这段代码，则会导致编译错误。这是因为表达式 i = 0 是 int 类型，而 if 语句要求 bool 类型的表达式。

char 类型用于表示 Unicode 字符。char 类型的变量表示单个 16 位 Unicode 字符。

decimal 类型适合于那些无法接受由浮点表示形式导致的舍入错误的计算。常见的示例包括税务计算和货币转换这样的金融计算。decimal 类型提供 28 位有效数字。

下表列出了预定义类型，并说明如何为每种类型编写文本值。

类型

说明

示例

object

所有其他类型的最终基类型

object o = null;

string

字符串类型；字符串是 Unicode 字符序列

string s = "hello";

sbyte

8 位有符号整型

sbyte val = 12;

short

16 位有符号整型

short val = 12;

int

32 位有符号整型

int val = 12;

long

64 位有符号整型

long val1 = 12;
long val2 = 34L;

byte

8 位无符号整型

byte val1 = 12;

ushort

16 位无符号整型

ushort val1 = 12;

uint

32 位无符号整型

uint val1 = 12;
uint val2 = 34U;

ulong

64 位无符号整型

ulong val1 = 12;
ulong val2 = 34U;
ulong val3 = 56L;
ulong val4 = 78UL;

float

单精度浮点型

float val = 1.23F;

double

双精度浮点型

double val1 = 1.23;
double val2 = 4.56D;

bool

布尔型；bool 值或为真或为假

bool val1 = true;
bool val2 = false;

char

字符类型；char 值是一个 Unicode 字符

char val = 'h';

decimal

精确的小数类型，具有 28 个有效数字

decimal val = 1.23M;

每个预定义类型都是一个在 System 命名空间内定义的类型的简写形式。例如，关键字 int 所指的实际上是结构类型 System.Int32。就编写源代码的样式而言，使用关键字比使用完整的系统类型名称要好。

预定义的值类型（如 int）在少数情况下会进行特殊处理，而大部分情况下完全像其他结构一样处理。运算符重载使开发人员能够定义行为与预定义的值类型基本相同的新结构类型。例如，Digit 结构可支持与预定义整型相同的数学运算，并可定义 Digit 与预定义类型之间的转换。

预定义类型本身使用运算符重载。例如，比较运算符 == 和 != 对于不同的预定义类型有不同的语义：

· 如果两个 int 类型的表达式表示相同的整数值，则两个表达式被视为相等。

· 如果两个 object 类型的表达式都引用同一对象或者都为 null，则两个表达式被视为相等。

· 如果两个 string 类型的表达式的字符串实例的长度相同并且每个字符位置中的字符也相同，或者都为 null，则两个表达式被视为相等。

示例

using System;

class Test

{

static void Main() {

string s = "Test";

string t = string.Copy(s);

Console.WriteLine(s == t);

Console.WriteLine((object)s == (object)t);

}

}

产生输出

True

False

这是因为第一个比较的是两个 string 类型的表达式，而第二个比较的是两个 object 类型的表达式。


1.2.2转换

1.2.2 转换

预定义类型还具有预定义的转换。例如，在预定义类型 int 和 long 之间定义了转换方法。C# 区分两种转换：“隐式转换”和“显式转换”。隐式转换仅适用于那些不用仔细检查即可安全地实现的转换。例如，从 int 到 long 的转换是隐式转换。这种转换总是会成功，而且从不会导致信息丢失。在以下示例中：

using System;

class Test

{

static void Main() {

int intValue = 123;

long longValue = intValue;

Console.WriteLine("{0}, {1}", intValue, longValue);

}

}

类型 int 转换为 long 是隐式地实现的。

相反，显式转换需用强制转换表达式 (cast) 来实现。在以下示例中：

using System;

class Test

{

static void Main() {

long longValue = Int64.MaxValue;

int intValue = (int) longValue;

Console.WriteLine("(int){0} = {1}", longValue, intValue);

}

}

类型 long 转换为 int 是显式转换，它使用了强制转换表达式。输出为：

(int) 9223372036854775807 = -1

这是因为有溢出发生。强制转换表达式既可用于显式转换亦可用于隐式转换。


1.2.3数组类型.191602

1.2.3 数组类型

数组可以是一维或多维的。“矩形”排列的和“交错形”排列的数组都受支持。

一维数组是最常见的类型。示例

using System;

class Test

{

static void Main() {

int[] arr = new int[5];

for (int i = 0; i < arr.Length; i++)

arr[i] = i * i;

for (int i = 0; i < arr.Length; i++)

Console.WriteLine("arr[{0}] = {1}", i, arr[i]);

}

}

创建一个由 int 值构成的一维数组，初始化数组元素，然后将它们每个都打印出来。产生的输出为：

arr[0] = 0

arr[1] = 1

arr[2] = 4

arr[3] = 9

arr[4] = 16

前面示例中使用的 int[] 类型即为一种数组类型。数组类型是用一个非数组类型名称后跟一个或多个秩说明符来表示的。示例

class Test

{

static void Main() {

int[] a1; // single-dimensional array of int

int[,] a2; // 2-dimensional array of int

int[,,] a3; // 3-dimensional array of int

int[][] j2; // "jagged" array: array of (array of int)

int[][][] j3; // array of (array of (array of int))

}

}

显示了一些局部变量，声明了一些属于以 int 作为元素的数组类型的不同变型。

数组类型为引用类型，因此数组变量的声明只是为数组引用留出空间。数组实例实际上是通过数组初始值设定项和数组创建表达式创建的。示例

class Test

{

static void Main() {

int[] a1 = new int[] {1, 2, 3};

int[,] a2 = new int[,] {{1, 2, 3}, {4, 5, 6}};

int[,,] a3 = new int[10, 20, 30];

int[][] j2 = new int[3][];

j2[0] = new int[] {1, 2, 3};

j2[1] = new int[] {1, 2, 3, 4, 5, 6};

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

}

}

显示了各种数组创建表达式。变量 a1、a2 和 a3 表示“矩形数组”，而变量 j2 表示“交错形数组”。这些术语基于数组的形状，这一点不足为奇。矩形数组总是具有矩形形状。已知数组每个维度的长度，它的矩形形状就很清楚了。例如，a3 的三个维度的长度分别是 10、20 和 30，很容易就可以看出此数组包含 10*20*30 个元素。

相反，变量 j2 表示一个“交错的”数组或“数组的数组”。具体说来，j2 表示 int 数组的数组，即类型 int[] 的一维数组的数组。这些 int[] 变量中的每一个都可以单独初始化，这就允许数组采用交错形状。此示例给每个 int[] 数组提供了一个不同的长度。具体说来，j2[0] 的长度为 3，j2[1] 的长度为 6，j2[2] 的长度为 9。

在 C++ 中，用 int x[3][5][7] 声明的数组会被视为三维矩形数组，而在 C# 中，int[][][] 声明的是一个交错的数组类型。

数组的元素类型和数组的形状（交错形还是矩形，以及它的维数）是数组类型定义的组成部分。另一方面，数组的大小（由它的每个维度的长度表示）却不是数组类型定义的组成部分。C# 语法明确地规定了这个区别，数组的每个维的长度需在数组创建表达式中指定，而不能在数组类型声明中指定。例如，声明

int[,,] a3 = new int[10, 20, 30];

包含了一个数组类型 int[,,] 和一个数组创建表达式 new int[10, 20, 30]。

对于局部变量和字段的声明，允许使用简写形式，因此没有必要再次声明数组类型。例如，示例

int[] a1 = new int[] {1, 2, 3};

可以简写为

int[] a1 = {1, 2, 3};

它们具有完全相同的程序语义。

从数组初始值设定项（如 {1, 2, 3}）所处的上下文可以确定该数组的类型。示例

class Test

{

static void Main() {

short[] a = {1, 2, 3};

int[] b = {1, 2, 3};

long[] c = {1, 2, 3};

}

}

显示同一数组初始值设定项语法可用于多种不同的数组类型。由于需要根据所处的上下文来确定数组初始值设定项的类型，因此，数组初始值设定项不能用于其所处的上下文中没有关于数组类型的显式声明的表达式中。


1.2.4类型系统统一化.199933

1.2.4 类型系统统一化

C# 提供了一个“统一类型系统”。所有类型（包括值类型）都是从 object 类型派生的。这样，类型 Object 中定义的方法就可以在任何值类型的值（甚至是像 int 这样的“基元”类型的值）上调用。示例

using System;

class Test

{

static void Main() {

Console.WriteLine(3.ToString());

}

}

从整数值 3 调用 object 定义的 ToString 方法，产生输出“3”。

示例

class Test

{

static void Main() {

int i = 123;

object o = i; // boxing

int j = (int) o; // unboxing

}

}

更有趣。一个 int 值可以转换为 object，并可再次转换回 int。此示例同时显示了“装箱”和“取消装箱”。当值类型的变量需要转换为引用类型时，执行“装箱”，即设置一个对象箱来保存值这个值（该值被复制到箱中）。“取消装箱”则正好相反。当对象箱被强制转换回其原来的值类型时，该值就从箱中取出并复制到适当的存储位置。

此类型系统统一化为值类型提供了对象性的优点，并且不会带来不必要的系统开销。对于不需要 int 值的行为与对象一样的程序，int 值只是 32 位值。对于需要 int 值的行为与对象一样的程序，可以根据需要使用此功能。这种将值类型作为对象处理的能力弥补了大多数语言中存在的值类型与引用类型之间的差距。例如，Stack 类可以提供 Push 和 Pop 方法，这些方法获得并返回一个 object 值。

public class Stack

{

public object Pop() {...}

public void Push(object o) {...}

}

由于 C# 具有统一类型系统，因此 Stack 类可以与任何类型的元素一起使用，其中包括像 int 这样的值类型。


1.3变量和参数.203679

1.3 变量和参数

变量表示存储位置。每个变量都具有一个类型，它确定哪些值可以存储在该变量中。局部变量是在方法、属性或索引器中声明的变量。局部变量是通过指定类型名称和声明符（它指定变量名和可选的初始值）定义的，如：

int a;

int b = 1;

但局部变量声明也可以包含多个声明符。a 和 b 的声明可以重写为：

int a, b = 1;

一个变量必须先赋值，然后才能使用它的值。示例

class Test

{

static void Main() {

int a;

int b = 1;

int c = a + b; // error, a not yet assigned

...

}

}

导致编译时错误，因为它试图在给变量 a 赋值之前使用它。第 5.3 节中定义了关于如何明确地赋值的规则。

字段（第 10.4 节）是与类或结构或与类或结构的实例关联的变量。用 static 修饰符声明的字段定义静态变量，不用此修饰符声明的字段则定义实例变量。静态字段与类型关联，而实例变量与实例关联。示例

using Personnel.Data;

class Employee

{

private static DataSet ds;

public string Name;

public decimal Salary;

...

}

显示了具有一个私有静态变量和两个公共实例变量的 Employee 类。

形参声明也定义变量。有四种类型的参数：值参数、引用参数、输出参数和参数数组。

值参用于“in”参数传递。在此过程中，自变量的值被传入方法中。因为一个值参在方法中用自己的变量存储（而非原自变量），所以对此参数的修改不会影响到原自变量。值参的变量是通过复制原自变量的值来初始化的。示例

using System;

class Test {

static void F(int p) {

Console.WriteLine("p = {0}", p);

p++;

}

static void Main() {

int a = 1;

Console.WriteLine("pre: a = {0}", a);

F(a);

Console.WriteLine("post: a = {0}", a);

}

}

显示了一个具有名为 p 的值参数的方法 F。示例输出结果如下

pre: a = 1

p = 1

post: a = 1

即使值参数 p 已修改。

“引用参数”用于“by reference”参数传递。在此过程中，“引用参数”就是调用者提供的自变量的别名。“引用参数”并不定义自己的变量，而是直接引用原自变量，因此对“引用参数”的修改就将直接影响相应原自变量的值。引用参数用 ref 修饰符来声明。示例

using System;

class Test {

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

int t = a;

a = b;

b = t;

}

static void Main() {

int x = 1;

int y = 2;

Console.WriteLine("pre: x = {0}, y = {1}", x, y);

Swap(ref x, ref y);

Console.WriteLine("post: x = {0}, y = {1}", x, y);

}

}

显示了一个具有两个引用参数的 Swap 方法。产生的输出为：

pre: x = 1, y = 2

post: x = 2, y = 1

在形参声明和形参的使用中都必须使用 ref 关键字。在调用位置使用 ref 可以引起对参数的特别注意，这样阅读代码的开发人员就会知道参数值可以因调用而更改。

对于输出参数来说，调用者提供的自变量的初始值并不重要，除此之外，输出参数与引用参数类似。输出参数是用 out 修饰符声明的。示例

using System;

class Test {

static void Divide(int a, int b, out int result, out int remainder) {

result = a / b;

remainder = a % b;

}

static void Main() {

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

for (int j = 1; j < 10; j++) {

int ans, r;

Divide(i, j, out ans, out r);

Console.WriteLine("{0} / {1} = {2}r{3}", i, j, ans, r);

}

}

}

显示了一个 Divide 方法，该方法包含两个输出参数：一个参数用于除法的结果，另一个参数用于余数。

对于值、引用和输出这三种类型的参数，在调用方提供的自变量和用于表示它们的参数之间存在一对一的对应关系。参数数组则允许存在多对一关系：多个参数可以用一个自变量来表示。换言之，参数数组允许可变长度的自变量列表。

参数数组用 params 修饰符声明。一个给定的方法只能有一个参数数组，而且它必须始终是最后一个指定的参数。参数数组的类型总是一维数组类型。调用方可以传递一个属同一类型的数组变量，或任意多个与该数组的元素属同一类型的自变量。例如，示例

using System;

class Test

{

static void F(params int[] args) {

Console.WriteLine("# of arguments: {0}", args.Length);

for (int i = 0; i < args.Length; i++)

Console.WriteLine("\targs[{0}] = {1}", i, args[i]);

}

static void Main() {

F();

F(1);

F(1, 2);

F(1, 2, 3);

F(new int[] {1, 2, 3, 4});

}

}

显示了带数目可变的 int 参数的方法 F，以及对此方法的若干个调用。输出为：

# of arguments: 0

# of arguments: 1

args[0] = 1

# of arguments: 2

args[0] = 1

args[1] = 2

# of arguments: 3

args[0] = 1

args[1] = 2

args[2] = 3

# of arguments: 4

args[0] = 1

args[1] = 2

args[2] = 3

args[3] = 4

此介绍中出现的大部分示例使用 Console 类的 WriteLine 方法。此方法的参数替换行为（如下面的示例所示）

int a = 1, b = 2;

Console.WriteLine("a = {0}, b = {1}", a, b);

是使用参数数组完成的。为适应常见的各种需要，WriteLine 方法有若干个重载的方法供选用，这些方法中，有些需传递固定个数的参数，有些使用了参数数组。

namespace System

{

public class Console

{

public static void WriteLine(string s) {...}

public static void WriteLine(string s, object a) {...}

public static void WriteLine(string s, object a, object b) {...}

...

public static void WriteLine(string s, params object[] args) {...}

}

}


1.4自动内存管理.217767

1.4 自动内存管理

手动内存管理要求开发人员管理内存块的分配和回收。手动内存管理可能既耗时又麻烦。在 C# 中提供了自动内存管理，使开发人员从这个繁重的任务中解脱出来。在绝大多数情况下，自动内存管理可以提高代码质量和开发人员的工作效率，并且不会对表达能力或性能造成负面影响。

在以下示例中：

using System;

public class Stack

{

private Node first = null;

public bool Empty {

get {

return (first == null);

}

}

public object Pop() {

if (first == null)

throw new Exception("Can't Pop from an empty Stack.");

else {

object temp = first.Value;

first = first.Next;

return temp;

}

}

public void Push(object o) {

first = new Node(o, first);

}

private class Node

{

public Node Next;

public object Value;

public Node(object value): this(value, null) {}

public Node(object value, Node next) {

Next = next;

Value = value;

}

}

}

显示了一个 Stack 类，它实际上是 Node 实例的一个链接表。Node 实例是在 Push 方法中创建的，当不再需要它们时会对其进行垃圾回收。当任何代码都不再可能访问某个 Node 实例时，该实例就成为垃圾回收的对象。例如，当从 Stack 中移除某项时，相关的 Node 实例就符合了垃圾回收条件，等待被回收。

在以下示例中：

class Test

{

static void Main() {

Stack s = new Stack();

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

s.Push(i);

s = null;

}

}

显示了使用 Stack 类的代码。该代码创建了一个 Stack 类，并用 10 个元素初始化该类，然后给它赋值 null。给变量 s 赋了 null 值后，Stack 及关联的 10 个 Node 实例变得符合垃圾回收条件。垃圾回收器可以立即清除上述实例，但并没要求它一定做到立即清除。

为 C# 服务的基础垃圾回收器可以通过在内存中移动对象来工作，而这种移动对于大多数 C# 开发人员是不可见的。对于那些通常满足于自动内存管理、但有时又需要精确控制或细微性能调整的开发人员，C# 提供了编写“不安全”代码的能力。这类代码可以直接处理指针类型和对象地址。但是，C# 要求程序员固定对象，暂时阻止垃圾回收器移动它们。

从开发人员和用户的角度，这种“不安全”代码功能实际上是一种“安全”的功能。在代码中必须用修饰符 unsafe 清楚地标记出不安全代码，以便开发人员不可能不经意地使用不安全语言功能，并且编译器和执行引擎协同工作，确保不安全代码无法假冒安全代码。这些限制将不安全代码的使用仅限于代码受到信任的情况。

示例

using System;

class Test

{

static void WriteLocations(byte[] arr) {

unsafe {

fixed (byte* pArray = arr) {

byte* pElem = pArray;

for (int i = 0; i < arr.Length; i++) {

byte value = *pElem;

Console.WriteLine("arr[{0}] at 0x{1:X} is {2}",

i, (uint)pElem, value);

pElem++;

}

}

}

}

static void Main() {

byte[] arr = new byte[] {1, 2, 3, 4, 5};

WriteLocations(arr);

}

}

显示了一个名为 WriteLocations 的方法。它含有一个不安全块，该块固定了一个数组实例，然后使用指针操作实现逐个地访问该数组的元素。每个数组元素的索引、值和位置被写入控制台。下面是一个可能的输出示例：

arr[0] at 0x8E0360 is 1

arr[1] at 0x8E0361 is 2

arr[2] at 0x8E0362 is 3

arr[3] at 0x8E0363 is 4

arr[4] at 0x8E0364 is 5

当然，确切的内存位置可能因应用程序的不同执行而异。


1.5表达式.227187

1.5 表达式

C# 包含一元运算符、二元运算符和一个三元运算符。下表概述了这些运算符，并将它们按优先级以从高到低的顺序列出：

章节 | 类别 | 运算符
第 7.5 节 | 基本 | x.y f(x) a[x] x++ x-- new typeof checked unchecked
第 7.6 节 | 一元 | + - ! ~ ++x --x (T)x
第 7.7 节 | 乘法 | * / %
第 7.7 节 | 加法 | + -
第 7.8 节 | 移位 | << >>
第 7.9 节 | 关系和类型检测 | < > <= >= is as
第 7.9 节 | 相等 | == !=
第 7.10 节 | 逻辑 AND | &
第 7.10 节 | 逻辑 XOR | ^
第 7.10 节 | 逻辑 OR | |
第 7.11 节 | 条件 AND | &&
第 7.11 节 | 条件 OR | ||
第 7.12 节 | 条件 | ?:
第 7.13 节 | 赋值 | = *= /= %= += -= <<= >>= &= ^= |=

当表达式包含多个运算符时，运算符的优先级控制各运算符的计算顺序。例如，表达式 x + y * z 按 x + (y * z) 计算，因为 * 运算符具有的优先级比 + 运算符高。

当操作数出现在具有相同优先级的两个运算符之间时，运算符的顺序关联性控制运算的执行顺序：

· 除了赋值运算符外，所有的二元运算符都向左顺序关联，意思是从左向右执行运算。例如，x + y + z 按 (x + y) + z 计算。

· 赋值运算符和条件运算符 (?:) 向右顺序关联，意思是从右向左执行运算。例如，x = y = z 按 x = (y = z) 计算。

优先级和顺序关联性都可以用括号控制。例如，x + y * z 先将 y 乘以 z 然后将结果与 x 相加，而 (x + y) * z 先将 x 与 y 相加，然后再将结果乘以 z。


1.6语句.240012

1.6 语句

C# 中的大多数语句都是直接从 C 和 C++ 借用的，但有一些值得注意的添加和修改。下表列出了可用的语句类型，并提供了每种类型的示例。

语句

示例

语句列表和块语句

static void Main() {
F();
G();
{
H();
I();
}
}

标记语句和 goto 语句

static void Main(string[] args) {
if (args.Length == 0)
goto done;
Console.WriteLine(args.Length);

done:
Console.WriteLine("Done");
}

局部常数声明

static void Main() {
const float pi = 3.14f;
const int r = 123;
Console.WriteLine(pi * r * r);
}

局部变量声明

static void Main() {
int a;
int b = 2, c = 3;
a = 1;
Console.WriteLine(a + b + c);
}

表达式语句

static int F(int a, int b) {
return a + b;
}

static void Main() {
F(1, 2); // Expression statement
}

if 语句

static void Main(string[] args) {
if (args.Length == 0)
Console.WriteLine("No args");
else
Console.WriteLine("Args");
}

switch 语句

static void Main(string[] args) {
switch (args.Length) {
case 0:
Console.WriteLine("No args");
break;
case 1:
Console.WriteLine("One arg ");
break;
default:
int n = args.Length;
Console.WriteLine("{0} args", n);
break;
}
}

while 语句

static void Main(string[] args) {
int i = 0;
while (i < args.Length) {
Console.WriteLine(args[i]);
i++;
}
}

do 语句

static void Main() {
string s;
do { s = Console.ReadLine(); }
while (s != "Exit");
}

for 语句

static void Main(string[] args) {
for (int i = 0; i < args.Length; i++)
Console.WriteLine(args[i]);
}

foreach 语句

static void Main(string[] args) {
foreach (string s in args)
Console.WriteLine(s);
}

break 语句

static void Main(string[] args) {
int i = 0;
while (true) {
if (i == args.Length)
break;
Console.WriteLine(args[i++]);
}
}

continue 语句

static void Main(string[] args) {
int i = 0;
while (true) {
Console.WriteLine(args[i++]);
if (i < args.Length)
continue;
break;
}
}

return 语句

static int F(int a, int b) {
return a + b;
}

static void Main() {
Console.WriteLine(F(1, 2));
return;
}

throw 语句和 try 语句

static int F(int a, int b) {
if (b == 0)
throw new Exception("Divide by zero");
return a / b;
}

static void Main() {
try {
Console.WriteLine(F(5, 0));
}
catch(Exception e) {
Console.WriteLine("Error");
}
}

checked 和 unchecked 语句

static void Main() {
int x = Int32.MaxValue;

Console.WriteLine(x + 1); // Overflow

checked {
Console.WriteLine(x + 1); // Exception
}

unchecked {
Console.WriteLine(x + 1); // Overflow
}
}

lock 语句

static void Main() {
A a = ...;
lock(a) {
a.P = a.P + 1;
}
}

using statements

static void Main() {
using (Resource r = new Resource()) {
r.F();
}
}


1.7类.255184

1.7 类

类声明定义新的引用类型。一个类可以从另一个类继承，并且可以实现多个接口。

类成员可以包括：常数、字段、方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数和嵌套类型声明。每个成员都有关联的可访问性（第 3.5 节），它控制能够访问该成员的程序文本区域。有五种可能的可访问性形式。下表概述了这些形式。

形式 | 直观含义
public | 不限制访问。
protected | 访问限于该成员所属的类或从该类派生来的类型。
internal | 访问限于此程序。
protected internal | 访问限于此程序或从该成员所属的类派生的类型。
private | 访问限于该成员所属的类型。

示例

using System;

class MyClass

{

public MyClass() {

Console.WriteLine("Instance constructor");

}

public MyClass(int value) {

MyField = value;

Console.WriteLine("Instance constructor");

}

~MyClass() {

Console.WriteLine("Destructor");

}

public const int MyConst = 12;

public int MyField = 34;

public void MyMethod(){

Console.WriteLine("MyClass.MyMethod");

}

public int MyProperty {

get {

return MyField;

}

set {

MyField = value;

}

}

public int this[int index] {

get {

return 0;

}

set {

Console.WriteLine("this[{0}] = {1}", index, value);

}

}

public event EventHandler MyEvent;

public static MyClass operator+(MyClass a, MyClass b) {

return new MyClass(a.MyField + b.MyField);

}

internal class MyNestedClass

{}

}

显示了一个类，它包含了具有各种可访问性的成员。示例

class Test

{

static void Main() {

// Instance constructor usage

MyClass a = new MyClass();

MyClass b = new MyClass(123);

// Constant usage

Console.WriteLine("MyConst = {0}", MyClass.MyConst);

// Field usage

a.MyField++;

Console.WriteLine("a.MyField = {0}", a.MyField);

// Method usage

a.MyMethod();

// Property usage

a.MyProperty++;

Console.WriteLine("a.MyProperty = {0}", a.MyProperty);

// Indexer usage

a[3] = a[1] = a[2];

Console.WriteLine("a[3] = {0}", a[3]);

// Event usage

a.MyEvent += new EventHandler(MyHandler);

// Overloaded operator usage

MyClass c = a + b;

}

static void MyHandler(object sender, EventArgs e) {

Console.WriteLine("Test.MyHandler");

}

internal class MyNestedClass

{}

}

显示了这些成员的用法。


1.7.1常数.265796

1.7.1 常数

常数是类成员，它表示一个常数值：一个可以在编译时计算的值。常数可以依赖于同一程序中的其他常数，但这种依赖性不能形成循环的依赖关系。第 7.15 节中定义了管理常数表达式的规则。示例

class Constants

{

public const int A = 1;

public const int B = A + 1;

}

显示了一个名为 Constants 的类，该类具有两个公共常数。

虽然我们把常数是当作静态成员，但常数的声明语句中不要求也不允许使用 static 修饰符。可以通过类访问常数，如下所示

using System;

class Test

{

static void Main() {

Console.WriteLine("{0}, {1}", Constants.A, Constants.B);

}

}

它分别输出 Constants.A 和 Constants.B 的值。


1.7.2字段.267697

1.7.2 字段

字段是一种表示与对象或类关联的变量的成员。示例

class Color

{

public static Color Red = new Color(0xFF, 0, 0);

public static Color Blue = new Color(0, 0xFF, 0);

public static Color Green = new Color(0, 0, 0xFF);

public static Color White = new Color(0xFF, 0xFF, 0xFF);

internal ushort redPart;

internal ushort bluePart;

internal ushort greenPart;

public Color(ushort red, ushort blue, ushort green) {

redPart = red;

bluePart = blue;

greenPart = green;

}

}

显示了一个 Color 类，它包含三个分别名为 redPart、bluePart 和 greenPart 的内部实例字段和四个分别名为 Red、Blue、Green 和 White 的静态字段。

以这种方式使用静态字段并不理想。该例中，这些字段在使用前的某个时间被初始化，此后，没任何手段可以阻止客户端更改这些字段的值。这样的修改在其他使用 Color 并假定这些字段的值不发生更改的程序中可能导致不可预知的错误。Readonly 字段可用来防止这类问题。给 readonly 字段的赋值只能作为声明的组成部分出现，或在同一类中的实例构造函数或静态构造函数中出现。静态 readonly 字段可以在静态构造函数中赋值，而非静态 readonly 字段可以在实例构造函数中赋值。因此，通过向静态字段添加 readonly 修饰符可使 Color 类具有附加的特性：

class Color

{

public static readonly Color Red = new Color(0xFF, 0, 0);

public static readonly Color Blue = new Color(0, 0xFF, 0);

public static readonly Color Green = new Color(0, 0, 0xFF);

public static readonly Color White = new Color(0xFF, 0xFF, 0xFF);

internal ushort redPart;

internal ushort bluePart;

internal ushort greenPart;

public Color(ushort red, ushort blue, ushort green) {

redPart = red;

bluePart = blue;

greenPart = green;

}

}


1.7.3方法.272523

1.7.3 方法

方法是一种用于实现可以由对象或类执行的计算或操作的成员。方法有一个形参表（可能是空的）、一个返回值（除非方法的返回类型为 void），它不是静态的，就是非静态的。通过类访问静态方法。通过类的实例访问非静态方法（也称为实例方法）。示例

using System;

public class Stack

{

public static Stack Clone(Stack s) {...}

public static Stack Flip(Stack s) {...}

public object Pop() {...}

public void Push(object o) {...}

public override string ToString() {...}

...

}

class Test

{

static void Main() {

Stack s = new Stack();

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

s.Push(i);

Stack flipped = Stack.Flip(s);

Stack cloned = Stack.Clone(s);

Console.WriteLine("Original stack: " + s.ToString());

Console.WriteLine("Flipped stack: " + flipped.ToString());

Console.WriteLine("Cloned stack: " + cloned.ToString());

}

}

显示了一个 Stack 类，该类具有若干静态方法（Clone 和 Flip）和若干实例方法（Push、Pop 和 ToString）。

方法可以被重载，这意味着可以定义多个方法，它们具有相同的名称，但每一个都具有唯一的“签名”。方法的签名由方法的名称及其形参的数目、修饰符和类型组成。方法的签名不包含返回类型。示例

using System;

class Test

{

static void F() {

Console.WriteLine("F()");

}

static void F(object o) {

Console.WriteLine("F(object)");

}

static void F(int value) {

Console.WriteLine("F(int)");

}

static void F(ref int value) {

Console.WriteLine("F(ref int)");

}

static void F(int a, int b) {

Console.WriteLine("F(int, int)");

}

static void F(int[] values) {

Console.WriteLine("F(int[])");

}

static void Main() {

F();

F(1);

int i = 10;

F(ref i);

F((object)1);

F(1, 2);

F(new int[] {1, 2, 3});

}

}

显示了一个具有若干名为 F 的方法的类。运行该程序，可见下列输出：

F()

F(int)

F(ref int)

F(object)

F(int, int)

F(int[])


1.7.4属性.279072

1.7.4 属性

属性是一种成员，可用它来访问对象或类的某个特性。属性的示例包括字符串的长度、字体的大小、窗口的标题、客户的名称，等等。属性是字段的自然扩展。属性和字段都是命名的成员，都具有相关的类型，且用于访问字段和属性的语法也相同。然而，与字段不同的是，属性不表示存储位置。相反，属性有访问器，访问器规定了当属性的值被读取或写入时要执行哪些语句。

属性用属性声明定义。属性声明的第一部分看上去与字段声明非常相似。第二部分包含一个 get 访问器和/或一个 set 访问器。在下面的示例中，Button 类定义一个 Caption 属性。

public class Button

{

private string caption;

public string Caption {

get {

return caption;

}

set {

caption = value;

Repaint();

}

}

}

可读取并写入的属性（如 Caption）同时包含 get 和 set 访问器。当读取属性值时调用 get 访问器；当写入属性值时则调用 set 访问器。在 set 访问器中，属性的新值是通过一个名为 value 的隐式参数来赋值的。

属性声明相对直接一些，但是属性的实际值在它们被使用时才可见。例如，读取和写入 Caption 属性的方式可以与读取和写入字段相同：

Button b = new Button();

b.Caption = "ABC"; // set; causes repaint

string s = b.Caption; // get

b.Caption += "DEF"; // get & set; causes repaint


1.7.5事件.282076

1.7.5 事件

事件是一种成员，对象或类能够通过它发送通知。类通过提供事件声明来定义事件。事件声明类似于字段声明，但它增加了 event 关键字和一组可选的事件访问器。用于声明事件的类型必须是某种委托类型。

一个委托类型的实例封装了一个或多个“可调用实体”。对于实例方法，可调用实体由一个实例和该实例的方法组成。对于静态方法，可调用实体仅由一个方法组成。当用一组合适的参数调用某个委托实例时，该委托实例所封装的每个可调用实体都会被逐个调用，且都使用给定的同一组参数。

在下面的示例中，

public delegate void EventHandler(object sender, System.EventArgs e);

public class Button

{

public event EventHandler Click;

public void Reset() {

Click = null;

}

}

Button 类定义一个 EventHandler 类型的 Click 事件。在 Button 类的内部，Click 成员如同一个 EventHandler 类型的私有字段。而在 Button 类的外部，Click 成员只能用在 += 和 -= 运算符的左侧。+= 运算符为 Click 事件添加一个事件处理程序，而 -= 运算符则为它移除一个事件处理程序。示例

using System;

public class Form1

{

public Form1() {

// Add Button1_Click as an event handler for Button1's Click event

Button1.Click += new EventHandler(Button1_Click);

}

Button Button1 = new Button();

void Button1_Click(object sender, EventArgs e) {

Console.WriteLine("Button1 was clicked!");

}

public void Disconnect() {

Button1.Click -= new EventHandler(Button1_Click);

}

}

显示一个 Form1 类，该类将 Button1_Click 添加为 Button1 的 Click 事件的事件处理程序。在 Disconnect 方法中，该事件处理程序被移除。

对于简单的事件声明，如

public event EventHandler Click;

编译器会自动地为它设置如何实现由运算符 += 和 -= 运算符指定的操作。

实施者如果想要对如何实现这些操作有更多的控制，可以自已编写 add 和 remove 访问器。例如，Button 类可以重写为：

public class Button

{

private EventHandler handler;

public event EventHandler Click {

add { handler += value; }

remove { handler -= value; }

}

}

此更改对客户端代码没有影响，却能使所设计的 Button 类更具灵活性。例如，Click 的事件处理程序可以不用一个字段来表示。


1.7.6运算符.288009

1.7.6 运算符

运算符是一种成员，它用来定义表达式运算符的含义，使其能应用于属于类的实例。可以定义三种运算符：一元运算符、二元运算符和转换运算符。所有运算符都必须声明为公共的和静态的。

下面的示例定义一个表示十进制数字（介于 0 和 9 之间的整数值）的 Digit 类型。

using System;

public struct Digit

{

byte value;

public Digit(byte value) {

if (value < 0 || value > 9) throw new ArgumentException();

this.value = value;

}

public Digit(int value): this((byte) value) {}

public static implicit operator byte(Digit d) {

return d.value;

}

public static explicit operator Digit(byte b) {

return new Digit(b);

}

public static Digit operator+(Digit a, Digit b) {

return new Digit(a.value + b.value);

}

public static Digit operator-(Digit a, Digit b) {

return new Digit(a.value - b.value);

}

public static bool operator==(Digit a, Digit b) {

return a.value == b.value;

}

public static bool operator!=(Digit a, Digit b) {

return a.value != b.value;

}

public override bool Equals(object value) {

if (value == null) return false;

if (GetType() == value.GetType()) return this == (Digit)value;

return false;

}

public override int GetHashCode() {

return value.GetHashCode();

}

public override string ToString() {

return value.ToString();

}

}

class Test

{

static void Main() {

Digit a = (Digit) 5;

Digit b = (Digit) 3;

Digit plus = a + b;

Digit minus = a – b;

bool equals = (a == b);

Console.WriteLine("{0} + {1} = {2}", a, b, plus);

Console.WriteLine("{0} - {1} = {2}", a, b, minus);

Console.WriteLine("{0} == {1} = {2}", a, b, equals);

}

}

此 Digit 类型定义下列运算符：

· 从 Digit 到 byte 的隐式转换运算符。

· 从 byte 到 Digit 的显式转换运算符。

· 将两个 Digit 值相加并返回一个 Digit 值的加法运算符。

· 从一个 Digit 值中减去另一个 Digit 值并返回一个 Digit 值的减法运算符。

· 相等 (==) 和不相等 (!=) 运算符，它们对两个 Digit 值进行比较。


1.7.7索引器.295363

1.7.7 索引器

索引器是一种成员，它使对象能够用与数组相同的方式进行索引。属性启用类似字段的访问，而索引器启用类似数组的访问。

例如，请看一下前面研究过的 Stack 类。该类的设计者可能想提供类似数组的访问，以便不必执行 Push 和 Pop 操作，就可以检查或改变堆栈上的各个项。也就是说，使 Stack 类既是链接表，又可像数组一样方便地对它进行访问。

索引器声明类似于属性声明，主要区别是索引器是无名称的（由于 this 被索引，因此在声明中使用的“名称”为 this），而且索引器包含索引参数。索引参数在方括号中提供。示例

using System;

public class Stack

{

private Node GetNode(int index) {

Node temp = first;

while (index > 0 && temp != null) {

temp = temp.Next;

index--;

}

if (index < 0 || temp == null)

throw new Exception("Index out of range."); return temp;

}

public object this[int index] {

get {

return GetNode(index).Value;

}

set {

GetNode(index).Value = value;

}

}

...

}

class Test

{

static void Main() {

Stack s = new Stack();

s.Push(1);

s.Push(2);

s.Push(3);

s[0] = 33; // Changes the top item from 3 to 33

s[1] = 22; // Changes the middle item from 2 to 22

s[2] = 11; // Changes the bottom item from 1 to 11

}

}

显示了 Stack 类的索引器。


1.7.8实例构造函数.299812

1.7.8 实例构造函数

实例构造函数是一种成员，用来实现初始化一个类的实例时所需的操作。

示例

using System;

class Point

{

public double x, y;

public Point() {

this.x = 0;

this.y = 0;

}

public Point(double x, double y) {

this.x = x;

this.y = y;

}

public static double Distance(Point a, Point b) {

double xdiff = a.x – b.x;

double ydiff = a.y – b.y;

return Math.Sqrt(xdiff * xdiff + ydiff * ydiff);

}

public override string ToString() {

return string.Format("({0}, {1})", x, y);

}

}

class Test

{

static void Main() {

Point a = new Point();

Point b = new Point(3, 4);

double d = Point.Distance(a, b);

Console.WriteLine("Distance from {0} to {1} is {2}", a, b, d);

}

}

显示了一个 Point 类，该类提供两个公共实例构造函数，一个不接受参数，而另一个接受两个 double 参数。

如果没有为某个类提供任何实例构造函数，则将自动提供一个不带参数的空实例构造函数。


1.7.9析构函数.303138

1.7.9 析构函数

析构函数是一种成员，用来实现析构一个类实例所需的操作。析构函数不能带参数，不能具有可访问性修饰符，也不能被显式调用。垃圾回收期间会自动调用所涉及实例的析构函数。

示例

using System;

class Point

{

public double x, y;

public Point(double x, double y) {

this.x = x;

this.y = y;

}

~Point() {

Console.WriteLine("Destructed {0}", this);

}

public override string ToString() {

return string.Format("({0}, {1})", x, y);

}

}

显示了一个包含析构函数的 Point 类。


1.7.10静态构造函数.304922

1.7.10 静态构造函数

静态构造函数是一种成员，用来实现初始化一个类所需的操作。静态构造函数不能带参数，不能具有可访问性修饰符，也不能被显式调用。类的静态构造函数是自动地被调用的。

示例

using Personnel.Data;

class Employee

{

private static DataSet ds;

static Employee() {

ds = new DataSet(...);

}

public string Name;

public decimal Salary;

...

}

显示了一个 Employee 类，该类含有一个静态构造函数，它初始化一个静态字段。


1.7.11继承.306357

1.7.11 继承

类支持单一继承，而类型 object 是所有类的最终基类。

前面的示例中涉及到的类全部隐式地从 object 派生。示例

using System;

class A

{

public void F() { Console.WriteLine("A.F"); }

}

显示了一个隐式地从 object 派生的类 A。示例

using System;

class B: A

{

public void G() { Console.WriteLine("B.G"); }

}

class Test

{

static void Main() {

B b = new B();

b.F(); // Inherited from A

b.G(); // Introduced in B

A a = b; // Treat a B as an A

a.F();

}

}

显示了一个从 A 派生的类 B。类 B 继承 A 的 F 方法，并引入一个它自己的 G 方法。

方法、属性和索引器可以是虚拟的，这意味着可以在派生类中重写它们的实现。示例

using System;

class A

{

public virtual void F() { Console.WriteLine("A.F"); }

}

class B: A

{

public override void F() {

base.F();

Console.WriteLine("B.F");

}

}

class Test

{

static void Main() {

B b = new B();

b.F();

A a = b;

a.F();

}

}

显示了一个包含虚方法 F 的类 A 和一个类 B（它重写了 F）。B 中的重写方法包含一个调用 base.F()，它调用 A 中被重写的方法。

一个类可以使用 abstract 修饰符来指示它自己是不完整的，只打算用作其他类的基类。这样的类称为抽象类。抽象类可以指定抽象成员，即非抽象派生类必须实现的成员。示例

using System;

abstract class A

{

public abstract void F();

}

class B: A

{

public override void F() { Console.WriteLine("B.F"); }

}

class Test

{

static void Main() {

B b = new B();

b.F();

A a = b;

a.F();

}

}

在抽象类 A 中引入了抽象方法 F。非抽象类 B 提供此方法的实现。


1.8结构.313205

1.8 结构

类与结构有很多相似之处：结构可以实现接口，并且可以具有与类相同的成员类型。然而，结构在几个重要方面不同于类：结构为值类型而不是引用类型，并且结构不支持继承。结构的值存储在“在堆栈上”或“内联”。细心的程序员有时可以通过聪明地使用结构来增强性能。

例如，将 Point 定义为结构而不是类在运行时可以节省很多内存空间。下面的程序创建并初始化一个 100 点的数组。对于作为类实现的 Point，出现了 101 个实例对象，因为数组需要一个，它的 100 个元素每个都需要一个。

class Point

{

public int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

class Test

{

static void Main() {

Point[] points = new Point[100];

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

points[i] = new Point(i, i*i);

}

}

如果将 Point 改为作为结构实现，如

struct Point

{

public int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

则只出现一个实例对象（用于数组的对象）。Point 实例在数组中内联分配。此优化可能会被误用。使用结构而不是类还会使应用程序运行得更慢或占用更多的内存，因为将结构实例作为值参数传递会导致创建结构的副本。


1.9接口.316407

1.9 接口

一个接口定义一个协定。实现接口的类或结构必须遵守其协定。接口可以包含方法、属性、索引器和事件作为成员。

示例

interface IExample

{

string this[int index] { get; set; }

event EventHandler E;

void F(int value);

string P { get; set; }

}

public delegate void EventHandler(object sender, EventArgs e);

显示了一个包含索引器、事件 E、方法 F 和属性 P 的接口。

接口可以使用多重继承。在下面的示例中，

interface IControl

{

void Paint();

}

interface ITextBox: IControl

{

void SetText(string text);

}

interface IListBox: IControl

{

void SetItems(string[] items);

}

interface IComboBox: ITextBox, IListBox {}

接口 IComboBox 同时从 ITextBox 和 IListBox 继承。

类和结构可以实现多个接口。在下面的示例中，

interface IDataBound

{

void Bind(Binder b);

}

public class EditBox: Control, IControl, IDataBound

{

public void Paint() {...}

public void Bind(Binder b) {...}

}

类 EditBox 从类 Control 派生，并且同时实现 IControl 和 IDataBound。

在前面的示例中，IControl 接口中的 Paint 方法和 IDataBound 接口中的 Bind 方法是使用 EditBox 类的公共成员实现的。C# 提供了另一种方式来实现这些方法，使得实现类避免将这些成员设置成公共的。这就是：接口成员可以用限定名来实现。例如，在 EditBox 类中将 Paint 方法命名为 IControl.Paint，将 Bind 方法命名为 IDataBound.Bind 方法。

public class EditBox: IControl, IDataBound

{

void IControl.Paint() {...}

void IDataBound.Bind(Binder b) {...}

}

用这种方式实现的接口成员称为显式接口成员，这是因为每个成员都显式地指定要实现的接口成员。显式接口成员只能通过接口来调用。例如，在 EditBox 中实现的 Paint 方法只能通过强制转换为 IControl 接口来调用。

class Test

{

static void Main() {

EditBox editbox = new EditBox();

editbox.Paint(); // error: no such method

IControl control = editbox;

control.Paint(); // calls EditBox's Paint implementation

}

}


1.10委托

1.10 委托

委托适用于那种在某些其他语言中需用函数指针来解决的情况（场合）。但是，与函数指针不同，委托是面向对象和类型安全的。

委托声明定义一个类，它是从 System.Delegate 类派生的类。委托实例封装了一个调用列表，该列表列出了一个或多个方法，每个方法称为一个可调用实体。对于实例方法，可调用实体由一个实例和该实例的方法组成。对于静态方法，可调用实体仅由一个方法组成。如果用一组合适的参数来调用一个委托实例，则该委托实例所封装的每个可调用实体都会被调用，并且用的都是上述的同一组参数。

委托实例的一个有趣且有用的属性是：它既不知道也不关心有关它所封装的方法所属的类的种种详情；对它来说最重要的是这些方法与该委托的类型兼容（第 15.1 节）。这使委托非常适合“匿名”调用。这是一个强大的功能。

定义和使用委托分三个步骤：声明、实例化和调用。委托用委托声明语法来声明。示例

delegate void SimpleDelegate();

声明一个名为 SimpleDelegate 的委托，它不带参数并且不返回任何结果。

在以下示例中：

class Test

{

static void F() {

System.Console.WriteLine("Test.F");

}

static void Main() {

SimpleDelegate d = new SimpleDelegate(F);

d();

}

}

创建一个 SimpleDelegate 实例，然后立即调用它。

这样做（为方法创建一个委托，然后立即通过这个委托调用该方法）并没有太大意义，因为直接调用方法更简单。当涉及它的匿名特性时，委托才真正显示出它的效用。示例

void MultiCall(SimpleDelegate d, int count) {

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

d();

}

}

显示了一个重复调用 SimpleDelegate 的 MultiCall 方法。MultiCall 方法不知道也不在乎 SimpleDelegate 的目标方法的类型、该方法所具有的可访问性或者该方法是否为静态。对它来说最重要的是目标方法与 SimpleDelegate 兼容（第 15.1 节）。


1.11枚举.326271

1.11 枚举

枚举类型声明为一组相关的符号常数定义了一个类型名称。枚举用于“多项选择”场合，就是程序运行时从编译时已经设定的固定数目的“选择”中做出决定。

示例

enum Color

{

Red,

Blue,

Green

}

class Shape

{

public void Fill(Color color) {

switch(color) {

case Color.Red:

...

break;

case Color.Blue:

...

break;

case Color.Green:

...

break;

default:

break;

}

}

}

显示了一个 Color 枚举和一个使用此枚举的方法。Fill 方法的签名清楚地表明可以用给定的颜色之一来填充形状。

使用枚举胜过使用整数常数（在没有枚举的语言中很常见），这是因为使用枚举使代码更具可读性和自归档。代码的自归档特性还使开发工具可以帮助代码编写和其他“设计器”活动。例如，对参数类型使用 Color 而不用 int 使聪明的代码编辑器能够建议 Color 值。


1.12命名空间和程序集.329557

1.12 命名空间和程序集

除了依赖于几个系统提供的类（如 System.Console），到目前为止介绍的程序都是独立存在的。但更常见的情况是：实际的应用程序由若干不同的部分组成，每个部分分别进行编译。例如，企业级应用程序可能依赖于若干不同的组件，其中包括某些内部开发的组件和某些从独立软件供应商处购买的组件。

命名空间和程序集有助于开发基于组件的系统。命名空间提供一个逻辑组织体系。命名空间既用作程序的“内部”组织体系，也用作“外部”组织体系（一种表示向其他程序公开程序元素的途径）。

程序集用于物理打包和部署。程序集可以包含类型、用于实现这些类型的可执行代码以及对其他程序集的引用。

有两种主要的程序集：应用程序和库。应用程序有一个主入口点，通常具有 .exe 文件扩展名；而库没有主入口点，通常具有 .dll 文件扩展名。

为了说明命名空间和程序集的使用，本节再次以前面介绍的“hello, world”程序为例，并将它分为两个部分：提供消息的类库和显示消息的控制台应用程序。

这个类库仅含一个名为 HelloMessage 的类。示例

// HelloLibrary.cs

namespace Microsoft.CSharp.Introduction

{

public class HelloMessage

{

public string Message {

get {

return "hello, world";

}

}

}

}

显示了名为 Microsoft.CSharp.Introduction 的命名空间中的 HelloMessage 类。HelloMessage 类提供一个名为 Message 的只读属性。命名空间可以嵌套，而声明

namespace Microsoft.CSharp.Introduction

{...}

仅是若干层命名空间嵌套的简写形式。若不简化，则应该像下面这样声明：

namespace Microsoft

{

namespace CSharp

{

namespace Introduction

{...}

}

}

将“hello, world”组件化的下一个步骤是编写使用 HelloMessage 类的控制台应用程序。可以使用此类的完全限定名 Microsoft.CSharp.Introduction.HelloMessage，但该名称太长，使用起来不方便。一种更方便的方法是使用“using 命名空间指令”，这样，使用相应的命名空间中的所有类型时就不必加限定名称。示例

// HelloApp.cs

using Microsoft.CSharp.Introduction;

class HelloApp

{

static void Main() {

HelloMessage m = new HelloMessage();

System.Console.WriteLine(m.Message);

}

}

显示了一个引用 Microsoft.CSharp.Introduction 命名空间的 using 命名空间指令。这样，HelloMessage 就成为 Microsoft.CSharp.Introduction.HelloMessage 的简写形式。

C# 还允许定义和使用别名。using 别名指令定义类型的别名。当两个类库之间发生名称冲突时，或者当使用大得多的命名空间中的少数类型时，这类别名很有用。示例

using MessageSource = Microsoft.CSharp.Introduction.HelloMessage;

显示了一个 using 别名指令，该指令将 MessageSource 定义为 HelloMessage 类的别名。

我们已编写的代码可以编译为包含类 HelloMessage 的类库和包含类 HelloApp 的应用程序。此编译步骤的细节可能因使用的编译器或工具而异。使用 Visual Studio .NET 中提供的命令行编译器时，用如下所列的命令：

csc /target:library HelloLibrary.cs

csc /reference:HelloLibrary.dll HelloApp.cs

它产生一个名为 HelloLibrary.dll 的类库和一个名为 HelloApp.exe 的应用程序。


1.13版本控制.336426

1.13 版本控制

版本控制是一个过程，它以兼容的方式对组件进行不断的改进。如果依赖于早期版本的代码重新编译后可以适用于新版本，则组件的新版本与早期版本源代码兼容。相反，如果依赖于早期版本的应用程序不用重新编译即可适用于新版本，则组件的新版本为二进制兼容。

大多数语言根本不支持二进制兼容性，而且许多语言对促进源代码兼容性所做甚少。实际上，某些语言所含的缺陷使得用它开发出来的组件在不断的改进过程中，一般至少会使依赖于该组件的某些客户端代码失效。

例如，请看一个发布名为 Base 的类的基类作者的情况。在第一个版本中，Base 不包含任何 F 方法。名为 Derived 的组件从 Base 派生，并引入 F。此 Derived 类与它所依赖的 Base 类一起发布给客户，客户又部署到众多客户端和服务器。

// Author A

namespace A

{

public class Base // version 1

{

}

}

// Author B

namespace B

{

class Derived: A.Base

{

public virtual void F() {

System.Console.WriteLine("Derived.F");

}

}

}

从这时起，开始产生版本问题。Base 的作者生成了一个拥有自己的 F 方法的新版本。

// Author A

namespace A

{

public class Base // version 2

{

public virtual void F() { // added in version 2

System.Console.WriteLine("Base.F");

}

}

}

这个新版本的 Base 在源代码和二进制方面都应该与初始版本兼容。（如果仅添加一个新的方法就会产兼容性问题，则基类可能就永远不能改进了。）不幸的是，Base 中的新 F 使 Derived 的 F 的含义不清。Derived 是指重写 Base 的 F 吗？这看上去不太可能，因为编译 Derived 时，Base 还没有 F！此外，如果 Derived 的 F 的确是重写了 Base 的 F，则它必须遵守由 Base 指定的协定（此协定在编写 Derived 时尚未指定）！在某些情况下，这是不可能的。例如，Base 的 F 可能要求它的重写始终调用基方法。Derived 的 F 不可能遵守这样的协定。

C# 通过要求开发人员明确声明它们的意图来解决此版本问题。在原始的代码示例中，代码很清楚，因为 Base 甚至没有 F。很明显，由于不存在名为 F 的基方法，因此 Derived 的 F 是一个新方法而不是对基方法的一个重写。

如果 Base 添加 F 并发布新版本，则在 Derived 的二进制版本中对“Derived 的 F”是什么仍很清楚：它语义上与重写无关，不应将它视为重写。

然而，当重新编译 Derived 时，其含义仍是不清楚的：Derived 的作者可能打算让它的 F 重写 Base 的 F 或者隐藏它。由于意图不清，编译器生成一个警告，并在默认情况下使 Derived 的 F 隐藏 Base 的 F。此编译过程造成了语义上的二义性（与重新编译 Derived 前相比较）。生成的警告提醒 Derived 的作者 Base 中存在 F 方法。

如果 Derived 的 F 在语义上与 Base 的 F 无关，则 Derived 的作者可以通过在 F 的声明中使用 new 关键字来表示此意图（并且有效地关闭警告）。

// Author A

namespace A

{

public class Base // version 2

{

public virtual void F() { // added in version 2

System.Console.WriteLine("Base.F");

}

}

}

// Author B

namespace B

{

class Derived: A.Base // version 2a: new

{

new public virtual void F() {

System.Console.WriteLine("Derived.F");

}

}

}

另一方面，Derived 的作者经过进一步考虑，可能决定 Derived 的 F 应重写 Base 的 F。可以通过使用 override 关键字来指定此意图，如下所示。

// Author A

namespace A

{

public class Base // version 2

{

public virtual void F() { // added in version 2

System.Console.WriteLine("Base.F");

}

}

}

// Author B

namespace B

{

class Derived: A.Base // version 2b: override

{

public override void F() {

base.F();

System.Console.WriteLine("Derived.F");

}

}

}

Derived 的作者还有另一种选择，就是更改 F 的名称，从而彻底避免名称冲突。尽管此更改会破坏 Derived 的源代码和二进制兼容性，但这种兼容性的重要性因方案而异。如果不向其他程序公开 Derived，则更改 F 的名称很可能是个好主意，因为这会提高程序的可读性，就是说 F 的含义不会再有任何混淆。


1.14属性.348491

1.14 属性

C# 是一种命令式语言，但像所有命令式语言一样，它具有某些声明性元素。例如，通过将类中的方法声明为 public、protected、internal、protected internal 或 private，指定它的可访问性。C# 使此功能一般化，以便程序员可以创造出一种新的声明性信息，将此声明性信息附加到各种程序实体，并在运行时检索此声明性信息。程序通过定义和使用属性（第 17 节）来描述这类额外的声明性信息。

例如，一个框架也许会定义一个可放置在程序元素（如类和方法）上的 HelpAttribute 属性，使开发人员能够提供从程序元素到其文档的映射。示例

using System;

[AttributeUsage(AttributeTargets.All)]

public class HelpAttribute: Attribute

{

public HelpAttribute(string url) {

this.url = url;

}

public string Topic = null;

private string url;

public string Url {

get { return url; }

}

}

定义了一个名为 HelpAttribute 的属性类，它具有一个定位参数 (string url) 和一个命名参数 (string Topic)。正如第 17.1 节中所解释的，该属性可以通过它的完整名称 HelpAttribute 或通过它的隐式简称 Help 引用。定位参数由该属性类的公共实例构造函数的形参定义，命名参数则由属性类的公共非静态读写字段和对应的属性定义。

示例

[Help("http://www.microsoft.com/.../Class1.htm")]

public class Class1

{

[Help("http://www.microsoft.com/.../Class1.htm", Topic = "F")]

public void F() {}

}

显示 Help 属性的几种用法。

在运行时可以利用反射支持检索给定程序元素的属性信息。示例

using System;

class Test

{

static void Main() {

Type type = typeof(Class1);

object[] arr = type.GetCustomAttributes(typeof(HelpAttribute), true);

if (arr.Length == 0)

Console.WriteLine("Class1 has no Help attribute.");

else {

HelpAttribute ha = (HelpAttribute) arr[0];

Console.WriteLine("Url = {0}, Topic = {1}", ha.Url, ha.Topic);

}

}

}

检查 Class1 是否具有 Help 属性，如果具有该属性，则写出关联的 Topic 和 Url 值。


2.词法结构.353801

Powered By Gavin http://www.CSharpWeb.com

2. 词法结构

本章定义 C# 程序的词法结构。


2.1程序.354126

2.1 程序

C# 程序由一个或多个源文件（正规地应称为编译单元（第 9.1 节））组成。源文件是有序的 Unicode 字符序列。源文件与文件系统中的文件通常具有一对一的对应关系，但这种对应关系不是必需的。为实现最大的可移植性，建议这些文件在文件系统中应按 UTF-8 规范编码。

从概念上讲，程序的编译分三个步骤：

· 转换，这一步将用特定字符指令系统和编码方案编写的文件转换为 Unicode 字符序列。

· 词法分析，这一步将 Unicode 输入字符流转换为标记流。

· 句法分析，这一步将标记流转换为可执行代码。


2.2语法.355587

Powered By Gavin http://www.CSharpWeb.com

2.2 语法

本规范采用两种文法（原理）来表示 C# 编程语言的语法。词法文法 (Lexical Grammar)（第 2.2.2 节）规定怎样将 Unicode 字符结合起来构成行结束符、空白、注释、标记和预处理指令等。句法文法 (Syntactic Grammar)（第 2.2.3 节）规定如何把那些由词法文法产生的标记再结合起来构成 C# 程序。


2.2.1文法表示法.356155

2.2.1 文法表示法

词法文法和句法文法用文法产生式来表示。每个文法产生式定义一个非结束符号和它可能的扩展（由非结束符或结束符组成的序列）。在文法产生式中，非结束符号显示为斜体，而结束符号显示为等宽字体。

文法产生式的第一行是该产生式所定义的非结束符号的名称，后跟一个冒号。每个后续的缩进行列出一个可能的扩展，它是以非结束符或结束符组成的序列的形式给出的。例如，产生式：

while-statement:（while 语句：）

while ( boolean-expression ) embedded-statement（while ( 布尔表达式 ) 嵌入语句）

定义了一个 while 语句，它是这样构成的：由标记 while 开始，后跟标记“(”、布尔表达式、标记“)”和嵌入的语句。

当有不止一个可能的非结束符号扩展时，列出这些可能的扩展（每个扩展单独占一行）。例如，产生式：

statement-list:（语句列表：）

statement（语句）
statement-list statement（语句列表 语句）

定义一个语句列表，它或仅含有一个语句，或由一个语句列表和随后跟着的一个语句组成。换言之，定义是递归的，语句列表由一个或多个语句组成。

一个符号若以下标“可选”作其后缀，就表明该符号是可选的。产生式：

block:（块：）

{ statement-list opt }（{ 语句列表可选 }）

是以下产生式的简短形式：

block:（块：）

{ }
{ statement-list }（{ 语句列表 }）

它定义了一个块，此块由一个用“{”和“}”标记括起来的可选语句列表组成。

可选项通常在单独的行上列出，但是当有许多可选项时，可以在单行上给定的扩展列表之前加上短语“下列之一”。这只是在单独一行上列出每个可选项的简短形式。例如，产生式：

real-type-suffix: one of（实数类型后缀：下列之一）

F f D d M m

是以下产生式的简短形式：

real-type-suffix:（实数类型后缀：）

F
f
D
d
M
m


2.2.2词法文法.361637

Powered By Gavin http://www.CSharpWeb.com

2.2.2 词法文法

第 2.3 节、第 2.4 节和第 2.5 节介绍 C# 的词法文法。词法文法的结束符号为 Unicode 字符集的字符，并且词法文法指定如何组合字符以构成标记（第 2.4 节）、空白（第 2.3.2 节）、注释（第 2.3.3 节）和预处理指令（第 2.5 节）。

C# 程序中的每个源文件都必须符合词法文法的输入产生式（第 2.3 节）。


2.2.3句法文法.362440

Powered By Gavin http://www.CSharpWeb.com

2.2.3 句法文法

本章后面的章节和附录介绍 C# 的句法文法。句法文法的结束符号是由词法文法定义的标记，句法文法指定如何组合这些标记以构成 C# 程序。

C# 程序中的每个源文件都必须符合句法文法的编译单元产生式（第 9.1 节）。


2.3词法分析.363060

2.3 词法分析

输入产生式定义 C# 源文件的词法结构。C# 程序中的每个源文件都必须符合此词法文法产生式。

input:（输入：）

input-section opt（输入节可选）

input-section:（输入节：）

input-section-part（输入节部分）
input-section input-section-part（输入节 输入节部分）

input-section-part:（输入节部分：）

input-elements opt new-line（输入元素可选 新行）
pp-directive（pp 指令）

input-elements:（输入元素：）

input-element（输入元素）
input-elements input-element（输入元素 输入元素）

input-element:（输入元素：）

whitespace（空白）
comment（注释）
token（标记）

C# 源文件的词法结构由五个基本元素组成：行结束符（第 2.3.1 节）、空白（第 2.3.2 节）、注释（第 2.3.3 节）、标记（第 2.4 节）和预处理指令（第 2.5 节）。在这些基本元素中，只有标记在 C# 程序的句法文法（第 2.2.3 节）中具有重要意义。

对 C# 源文件的词法处理就是将文件缩减成标记序列，该序列然后即成为句法分析的输入。行结束符、空白和注释可用于分隔标记，预处理指令可导致跳过源文件中的某些节，除此之外这些词法元素对 C# 程序的句法结构没有任何影响。

当有若干词法文法产生式与源文件中的一个字符序列匹配时，词法处理总是构成尽可能最长的词法元素。例如，字符序列 // 按单行注释的开头处理，这是因为该词法元素比一个 / 标记要长。


2.3.1行结束符.367068

2.3.1 行结束符

行结束符将 C# 源文件的字符划分为行。

new-line:（新行：）

回车符 (U+000D)
换行符 (U+000A)
回车符 (U+000D) 后跟换行符 (U+000A)
行分隔符 (U+2028)
段落分隔符 (U+2029)

为了与添加文件尾标记的源代码编辑工具兼容，并能够以正确结束的行序列的形式查看源文件，下列转换按顺序应用到 C# 程序中的每个源文件：

· 如果源文件的最后一个字符为 Control-Z 字符 (U+001A)，则删除此字符。

· 如果源文件非空并且源文件的最后一个字符不是回车符 (U+000D)、换行符 (U+000A)、行分隔符 (U+2028) 或段落分隔符 (U+2029)，则将在源文件的结尾添加一个回车符 (U+000D)。


2.3.2空白.369148

2.3.2 空白

空白被定义为任何含 Unicode 类 Zs 的字符（包括空白字符）以及水平制表符、垂直制表符和换页符。

whitespace:（空白：）

任何含 Unicode 类 Zs 的字符
水平制表符 (U+0009)
垂直制表符 (U+000B)
换页符 (U+000C)


2.3.3注释.370022

2.3.3 注释

支持两种形式的注释：单行注释和带分隔符的注释。单行注释以字符 // 开头并延续到源行的结尾。带分隔符的注释以字符 /* 开头，以字符 */ 结束。带分隔符的注释可以跨多行。

comment:（注释：）

single-line-comment（单行注释）
delimited-comment（带分隔符的注释）

single-line-comment:（单行注释：）

// input-characters opt（// 输入字符可选）

input-characters:（输入字符：）

input-character（输入字符）
input-characters input-character（输入字符 输入字符）

input-character:（输入字符：）

除换行符外的任何 Unicode 字符

new-line-character:（换行符：）

回车符 (U+000D)
换行符 (U+000A)
行分隔符 (U+2028)
段落分隔符 (U+2029)

delimited-comment:（带分隔符的注释：）

/* delimited-comment-characters opt */（/* 带分隔符的注释字符可选 */）

delimited-comment-characters:（带分隔符的注释字符：）

delimited-comment-character（带分隔符的注释字符）
delimited-comment-characters delimited-comment-character（带分隔符的注释字符 带分隔符的注释字符）

delimited-comment-character:（带分隔符的注释字符：）

not-asterisk（非星号）
* not-slash（* 非斜杠）

not-asterisk:（非星号：）

除 * 外的任何 Unicode 字符

not-slash:（非斜杠：）

除 / 外的任何 Unicode 字符

注释不嵌套。字符序列 /* 和 */ 在 // 注释中没有任何特殊含义，字符序列 // 和 /* 在带分隔符的注释中没有任何特殊含义。

在字符和字符串文本内不处理注释。

示例

/* Hello, world program

This program writes "hello, world" to the console

*/

class Hello

{

static void Main() {

System.Console.WriteLine("hello, world");

}

}

包含一个带分隔符的注释。

示例

// Hello, world program

// This program writes "hello, world" to the console

//

class Hello // any name will do for this class

{

static void Main() { // this method must be named "Main"

System.Console.WriteLine("hello, world");

}

}

显示了若干单行注释。


2.4标记.377436

2.4 标记

有若干种标记：标识符、关键字、文本、运算符和标点符号。空白和注释不是标记，但它们可充当标记的分隔符。

token:（标记：）

identifier（标识符）
keyword（关键字）
integer-literal（整数）
real-literal（实数）
character-literal（字符）
string-literal（字符串）
operator-or-punctuator（运算符或标点）


2.4.1Unicode字符转义序列.378891

2.4.1 Unicode 字符转义序列

Unicode 字符转义序列表示一个 Unicode 字符。Unicode 字符转义序列在标识符（第 2.4.2 节）、字符（第 2.4.4.4 节）和规则字符串（第 2.4.4.5 节）中处理。不在其他任何位置处理 Unicode 字符转义（例如，在构成运算符、标点符号或关键字时）。

unicode-escape-sequence:（unicode 转义序列：）

\u hex-digit hex-digit hex-digit hex-digit（\u 十六进制数字 十六进制数字 十六进制数字 十六进制数字）
\U hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit（\U 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字）

Unicode 转义序列表示由“\u”或“\U”字符后面的十六进制数字构成的单个 Unicode 字符。由于 C# 在字符和字符串值中使用 Unicode 代码点的 16 位编码，因此从 U+10000 到 U+10FFFF 的 Unicode 字符不能在字符中使用，在字符串中则用一个 Unicode 代理项对来表示。不支持代码数据点在 0x10FFFF 以上的 Unicode 字符。

不执行多次转换。例如，字符串“\u005Cu005C”等同于“\u005C”，而不是“\”。Unicode 值 \u005C 是字符“\”。

示例

class Class1

{

static void Test(bool \u0066) {

char c = '\u0066';

if (\u0066)

System.Console.WriteLine(c.ToString());

}

}

表明了 \u0066（它是字母“f”的转义序列）的一些用法。该程序等效于

class Class1

{

static void Test(bool f) {

char c = 'f';

if (f)

System.Console.WriteLine(c.ToString());

}

}


2.4.2标识符.386347

2.4.2 标识符

本节给出的标识符规则完全符合 Unicode 标准附件 15 推荐的规则，但以下情况除外：允许将下划线用作初始字符（这是 C 编程语言的传统），允许在标识符中使用 Unicode 转义序列，以及允许“@”字符作为前缀以使关键字能够用作标识符。

identifier:（标识符：）

available-identifier（可用的标识符）
@ identifier-or-keyword（@ 标识符或关键字）

available-identifier:（可用的标识符：）

不是“关键字”的“标识符或关键字”

identifier-or-keyword:（标识符或关键字：）

identifier-start-character identifier-part-characters opt（标识符开始字符 标识符部分字符可选）

identifier-start-character:（标识符开始字符：）

letter-character（字母字符）
_（下划线字符 U+005F）

identifier-part-characters:（标识符部分字符：）

identifier-part-character（标识符部分字符）
identifier-part-characters identifier-part-character（标识符部分字符 标识符部分字符）

identifier-part-character:（标识符部分字符：）

letter-character（字母字符）
decimal-digit-character（十进制数字字符）
connecting-character（连接字符）
combining-character（组合字符）
formatting-character（格式设置字符）

letter-character:（字母字符：）

类 Lu、Ll、Lt、Lm、Lo 或 Nl 的 Unicode 字符
表示类 Lu、Ll、Lt、Lm、Lo 或 Nl 的字符的 unicode 转义序列

combining-character:（组合字符：）

类 Mn 或 Mc 的 Unicode 字符
表示类 Mn 或 Mc 的字符的 unicode 转义序列

decimal-digit-character:（十进制数字字符：）

类 Nd 的 Unicode 字符
表示类 Nd 的字符的 unicode 转义序列

connecting-character:（连接字符：）

类 Pc 的 Unicode 字符
表示类 Pc 的字符的 unicode 转义序列

formatting-character:（格式设置字符：）

类 Cf 的 Unicode 字符
表示类 Cf 的字符的 unicode 转义序列

有关上面提到的 Unicode 字符类的信息，请参见《Unicode 标准 3.0 版》的第 4.5 节。

有效标识符的例子包括“identifier1”、“_identifier2”和“@if”。

符合规范的程序中的标识符必须符合由“Unicode 标准化格式 C”（按“Unicode 标准附录 15”中的定义）定义的规范格式。当遇到非“标准化格式 C”格式的标识符时，怎样处理它可由 C# 的具体实现确定，但是不要求诊断。

使用前缀“@”可以将关键字用作标识符，这在与其他编程语言建立接口时很有用。字符 @ 并不是标识符的实际组成部分，因此在其他语言中可能将此标识符视为不带前缀的正常标识符。带 @ 前缀的标识符称作逐字标识符。允许将 @ 前缀用于非关键字的标识符，但是（从代码书写样式的意义上）强烈建议不要这样做。

示例：

class @class

{

public static void @static(bool @bool) {

if (@bool)

System.Console.WriteLine("true");

else

System.Console.WriteLine("false");

}

}

class Class1

{

static void M() {

cl\u0061ss.st\u0061tic(true);

}

}

定义一个名为“class”的类，该类具有一个名为“static”的静态方法，此方法带一个名为“bool”的参数。请注意，由于在关键字中不允许使用 Unicode 转义符，因此标记“cl\u0061ss”是标识符，与“@class”标识符相同。

两个标识符如果在按顺序实施了下列转换后相同，则被视为相同：

· 如果使用了前缀“@”，移除它。

· 将每个“unicode 转义序列”转换为它的对应 Unicode 字符。

· 移除所有“格式化字符”。

包含两个连续下划线字符 (U+005F) 的标识符被保留供具体实现使用。例如，一个实现可以设置它自己的以两个下划线开头的扩展关键字。


2.4.3关键字.396878

2.4.3 关键字

关键字是类似标识符的保留的字符序列，不能用作标识符（以 @ 字符开头时除外）。

keyword: one of（关键字：下列之一）

abstract | as | base | bool | break | byte | case
catch | char | checked | class | const | continue | decimal
default | delegate | do | double | else | enum | event
explicit | extern | false | finally | fixed | float | for
foreach | goto | if | implicit | in | int | interface
internal | is | lock | long | namespace | new | null
object | operator | out | override | params | private | protected
public | readonly | ref | return | sbyte | sealed | short
sizeof | stackalloc | static | string | struct | switch | this
throw | true | try | typeof | uint | ulong | unchecked
unsafe | ushort | using | virtual | void | volatile | while

在文法中的某些位置，特定的标识符有特殊的含义，但不是关键字。例如，在属性声明中，“get”和“set”标识符有特殊的含义（第 10.6.2 节）。在这些位置决不允许使用 get 或 set 以外的标识符，因此这种用法不会与将这些字用作标识符冲突。


2.4.4文本.416150

2.4.4 文本

文本是值的源代码表示形式。

literal:（文本：）

boolean-literal（布尔值）
integer-literal（整数）
real-literal（实数）
character-literal（字符）
string-literal（字符串）
null-literal（空值）


2.4.4.1布尔值.417353

2.4.4.1 布尔值

有两种布尔值：true 和 false。

boolean-literal:（布尔值：）

true
false

布尔值的类型为 bool。


2.4.4.2整数.418195

2.4.4.2 整数

整数用于编写类型 int、uint、long 和 ulong 的值。整数具有两种可能的形式：十进制和十六进制。

integer-literal:（整数：）

decimal-integer-literal（十进制整数）
hexadecimal-integer-literal（十六进制整数）

decimal-integer-literal:（十进制整数：）

decimal-digits integer-type-suffix opt（十进制数字 整数类型后缀可选）

decimal-digits:（十进制数字：）

decimal-digit（十进制数字）
decimal-digits decimal-digit（十进制数字 十进制数字）

decimal-digit: one of（十进制数字：下列之一）

0 1 2 3 4 5 6 7 8 9

integer-type-suffix: one of（整数类型后缀：下列之一）

U u L l UL Ul uL ul LU Lu lU lu

hexadecimal-integer-literal:（十六进制整数：）

0x hex-digits integer-type-suffix opt（0x 十六进制数字 整型后缀可选）
0X hex-digits integer-type-suffix opt（0X 十六进制数字 整型后缀可选）

hex-digits:（十六进制数字：）

hex-digit（十六进制数字）
hex-digits hex-digit（十六进制数字 十六进制数字）

hex-digit: one of（十六进制数字：下列之一）

0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

一个整数的类型按下面这样确定：

· 如果该整数没带后缀，则它属于以下所列的类型中第一个能够表示其值的那个类型：int、uint、long、ulong。

· 如果该整数带有后缀 U 或 u，则它属于以下所列的类型中第一个能够表示其值的那个类型：uint、ulong。

· 如果该整数带有后缀 L 或 l，则它属于以下所列的类型中第一个能够表示其值的那个类型：long、ulong。

· 如果该整数带有后缀 UL、Ul、uL、ul、LU、Lu、lU 或 lu，则它属于 ulong 类型。

如果整数表示的值超出了 ulong 类型的范围，则将发生编译时错误。

作为书写的风格（样式），建议在书写类型 long 的文本时使用“L”而不是“l”，因为字母“l”容易与数字“1”混淆。

为允许尽可能小的 int 和 long 值写为十进制整数，有下面两个规则：

· 当具有值 2147483648 (231) 且没有“整型后缀”的一个十进制整数作为标记紧接在一元负运算符标记（第 7.6.2 节）后出现时，结果为具有值 ?2147483648 (?231) 的 int 类型常数。在所有其他情况下，这样的十进制整数属于 uint 类型。

· 当具有值 9223372036854775808 (263) 的一个十进制整数（没带整型后缀，或带有整型后缀 L 或 l）作为一个标记紧跟在一个一元负运算符标记（第 7.6.2 节）后出现时，结果是具有值 ?9223372036854775808 (?263) 的 long 类型的常数。在所有其他情况下，这样的十进制整数属于 ulong 类型。


2.4.4.3实数.426960

2.4.4.3 实数

实数用于编写类型 float、double 和 decimal 的值。

real-literal:（实数：）

decimal-digits . decimal-digits exponent-part opt real-type-suffix opt（十进制数字 . 十进制数字 指数部分可选 实数类型后缀可选）
. decimal-digits exponent-part opt real-type-suffix opt（. 十进制数字 指数部分可选 实数类型后缀可选）
decimal-digits exponent-part real-type-suffix opt（十进制数字 指数部分 实数类型后缀可选）
decimal-digits real-type-suffix（十进制数字 实数类型后缀）

exponent-part:（指数部分：）

e sign opt decimal-digits（e 符号可选 十进制数字）
E sign opt decimal-digits（E 符号可选 十进制数字）

sign: one of（符号：下列之一）

+ -

real-type-suffix: one of（实数类型后缀：下列之一）

F f D d M m

如果未指定实数类型后缀，则实数的类型为 double。否则，实数类型后缀确定实数的类型，如下所示：

· 以 F 或 f 为后缀的实数的类型为 float。例如，实数 1f、1.5f、1e10f 和 123.456F 的类型都是 float。

· 以 D 或 d 为后缀的实数的类型为 double。例如，实数 1d、1.5d、1e10d 以及 123.456D 的类型都是 double。

· 以 M 或 m 为后缀的实数的类型为 decimal。例如，实数 1m、1.5m、1e10m 和 123.456M 的类型都是 decimal。此实数通过取精确值转换为 decimal 值，如果有必要，用银行家舍入法（第 4.1.7 节）舍入为最接近的可表示值。保留该实数的所有小数位数，除非值被舍入或者值为零（在后一种情况中，符号和小数位数为 0）。因此，实数 2.900m 经分析后将形成这样的一个小数：符号为 0、系数为 2900，小数位数为 3。

如果一个给定的实数不能用指定的类型表示，则会发生编译时错误。

使用 IEEE“就近舍入”模式确定类型 float 或 double 的实数的值。

注意在实数中，小数点后必须始终是十进制数字。例如，1.3F 是实数，但 1.F 不是。


2.4.4.4字符.435902

2.4.4.4 字符

字符表示单个字符，通常由置于引号中的一个字符组成，如 'a'。

character-literal:（字符：）

' character '（' 字符 '）

character:（字符：）

single-character（单字符）
simple-escape-sequence（简单转义序列）
hexadecimal-escape-sequence（十六进制转义序列）
unicode-escape-sequence（unicode 转义序列）

single-character:（单字符：）

除 ' (U+0027)、\ (U+005C) 和换行符外的任何字符

simple-escape-sequence: one of（简单转义序列：下列之一）

\' \" \\ \0 \a \b \f \n \r \t \v

hexadecimal-escape-sequence:（十六进制转义序列：）

\x hex-digit hex-digit opt hex-digit opt hex-digit opt（\x 十六进制数字 十六进制数字可选 十六进制数字可选 十六进制数字可选）

“字符”中跟在反斜杠字符 (\) 后面的字符必须是下列字符之一：'、"、\、0、a、b、f、n、r、t、u、U、x、v。否则将发生编译时错误。

十六进制转义序列表示单个 Unicode 字符，它的值由“\x”后接十六进制数组成。

如果一个字符表示的值大于 U+FFFF，则将发生编译时错误。

字符中的 Unicode 字符转义序列（第 2.4.1 节）必须在 U+0000 到 U+FFFF 的范围内。

一个简单转义序列表示一个 Unicode 字符编码，详见下表。

转义序列 | 字符名称 | Unicode 编码
\' | 单引号 | 0x0027
\" | 双引号 | 0x0022
\\ | 反斜杠 | 0x005C
\0 | 空 | 0x0000
\a | 警报 | 0x0007
\b | 退格符 | 0x0008
\f | 换页符 | 0x000C
\n | 换行符 | 0x000A
\r | 回车 | 0x000D
\t | 水平制表符 | 0x0009
\v | 垂直制表符 | 0x000B

“字符”的类型为 char。


2.4.4.5字符串.450259

2.4.4.5 字符串

C# 支持两种形式的字符串：规则字符串和逐字字符串。

规则字符串由包含在双引号中的零个或多个字符组成（如 "hello"），并且可以包含简单转义序列（如表示制表符的 \t）、十六进制转义序列和 Unicode 转义序列。

逐字字符串由 @ 字符后跟开始的双引号字符、零个或多个字符以及结束的双引号字符组成。一个简单的示例就是 @"hello"。在逐字字符串中，分隔符之间的字符逐字解释，唯一的例外是“引号转义序列”。具体说来，在逐字字符串中不处理简单转义序列以及十六进制和 Unicode 转义序列。逐字字符串可以跨多行。

string-literal:（字符串：）

regular-string-literal（规则字符串）
verbatim-string-literal（逐字的字符串）

regular-string-literal:（规则字符串：）

" regular-string-literal-characters opt "（" 规则字符串字符可选 "）

regular-string-literal-characters:（规则字符串字符：）

regular-string-literal-character（规则字符串字符）
regular-string-literal-characters regular-string-literal-character（规则字符串字符 规则字符串字符）

regular-string-literal-character:（规则字符串字符：）

single-regular-string-literal-character（单个规则字符串字符）
simple-escape-sequence（简单转义序列）
hexadecimal-escape-sequence（十六进制转义序列）
unicode-escape-sequence（unicode 转义序列）

single-regular-string-literal-character:（单个规则字符串字符：）

除 " (U+0022)、\ (U+005C) 和换行符外的任何字符

verbatim-string-literal:（逐字的字符串：）

@" verbatim -string-literal-characters opt "（@" 逐字的字符串字符可选 "）

verbatim-string-literal-characters:（逐字的字符串字符：）

verbatim-string-literal-character（逐字的字符串字符）
verbatim-string-literal-characters verbatim-string-literal-character（逐字的字符串字符 逐字的字符串字符）

verbatim-string-literal-character:（逐字的字符串字符：）

single-verbatim-string-literal-character（单个逐字的字符串字符）
quote-escape-sequence（引号转义序列）

single-verbatim-string-literal-character:（单个逐字的字符串字符：）

除 " 外的任何字符

quote-escape-sequence:（引号转义序列：）

""

规则字符串字符中跟在反斜杠字符 (\) 后面的字符必须是下列字符之一：'、"、\、0、a、b、f、n、r、t、u、U、x、v。否则将发生编译时错误。

示例

string a = "hello, world"; // hello, world

string b = @"hello, world"; // hello, world

string c = "hello \t world"; // hello world

string d = @"hello \t world"; // hello \t world

string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me

string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me

string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt

string h = @"\\server\share\file.txt"; // \\server\share\file.txt

string i = "one\r\ntwo\r\nthree";

string j = @"one

two

three";

显示了各种不同的字符串。最后一个字符串 j 是跨多行的逐字字符串。引号之间的字符（包括空白，如换行符等）也逐字符保留。

由于十六进制转义序列可以包含数目可变的十六进制数字，因此字符串 "\x123" 只包含一个具有十六进制值 123 的字符。若要创建一个包含具有十六进制值 12 的字符，后跟一个字符 3 的字符串，可以改写为 "\x00123" 或 "\x12" + "3"。

“字符串”的类型为 string。

每个字符串不一定产生新的字符串实例。当根据字符串相等运算符（第 7.9.7 节）确认为相等的两个或更多个字符串出现在同一个程序集中时，这些字符串引用相同的字符串实例。例如，

class Test

{

static void Main() {

object a = "hello";

object b = "hello";

System.Console.WriteLine(a == b);

}

}

产生的输出为 True，这是因为两个字符串引用相同的字符串实例。


2.4.4.6空文本.461422

Powered By Gavin http://www.CSharpWeb.com

2.4.4.6 空文本

null-literal:（空文本：）

null

“空文本”的类型为 null 类型。


2.4.5运算符和标点符号.461987

2.4.5 运算符和标点符号

有若干种运算符和标点符号。运算符在表达式中用于描述涉及一个或多个操作数的运算。例如，表达式 a + b 使用 + 运算符添加两个操作数 a 和 b。标点符号用于分组和分隔。

operator-or-punctuator（运算符或标点符号）：下列之一

{ } [ ] ( ) . , : ;

+ - * / % & | ^ ! ~

= < > ? ++ -- && || << >>

== != <= >= += -= *= /= %= &=

|= ^= <<= >>= ->


2.5预处理指令.468073

2.5 预处理指令

预处理指令提供按条件跳过源文件中的节、报告错误和警告条件，以及描绘源代码的不同区域的能力。使用术语“预处理指令”只是为了与 C 和 C++ 编程语言保持一致。在 C# 中没有单独的预处理步骤；预处理指令按词法分析阶段的一部分处理。

pp-directive:（pp 指令：）

pp-declaration（pp 声明）
pp-conditional（pp 条件）
pp-line（pp 行）
pp-diagnostic（pp 诊断）
pp-region（pp 区域）

下面是可用的预处理指令：

· #define 和 #undef，分别用于定义和取消定义条件编译符号（第 2.5.3 节）。

· #if、#elif、#else 和 #endif，用于按条件跳过源代码中的节（第 2.5.4 节）。

· #line，用于控制行号（在发布错误和警告信息时使用）（第 2.5.7 节）。

· #error 和 #warning，分别用于发出错误和警告（第 2.5.5 节）。

· #region 和 #endregion，用于显式标记源代码中的节（第 2.5.6 节）。

预处理指令总是占用源代码中的单独一行，并且总是以 # 字符和预处理指令名称开头。# 字符的前面以及 # 字符与指令名称之间可以出现空白符。

包含 #define、#undef、#if、#elif、#else、#endif 或 #line 指令的源代码行可以用单行注释结束。在包含预处理指令的源行上不允许使用带分隔符的注释（/* */ 样式的注释）。

预处理指令既不是标记，也不是 C# 句法文法的组成部分。但是，可以用预处理指令包含或排除标记序列，并且可以以这种方式影响 C# 程序的含义。例如，编译后，程序：

#define A

#undef B

class C

{

#if A

void F() {}

#else

void G() {}

#endif

#if B

void H() {}

#else

void I() {}

#endif

}

产生与下面的程序完全相同的标记序列：

class C

{

void F() {}

void I() {}

}

因此，尽管上述两个程序在词法分析中完全不同，但它们在句法分析中是相同的。


2.5.1条件编译符号.474156

2.5.1 条件编译符号

#if、#elif、#else 和 #endif 指令提供的条件编译功能是通过预处理表达式（第 2.5.1 节）和条件编译符号来控制的。

conditional-symbol:（条件符号：）

除 true 和 false 外的任何标识符或关键字

条件编译符号有两种可能的状态：已定义的或未定义的。在源文件词法处理开始时，条件编译符号除非已由外部机制（如命令行编译器选项）显式定义，否则是未定义的。当处理 #define 指令时，在指令中指定的条件编译符号在那个源文件中成为已定义的。此后，该符号就一直保持已定义的状态，直到处理一条关于同一符号的 #undef 指令，或者到达源文件的结尾。这意味着一个源文件中的 #define 和 #undef 指令对同一程序中的其他源文件没有任何影响。

当在预处理表达式中引用时，已定义的条件编译符号具有布尔值 true，未定义的条件编译符号具有布尔值 false。不要求在预处理表达式中引用条件编译符号之前显式声明它们。相反，未声明的符号只是未定义的，因此具有值 false。

条件编译符号的命名空间与 C# 程序中的所有其他命名实体截然不同。只能在 #define 和 #undef 指令以及预处理表达式中引用条件编译符号。


2.5.2预处理表达式.476104

2.5.2 预处理表达式

预处理表达式可以出现在 #if 和 #elif 指令中。在预处理表达式中允许使用 !、==、!=、&& 和 || 运算符，并且可以使用括号进行分组。

pp-expression:（pp 表达式：）

whitespace opt pp-or-expression whitespace opt（空白可选 pp 或表达式 空白可选）

pp-or-expression:（pp 或表达式：）

pp-and-expression（pp 与表达式）
pp-or-expression whitespace opt || whitespace opt pp-and-expression（pp 或表达式 空白可选 || 空白可选 pp 与表达式）

pp-and-expression:（pp 与表达式：）

pp-equality-expression（pp 相等表达式）
pp-and-expression whitespace opt && whitespace opt pp-equality-expression（pp 与表达式 空白可选 && 空白可选 pp 相等表达式）

pp-equality-expression:（pp 相等表达式：）

pp-unary-expression（pp 一元表达式）
pp-equality-expression whitespace opt == whitespace opt pp-unary-expression（pp 相等表达式 空白可选 == 空白可选 pp 一元表达式）
pp-equality-expression whitespace opt != whitespace opt pp-unary-expression（pp 相等表达式 空白可选 != 空白可选 pp 一元表达式）

pp-unary-expression:（pp 一元表达式：）

pp-primary-expression（pp 基本表达式）
! whitespace opt pp-unary-expression（! 空白可选 pp 一元表达式）

pp-primary-expression:（pp 基本表达式：）

true
false
conditional-symbol（条件符号）
( whitespace opt pp-expression whitespace opt )（(空白可选 pp 表达式 空白可选 )）

当在预处理表达式中引用时，已定义的条件编译符号具有布尔值 true，未定义的条件编译符号具有布尔值 false。

预处理表达式的计算总是产生一个布尔值。预处理表达式的计算规则与常数表达式（第 7.15 节）相同，唯一的例外是：在这里，唯一可引用的用户定义实体是条件编译符号。


2.5.3声明指令.487166

2.5.3 声明指令

声明指令用于定义或取消定义条件编译符号。

pp-declaration:（pp 声明：）

whitespace opt # whitespace opt define whitespace conditional-symbol pp-new-line（空白可选 # 空白可选 define 空白 条件符号 pp 新行）
whitespace opt # whitespace opt undef whitespace conditional-symbol pp-new-line（空白可选 # 空白可选 undef 空白 条件符号 pp 新行）

pp-new-line:（pp 新行：）

whitespace opt single-line-comment opt new-line（空白可选 单行注释可选 新行）

对 #define 指令的处理使给定的条件编译符号成为已定义的（从跟在指令后面的源代码行开始）。类似地，对 #undef 指令的处理使给定的条件编译符号成为未定义的（从跟在指令后面的源代码行开始）。

源文件中的任何 #define 和 #undef 指令都必须出现在源文件中第一个“标记”（第 2.4 节）的前面，否则将发生编译时错误。直观地讲，#define 和 #undef 指令必须位于源文件中所有“实代码”的前面。

示例：

#define Enterprise

#if Professional || Enterprise

#define Advanced

#endif

namespace Megacorp.Data

{

#if Advanced

class PivotTable {...}

#endif

}

是有效的，这是因为 #define 指令位于源文件中第一个标记（namespace 关键字）的前面。

下面的示例产生编译时错误，因为 #define 指令在实代码后面出现：

#define A

namespace N

{

#define B

#if B

class Class1 {}

#endif

}

#define 指令可用于重复地定义一个已定义的条件编译符号，而不必对该符号插入任何 #undef。下面的示例定义一个条件编译符号 A，然后再次定义它。

#define A

#define A

#undef 指令可用于取消定义一个本来已经是未定义的条件编译符号。下面的示例定义一个条件编译符号 A，然后两次取消定义该符号；第二个 #undef 没有作用但仍是有效的。

#define A

#undef A

#undef A


2.5.4条件编译指令.495438

2.5.4 条件编译指令

条件编译指令用于按条件包含或排除源文件中的某些部分。

pp-conditional:（pp 条件：）

pp-if-section pp-elif-sections opt pp-else-section opt pp-endif（pp if 节 pp elif 节可选 pp else 节可选 pp endif）

pp-if-section:（pp if 节：）

whitespace opt # whitespace opt if whitespace pp-expression pp-new-line conditional-section opt（空白可选 # 空白可选 if 空白 pp 表达式 pp 新行 条件节可选）

pp-elif-sections:（pp elif 节：）

pp-elif-section（pp elif 节）
pp-elif-sections pp-elif-section（pp elif 节 pp elif 节）

pp-elif-section:（pp elif 节：）

whitespace opt # whitespace opt elif whitespace pp-expression pp-new-line conditional-section opt（空白可选 # 空白可选 elif 空白 pp 表达式 pp 新行 条件节可选）

pp-else-section:（pp-else 节：）

whitespace opt # whitespace opt else pp-new-line conditional-section opt（空白可选 # 空白可选 else pp 新行 条件节可选）

pp-endif:

whitespace opt # whitespace opt endif pp-new-line（空白可选 # 空白可选 endif pp 新行）

conditional-section:（条件节：）

input-section（输入节）
skipped-section（跳过节）

skipped-section:（跳过节：）

skipped-section-part（跳过节部分）
skipped-section skipped-section-part（跳过节 跳过节部分）

skipped-section-part:（跳过节部分：）

skipped-characters opt new-line（跳过字符可选 新行）
pp-directive（pp 指令）

skipped-characters:（跳过字符：）

whitespace opt not-number-sign input-characters opt（空白可选 非数字符号 输入字符可选）

not-number-sign:（非数字符号：）

除 # 外的任何输入字符

按照语法的规定，条件编译指令必须写成集的形式，集的组成依次为：一个 #if 指令、一个或多个 #elif 指令（或没有）、一个或多个 #else 指令（或没有）和一个 #endif 指令。指令之间是源代码的条件节。每节代码直接位于它前面的那个指令控制。条件节本身可以包含嵌套的条件编译指令，前提是这些指令构成完整的指令集。

“pp 条件”最多只能选择一个它所包含的“条件节”去做通常的词法处理：

· 按顺序计算 #if 和 #elif 指令的“pp 表达式”直到获得值 true。如果表达式的结果为 true，则选择对应指令的“条件节”。

· 如果所有“pp 表达式”的结果都为 false 并且存在 #else 指令，则选择 #else 指令的“条件节”。

· 否则不选择任何“条件节”。

选定的“条件节”（若有）按正常的“输入节”处理：节中包含的源代码必须符合词法文法；从节中的源代码生成标记；节中的预处理指令具有规定的效果。

剩余的“条件节”（若有）按“跳过节”处理：除了预处理指令，节中的源代码不必一定要符合词法文法；不从节中的源代码生成任何词法标记；节中的预处理指令必须在词法上正确，但不另外处理。在按“跳过节”处理的“条件节”中，任何嵌套的“条件节”（包含在嵌套的 #if...#endif 和 #region...#endregion 构造中）也按“跳过节”处理。

下面的示例阐释如何嵌套条件编译指令：

#define Debug // Debugging on

#undef Trace // Tracing off

class PurchaseTransaction

{

void Commit() {

#if Debug

CheckConsistency();

#if Trace

WriteToLog(this.ToString());

#endif

#endif

CommitHelper();

}

}

除预处理指令外，跳过的源代码与词法分析无关。例如，尽管在 #else 节中有未结束的注释，但下面的示例仍然有效：

#define Debug // Debugging on

class PurchaseTransaction

{

void Commit() {

#if Debug

CheckConsistency();

#else

/* Do something else

#endif

}

}

但请注意，即使是在源代码的跳过节中，也要求预处理指令在词法上正确。

当预处理指令出现在多行输入元素的内部时，不作为预处理指令处理。例如，程序：

class Hello

{

static void Main() {

System.Console.WriteLine(@"hello,

#if Debug

world

#else

Nebraska

#endif

");

}

}

输出结果为：

hello,

#if Debug

world

#else

Nebraska

#endif

在特殊的情况下，如何处理预处理指令集可能取决于 pp 表达式的计算。示例：

#if X

/*

#else

/* */ class Q { }

#endif

总是产生同样的标记流 (class Q { })，不管是否定义了 X。如果定义了 X，由于多行注释的缘故，只处理 #if 和 #endif 指令。如果未定义 X，则这三个指令（#if、#else、#endif）是指令集的组成部分。


2.5.5诊断指令.516515

2.5.5 诊断指令

诊断指令用于显式生成错误信息和警告消息，这些信息的报告方式与其他编译时错误和警告相同。

pp-diagnostic:（pp 诊断：）

whitespace opt # whitespace opt error pp-message（空白可选 # 空白可选 error pp 消息）
whitespace opt # whitespace opt warning pp-message（空白可选 # 空白可选 warning pp 消息）

pp-message:（pp 消息：）

new-line（新行）
whitespace input-characters opt new-line（空白 输入字符可选 新行）

示例：

#warning Code review needed before check-in

#if Debug && Retail

#error A build can't be both debug and retail

#endif

class Test {...}

总是产生一个警告（“Code review needed before check-in”），如果同时定义条件符号 Debug 和 Retail，则产生一个编译时错误（“A build can't be both debug and retail”）。注意 pp-message（pp 消息）可以包含任意文本；具体说来，它可以包含格式不正确的标记，比如“can't”中的单引号就是这样。


2.5.6区域指令.521323

2.5.6 区域指令

区域指令用于显式标记源代码的区域。

pp-region:（pp 区域：）

pp-start-region conditional-section opt pp-end-region（pp 开始区域 条件节可选 pp 结束区域）

pp-start-region:（pp 开始区域：）

whitespace opt # whitespace opt region pp-message（空白可选 # 空白可选 region pp 消息）

pp-end-region:（pp 结束区域：）

whitespace opt # whitespace opt endregion pp-message（空白可选 # 空白可选 endregion pp 消息）

区域不具有任何附加的语义含义；区域旨在由程序员或自动工具用来标记源代码中的节。#region 或 #endregion 指令中指定的消息同样不具有任何语义含义；它只是用于标识区域。匹配的 #region 和 #endregion 指令可能具有不同的“pp 消息”。

区域的词法处理：

#region

...

#endregion

与以下形式的条件编译指令的词法处理完全对应：

#if true

...

#endif


2.5.7行指令.526191

2.5.7 行指令

行指令可用于改变编译器在输出（如警告和错误）中报告的行号和源文件名称。

行指令最通用于从某些其他文本输入生成 C# 源代码的元编程工具。

pp-line:（pp 行：）

whitespace opt # whitespace opt line whitespace line-indicator pp-new-line（空白可选 # 空白可选 line 空白 行指示符 pp 新行）

line-indicator:（行指示符：）

decimal-digits whitespace file-name（十进制数字 空白 文件名）

decimal-digits（十进制数字）

default

hidden

file-name:（文件名：）

" file-name-characters "（" 文件名字符 "）

file-name-characters:（文件名字符：）

file-name-character（文件名字符）
file-name-characters file-name-character（文件名字符 文件名字符）

file-name-character:（文件名字符：）

除 " 外的任何输入字符

当不存在 #line 指令时，编译器在它的输出中报告真实的行号和源文件名称。#line 指令最通用于从某些其他文本输入生成 C# 源代码的元编程工具。当处理的 #line 指令包含不是 default 的行指示符时，编译器将该指令“后面”的行视为具有给定的行号（如果指定了，还包括文件名）。

#line default 指令消除前面所有 #line 指令的影响。编译器报告后续行的真实行信息，就像尚未处理任何 #line 指令一样。

#line hidden 指令对错误信息中报告的文件号和行号无效，但对源代码级调试确实有效。调试时，#line hidden 指令和后面的 #line 指令（不是 #line hidden）之间的所有行都没有行号信息。在调试器中逐句执行代码时，将全部跳过这些行。

注意，file-name（文件名）与常规字符串的不同之处在于不处理转义字符；“\”字符在 file-name（文件名）中只是表示一个普通的反斜杆字符。


3.基本概念.532885

Powered By Gavin http://www.CSharpWeb.com

3. 基本概念

本章定义理解后面的章节所需的基本概念。


3.1应用程序启动.533196

3.1 应用程序启动

具有入口点的程序集称为应用程序。应用程序运行时，将创建新的应用程序域。同一台计算机上可能会同时运行着同一个应用程序的若干个实例，此时，每一个实例都拥有各自的应用程序域。

应用程序域通过作为应用程序状态的容器，使应用程序得以隔离。应用程序域作为应用程序中和它使用的类库中所定义的类型的容器和边界。同一个类型若被加载到不同的应用程序域中就成为各自独立的客体，由它们在各自应用程序域中产生的实例亦不可直接共享。例如，对于这些类型的静态变量，每个应用程序域都有自己的副本，并且这些类型的静态构造函数在每个应用程序域中也要（最多）运行一次。关于如何处理程序域的创建和销毁，各实现可以按具体情况确定自己的策略或机制。

当执行环境调用指定的方法（称为程序的入口点）时发生应用程序启动。此入口点方法总是被命名为 Main，可以具有下列签名之一：

static void Main() {...}

static void Main(string[] args) {...}

static int Main() {...}

static int Main(string[] args) {...}

如上所示，入口点可以选择返回一个 int 值。此返回值用于应用程序终止（第 3.2 节）。

入口点可以包含一个形参（可选）。该参数可以具有任意名称，但参数的类型必须为 string[]。如果存在形参，执行环境会创建并传递一个包含命令行参数的 string[] 参数，这些命令行参数是在启动应用程序时指定的。string[] 参数永远不能为 null，但如果没有指定命令行参数，它的长度可以为零。

由于 C# 支持方法重载，因此类或结构可以包含某个方法的多个定义（前提是每个定义有不同的签名）。但在一个程序内，没有任何类或结构可以包含一个以上的名为 Main 的方法，因为 Main 的定义限定它只能被用作应用程序的入口点。允许使用 Main 的其他重载版本，前提是它们具有一个以上的参数，或者它们的唯一参数的类型不是 string[]。

应用程序可由多个类或结构组成。在这些类或结构中，可能会有若干个拥有自己的 Main 方法，因为 Main 的定义限定它只能被用作应用程序的入口点。这种情况下，必须利用某种外部机制（如命令行编译器的选项）来指定将哪一个 Main 方法用作入口点。

在 C# 中，每个方法都必须定义为类或结构的成员。通常，方法的已声明可访问性（第 3.5.1 节）由其声明中指定的访问修饰符（第 10.2.3 节）确定。同样，类型的已声明可访问性由其声明中指定的访问修饰符确定。为了使给定类型的给定方法可以被调用，类型和成员都必须是可访问的。然而，应用程序入口点是一种特殊情况。具体说来，执行环境可以访问应用程序的入口点，无论它本身的可访问性和封闭它的类型的可访问性是如何在声明语句中设置的。

在所有其他方面，入口点方法的行为与非入口点方法类似。


3.2应用程序终止.536508

3.2 应用程序终止

应用程序终止将控制返回给执行环境。

如果应用程序的入口点方法的返回类型为 int，则返回的值用作应用程序的终止状态代码。此代码的用途是允许与执行环境进行关于应用程序运行状态（成功或失败）的通信。

如果入口点方法的返回类型为 void，那么，到达了终止该方法的右大括号 (})，或者执行不带表达式的 return 语句，将产生终止状态代码 0。

在应用程序终止之前，将调用其中还没有被垃圾回收的所有对象的析构函数，除非这类清理功能已被设置为停止使用（例如，通过调用库方法 GC.SuppressFinalize）。


3.3声明.537563

3.3 声明

C# 程序中的声明定义程序的构成元素。C# 程序是用命名空间（第 9 节）组织起来的，一个命名空间可以包含类型声明和嵌套的命名空间声明。类型声明（第 9.5 节）用于定义类（第 10 节）、结构（第 11 节）、接口（第 13 节）、枚举（第 14 节）和委托（第 15 节）。在一个类型声明中可以使用哪些类型作为其成员，取决于该类型声明的形式。例如，类声明可以包含常数声明（第 10.3 节）、字段声明（第 10.4 节）、方法声明（第 10.5 节）、属性声明（第 10.6 节）、事件声明（第 10.7 节）、索引器声明（第 10.8 节）、运算符声明（第 10.9 节）、实例构造函数声明（第 10.10 节）、静态构造函数声明（第 10.11 节）、析构函数声明（第 10.12 节）和嵌套的类型声明。

一个声明所定义的名称属于它自已所属的那个“声明空间”。除非是重载成员（第 3.6 节），否则，在同一个声明空间下若有两个以上的声明语句声明了具有相同名称的成员，就会产生编译时错误。同一个声明空间内绝不能包含不同类型的同名成员。例如，声明空间绝不能包含同名的字段和方法。

有若干种不同类型的声明空间，如下所述。

· 在程序的所有源文件中，一个声明语句（称为“命名空间成员声明”）若没有被置于任何一个“命名空间声明”下，它就属于一个组合声明空间（称为“全局声明空间”）。

· 在程序的所有源文件中，一个“命名空间成员声明”若在“命名空间声明”中具有相同的完全限定的命名空间名称，它就属于一个组合声明空间。

· 每个类、结构或接口声明创建一个新的声明空间。新的声明空间名称是通过“类成员声明”、“结构成员声明”或“接口成员声明”引入的。除了重载实例构造函数声明和静态构造函数声明外，类或结构成员声明不能引入与该类或结构同名的成员。类、结构或接口允许声明重载方法和索引器。另外，类或结构允许重载实例构造函数和运算符的声明。例如，类、结构或接口可以包含多个同名的方法声明，前提是这些方法声明的签名（第 3.6 节）不同。注意，基类与类的声明空间无关，基接口与接口的声明空间无关。因此，允许在派生类或接口内声明与所继承的成员同名的成员。我们说这类成员“隐藏”了那些被它们所继承的成员。

· 每个枚举声明创建一个新的声明空间。名称通过“枚举成员声明”引入此声明空间。

· 每个“块”或“switch 块”为局部变量和常量创建一个不同的声明空间。名称通过“局部变量声明”和“局部常量声明”引入此声明空间。如果块是实例构造函数、方法或运算符声明的体，或是索引器声明的 get 或 set 访问器，这些声明中声明的参数是块的局部变量声明空间的成员。块的局部变量声明空间包含任何嵌套块。因此，在嵌套块中不可能声明与封闭它的块中的局部变量同名的局部变量。

· 每个“块”或“switch 块”都为标签创建一个单独的声明空间。名称通过标记语句引入此声明空间，通过 goto 语句被引用。块的“标签声明空间”包含任何嵌套块。因此，在嵌套块中不可能声明与封闭它的块中的标签同名的标签。

声明名称的文本顺序通常不重要。具体说来，声明和使用命名空间、常数、方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数和类型时，文本顺序并不重要。在下列情况下声明顺序非常重要：

· 字段声明和局部变量声明的声明顺序确定其初始值设定项（若有的话）的执行顺序。

· 在使用局部变量前必须先定义它们（第 3.7 节）。

· 当省略“常数表达式”值时，枚举成员声明（第 14.3 节）的声明顺序非常重要。

命名空间的声明空间是“开放式的”，两个具有相同的完全限定名的命名空间声明属于相同的声明空间。例如

namespace Megacorp.Data

{

class Customer

{

...

}

}

namespace Megacorp.Data

{

class Order

{

...

}

}

以上两个命名空间声明属于相同的声明空间，在本示例中声明两个具有完全限定名 Megacorp.Data.Customer 和 Megacorp.Data.Order 的类。由于两个声明共同构成同一个声明空间，因此如果每个声明中都包含一个同名类的声明，则将导致编译时错误。

正如前面详细说明的，块的声明空间包括所有嵌套块。因此，在下面的示例中，F 和 G 方法导致编译时错误，因为名称 i 是在外部块中声明的，不能在内部块中重新声明。但方法 H 和 I 都是有效的，因为这两个 i 是在单独的非嵌套块中声明的。

class A

{

void F() {

int i = 0;

if (true) {

int i = 1;

}

}

void G() {

if (true) {

int i = 0;

}

int i = 1;

}

void H() {

if (true) {

int i = 0;

}

if (true) {

int i = 1;

}

}

void I() {

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

H();

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

H();

}

}


3.4成员.547266

Powered By Gavin http://www.CSharpWeb.com

3.4 成员

命名空间和类型具有“成员”。通常可以通过限定名来访问实体的成员。限定名以对实体的引用开头，后跟一个“.”标记，再接成员的名称。

类型的成员或者是在该类型中声明的，或者是从该类型的基类“继承”的。当类型从基类继承时，基类的所有成员（实例构造函数、析构函数和静态构造函数除外）都成为派生类型的成员。基类中关于成员的可访问性的声明不能控制该成员是否可继承：继承性适用于任何成员，只要它们不是实例构造函数、静态构造函数或析构函数。然而，在派生类中可能不能访问已被继承的成员，原因或者是因为其已声明可访问性（第 3.5.1 节），或者是因为它已被类型本身中的声明所隐藏（第 3.7.1.2 节）。


3.4.1命名空间成员.548190

Powered By Gavin http://www.CSharpWeb.com

3.4.1 命名空间成员

命名空间和类型若没有封闭它的命名空间，则属于“全局命名空间”的成员。这直接对应于全局声明空间中声明的名称。

在某命名空间中声明的命名空间和类型是该命名空间的成员。这直接对应于该命名空间的声明空间中声明的名称。

命名空间没有访问限制。不可能把命名空间设置成私有的、受保护的或内部的，命名空间名称始终是可公开访问的。


3.4.2结构成员.548848

3.4.2 结构成员

结构的成员是在结构中声明的成员和从类 object 继承的成员。

简单类型的成员直接对应于结构类型的成员，此简单类型正是该结构的化名：

· sbyte 的成员是 System.SByte 结构的成员。

· byte 的成员是 System.Byte 结构的成员。

· short 的成员是 System.Int16 结构的成员。

· ushort 的成员是 System.UInt16 结构的成员。

· int 的成员是 System.Int32 结构的成员。

· uint 的成员是 System.UInt32 结构的成员。

· long 的成员是 System.Int64 结构的成员。

· ulong 的成员是 System.UInt64 结构的成员。

· char 的成员是 System.Char 结构的成员。

· float 的成员是 System.Single 结构的成员。

· double 的成员是 System.Double 结构的成员。

· decimal 的成员是 System.Decimal 结构的成员。

· bool 的成员是 System.Boolean 结构的成员。


3.4.3枚举成员.553180

Powered By Gavin http://www.CSharpWeb.com

3.4.3 枚举成员

枚举成员是在枚举中声明的常数和从类 System.Enum 继承的成员。


3.4.4类成员.553597

3.4.4 类成员

类的成员是在类中声明的成员和从该类的基类（没有基类的 object 类除外）继承的成员。从基类继承的成员包括基类的常数、字段、方法、属性、事件、索引器、运算符和类型，但不包括基类的实例构造函数、析构函数和静态构造函数。基类成员被是否继承与它们的可访问性无关。

类声明可以包含以下对象的声明：常数、字段、方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数和类型。

object 和 string 的成员直接对应于它们所化名的类类型的成员：

· object 的成员是 System.Object 类的成员。

· string 的成员是 System.String 类的成员。


3.4.5接口成员.555060

Powered By Gavin http://www.CSharpWeb.com

3.4.5 接口成员

接口的成员是在接口中和该接口的所有基接口中声明的成员，以及从 object 类继承的成员。


3.4.6数组成员.555501

Powered By Gavin http://www.CSharpWeb.com

3.4.6 数组成员

数组的成员是从类 System.Array 继承的成员。


3.4.7委托成员.555877

Powered By Gavin http://www.CSharpWeb.com

3.4.7 委托成员

委托的成员是从类 System.Delegate 继承的成员。


3.5成员访问.556254

Powered By Gavin http://www.CSharpWeb.com

3.5 成员访问

成员的声明可用于控制对该成员的访问。成员的可访问性是由该成员的可访问性声明（第 3.5.1 节）和直接包含它的那个类型的可访问性（若它存在）结合起来确定的。

如果允许对特定成员进行访问，则称该成员是“可访问的”。相反，如果不允许对特定成员进行访问，则称该成员是“不可访问的”。当导致访问发生的源代码的文本位置包括在某成员的可访问域（第 3.5.2 节）中时，允许对该成员进行访问。


3.5.1已声明可访问性.556946

3.5.1 已声明可访问性

成员的“已声明可访问性”可以是下列之一：

· Public，选择它的方法是在成员声明中包括 public 修饰符。public 的直观含义是“访问不受限制”。

· Protected，选择它的方法是在成员声明中包括 protected 修饰符。protected 的直观含义是“访问范围限定于它所属的类或从该类派生的类型”。

· Internal，选择它的方法是在成员声明中包括 internal 修饰符。internal 的直观含义是“访问范围限定于此程序”。

· Protected internal（意为受保护或内部的），选择它的方法是在成员声明中包括 protected 和 internal 修饰符。protected internal 的直观含义是“访问范围限定于此程序或那些由它所属的类派生的类型”。

· Private，选择它的方法是在成员声明中包括 private 修饰符。private 的直观含义是“访问范围限定于它所属的类型”。

声明一个成员时所能选择的已声明可访问性的类型，依赖于该成员声明出现处的上下文。此外，当成员声明不包含任何访问修饰符时，声明发生处的上下文会为该成员选择一个默认的已声明可访问性。

· 命名空间隐式地具有 public 已声明可访问性。在命名空间声明中不允许使用访问修饰符。

· 编译单元或命名空间中声明的类型可以具有 public 或 internal 已声明可访问性，默认的已声明可访问性为 internal。

· 类成员可具有五种已声明可访问性中的任何一种，默认为 private 已声明可访问性。（请注意，声明为类成员的类型可具有五种已声明可访问性中的任何一种，而声明为命名空间成员的类型只能具有 public 或 internal 已声明可访问性。）

· 结构成员可以具有 public、internal 或 private 已声明可访问性并默认为 private 已声明可访问性，这是因为结构是隐式地密封的。结构的成员若是在此结构中声明的（也就是说，不是由该结构从它的基类中继承的）不能具有 protected 或 protected internal 已声明可访问性。（请注意，声明为结构成员的类型可具有 public、internal 或 private 已声明可访问性，而声明为命名空间成员的类型只能具有 public 或 internal 已声明可访问性。）

· 接口成员隐式地具有 public 已声明可访问性。在接口成员声明中不允许使用访问修饰符。

· 枚举成员隐式地具有 public 已声明可访问性。在枚举成员声明中不允许使用访问修饰符。


3.5.2可访问域.563177

3.5.2 可访问域

一个成员的可访问域由（可能是不连续的）程序文本节组成，从那里可以访问该成员。出于定义成员可访问域的目的，如果成员不是在某个类型内声明的，就称该成员是顶级的；如果成员是在其他类型内声明的，就称该成员是嵌套的。此外，程序的程序文本定义为包含在该程序的所有源文件中的全部程序文本，而类型的程序文本定义为包含在该类型（可能还包括嵌套在该类型内的类型）的“类体”、“结构体”、“接口体”或“枚举体”中的开始和结束（“{”和“}”）标记之间的全部程序文本。

预定义类型（如 object、int 或 double）的可访问域是无限制的。

在程序 P 中声明的顶级类型 T 的可访问域如下定义：

· 如果 T 的已声明可访问性为 public，则 T 的可访问域是 P 的以及引用 P 的任何程序的程序文本。

· 如果 T 的已声明可访问性为 internal，则 T 的可访问域是 P 的程序文本。

从这些定义可以推断出：顶级类型的可访问域始终至少是声明了该类型的程序的程序文本。

在程序 P 内的类型 T 中声明的嵌套成员 M 的可访问域如下定义（注意 M 本身可能就是一个类型）：

· 如果 M 的已声明可访问性为 public，则 M 的可访问域是 T 的可访问域。

· 如果 M 的已声明可访问性是 protected internal，设 D 表示 P 的程序文本和从 T 派生的任何类型（在 P 的外部声明）的程序文本的并集。M 的可访问域是 T 与 D 的可访问域的交集。

· 如果 M 的已声明可访问性是 protected，则设 D 表示 T 的程序文本和从 T 派生的任何类型的程序文本的并集。M 的可访问域是 T 与 D 的可访问域的交集。

· 如果 M 的已声明可访问性为 internal，则 M 的可访问域是 T 的可访问域与 P 的程序文本的交集。

· 如果 M 的已声明可访问性为 private，则 M 的可访问域是 T 的程序文本。

从这些定义可以看出：嵌套成员的可访问域总是至少为声明该成员的类型的程序文本。还可以看出：成员的可访问域包含的范围决不会比声明该成员的类型的可访问域更广。

用直观的话来讲，当访问类型或成员 M 时，按下列步骤进行计算以确保允许进行访问：

· 首先，如果 M 是在某个类型（相对于编译单元或命名空间）内声明的，则当该类型不可访问时将会发生编译时错误。

· 然后，如果 M 为 public，则允许进行访问。

· 否则，如果 M 为 protected internal，则当访问发生在声明了 M 的程序中，或发生在从声明 M 的类派生的类中并通过派生类类型（第 3.5.3 节）进行访问时，允许进行访问。

· 否则，如果 M 为 protected，则当访问发生在声明了 M 的类中，或发生在从声明 M 的类派生的类中并通过派生类类型（第 3.5.3 节）进行访问时，允许进行访问。

· 否则，如果 M 为 internal，则当访问发生在声明了 M 的程序中时允许进行访问。

· 否则，如果 M 为 private，则当访问发生在声明了 M 的类型中时允许进行访问。

· 否则，类型或成员不可访问，并发生编译时错误。

在下面的示例中，

public class A

{

public static int X;

internal static int Y;

private static int Z;

}

internal class B

{

public static int X;

internal static int Y;

private static int Z;

public class C

{

public static int X;

internal static int Y;

private static int Z;

}

private class D

{

public static int X;

internal static int Y;

private static int Z;

}

}

类和成员具有下列可访问域：

· A 和 A.X 的可访问域无限制。

· A.Y、B、B.X、B.Y、B.C、B.C.X 和 B.C.Y 的可访问域是包含程序的程序文本。

· A.Z 的可访问域是 A 的程序文本。

· B.Z 和 B.D 的可访问域是 B 的程序文本，包括 B.C 和 B.D 的程序文本。

· B.D.X 和 B.D.Y 的可访问域是 B 的程序文本，包括 B.C 和 B.D 的程序文本。

· B.D.Z 的可访问域是 B.D 的程序文本。

如示例所示，成员的可访问域决不会大于包含它的类型的可访问域。例如，即使所有的 X 成员都具有公共级的已声明可访问性，但除了 A.X 外，所有其他成员的可访问域都受包含类型的约束。

如第 3.4 节中所描述的那样，基类的所有成员（实例构造函数、析构函数和静态构造函数除外）都由派生类型继承。这甚至包括基类的私有成员。但是，私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中，

class A

{

int x;

static void F(B b) {

b.x = 1; // Ok

}

}

class B: A

{

static void F(B b) {

b.x = 1; // Error, x not accessible

}

}

类 B 继承类 A 的私有成员 x。因为该成员是私有的，所以只能在 A 的“类体”中对它进行访问。因此，对 b.x 的访问在 A.F 方法中取得了成功，在 B.F 方法中却失败了。


3.5.3实例成员的受保护访问.578672

3.5.3 实例成员的受保护访问

当在声明了某个 protected 实例成员的类的程序文本之外访问该实例成员时，以及当在包含有某个 protected internal 实例成员声明的程序的程序文本之外访问该实例成员时，要求这种访问发生在该成员所属类的一个派生类的程序文本中，并引用该派生类的一个实例。假定 B 是一个基类，它声明了一个受保护的实例成员 M，而 D 是从 B 派生的类。在 D 的类体中，对 M 的访问可采取下列形式之一：

· M 形式的非限定类型名或初等表达式。

· E.M 形式的初等表达式（其中，E 是类 D 或是从 D 派生的类）。

· base.M 形式的初等表达式。

除了上述形式外，派生类还可以在它自已的构造函数-初始化器中（第 10.10.1 节）访问它的基类的受保护的实例构造函数。

在下面的示例中，

public class A

{

protected int x;

static void F(A a, B b) {

a.x = 1; // Ok

b.x = 1; // Ok

}

}

public class B: A

{

static void F(A a, B b) {

a.x = 1; // Error, must access through instance of B

b.x = 1; // Ok

}

}

在 A 中可以通过 A 和 B 的实例访问 x，这是因为在两种情况下访问都通过 A 的实例或从 A 派生的类发生。但是在 B 中，由于 A 不从 B 派生，所以不可能通过 A 的实例访问 x。


3.5.4可访问性约束.582991

3.5.4 可访问性约束

C# 语言中的有些构造要求某个类型至少与某个成员或其他类型具有同样的可访问性。如果 T 的可访问域是 M 可访问域的超集，我们就说类型 T 至少与成员或类型 M 具有同样的可访问性。换言之，如果 T 在所有可访问 M 的上下文中都是可访问的，则 T 至少与 M 具有同样的可访问性。

存在下列可访问性约束：

· 类类型的直接基类必须至少与类类型本身具有同样的可访问性。

· 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。

· 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。

· 常数的类型必须至少与常数本身具有同样的可访问性。

· 字段的类型必须至少与字段本身具有同样的可访问性。

· 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。

· 属性的类型必须至少与属性本身具有同样的可访问性。

· 事件的类型必须至少与事件本身具有同样的可访问性。

· 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。

· 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。

· 实例构造函数的参数类型必须至少与实例构造函数本身具有同样的可访问性。

在下面的示例中，

class A {...}

public class B: A {...}

B 类导致编译时错误，因为 A 并不具有至少与 B 相同的可访问性。

同样，在示例中

class A {...}

public class B

{

A F() {...}

internal A G() {...}

public A H() {...}

}

B 中的方法 H 导致编译时错误，因为返回类型 A 并不具有至少与该方法相同的可访问性。


3.6签名和重载.588068

3.6 签名和重载

方法、实例构造函数、索引器和运算符是由它们的签名来刻画的：

· 方法签名由方法的名称和它的每一个形参（按从左到右的顺序）的类型和种类（值、引用或输出）组成。需注意的是，方法签名既不包含返回类型，也不包含 params 修饰符（它可用于最右边的参数）。

· 实例构造函数签名由它的每一个形参（按从左到右的顺序）的类型和种类（值、引用或输出）组成。具体说来，实例构造函数的签名不包含可为最右边的参数指定的 params 修饰符。

· 索引器签名由它的每一个形参（按从左到右的顺序）的类型组成。具体说来，索引器的签名不包含元素类型。

· 运算符签名由运算符的名称和它的每一个形参（按从左到右的顺序）的类型组成。具体说来，运算符的签名不包含结果类型。

签名是对类、结构和接口的成员实施重载的机制：

· 方法重载允许类、结构或接口用同一个名称声明多个方法，条件是它们的签名在该类、结构或接口中是唯一的。

· 实例构造函数重载允许类或结构声明多个实例构造函数，条件是它们的签名在该类或结构中是唯一的。

· 索引器重载允许类、结构或接口声明多个索引器，条件是它们的签名在该类、结构或接口中是唯一的。

· 运算符重载允许类或结构用同一名称声明多个运算符，条件是它们的签名在该类或结构中是唯一的。

下面的示例显示了一组重载方法声明及其签名。

interface ITest

{

void F(); // F()

void F(int x); // F(int)

void F(ref int x); // F(ref int)

void F(int x, int y); // F(int, int)

int F(string s); // F(string)

int F(int x); // F(int) error

void F(string[] a); // F(string[])

void F(params string[] a); // F(string[]) error

}

请注意，所有 ref 和 out 参数修饰符（第 10.5.1 节）都是签名的组成部分。因此，F(int) 和 F(ref int) 这两个签名都具有唯一性。还请注意，返回类型和 params 修饰符不是签名的组成部分，所以不可能仅基于返回类型或是否存在 params 修饰符来实施重载。因此，上面列出的关于方法 F(int) 和 F(params string[]) 的声明会导致编译时错误。


3.7范围.593542

3.7 范围

名称的范围是一个程序文本区域，在其中可以引用由该名称声明的实体，对该名称加以限定。范围可以嵌套，并且内部范围可以重新声明外部范围中的名称的含义。（但这并不会取消第 3.3 节强加的限制，即在嵌套块中不可能声明与它的封闭块中的局部变量同名的局部变量。）因此，我们就说，外部范围中的这个同名的名称在由内部范围覆盖的程序文本区域中是隐藏的，对它只能通过它的限定名才能从内部范围来访问。

· 一个由“命名空间成员声明”（第 9.4 节）所声明的命名空间成员的范围，如果没有其他封闭它的“命名空间成员声明”，则它的范围是整个程序文本。

· 命名空间声明中命名空间成员声明所声明的命名空间成员的范围是这样定义的，如果该命名空间成员声明的完全限定名为 N，则其声明的命名空间成员的范围是，完全限定名为 N 或以 N 开头后跟句点的每个命名空间声明的命名空间体。

· 由 using 指令（第 9.3 节）定义或导入的名称的是，出现 using 指令的编译单元或命名空间体内的所有的整个“命名空间成员声明”中。using 指令可以使零个或更多的命名空间或者类型名称在特定的编译单元或命名空间体中可用，但不会把任何新成员提供给隐含的下层声明空间。换言之，using 指令仅在使用它的编译单元或命名空间体范围内有效，它的功效是不可传递的。

· 由“类成员声明”（第 10.2 节）所声明的成员范围是该声明所在的那个“类体”。此外，类成员的范围扩展到该成员的可访问域（第 3.5.2 节）中的那些派生类的“类体”。

· 由“结构成员声明”（第 11.2 节）声明的成员范围是该声明所在的结构体。

· 由“枚举成员声明”（第 14.3 节）声明的成员范围是该声明所在的枚举体。

· 在“方法声明”（第 10.5 节）中声明的参数范围是该方法所在的方法体。

· 在“索引器声明”（第 10.8 节）中声明的参数范围是该索引器声明的访问器声明。

· 在“运算符声明”（第 10.9 节）中声明的参数范围是该运算符声明所在的块。

· 在“构造函数声明”（第 10.10 节）中声明的参数范围是该构造函数声明的构造函数初始值设定项和块。

· 在“标记语句”（第 8.4 节）中声明的标签范围是该声明所在的块。

· 在“局部变量声明”（第 8.5.1 节）中声明的局部变量范围是该声明所在的块。

· 在 switch 语句（第 8.7.2 节）的 switch 块中声明的局部变量范围是 switch 块。

· 在 for 语句的 for 初始值设定项（第 8.8.3 节）中声明的局部变量的范围是，该 for 语句的 for 初始值设定项、for 条件、for 迭代程序以及所包含的语句。

· 在局部常量声明（第 8.5.2 节）中声明的局部常量范围是该声明发生的块。在某局部常量常数声明符之前的文本位置中引用该局部常量是编译时错误。

在命名空间、类、结构或枚举成员的范围内，可以在位于该成员的声明之前的文本位置引用该成员。例如

class A

{

void F() {

i = 1;

}

int i = 0;

}

这里，F 在声明 i 之前引用它是有效的。

在局部变量的范围内，在位于该局部变量的“局部变量声明符”之前的文本位置引用该局部变量是编译时错误。例如

class A

{

int i = 0;

void F() {

i = 1; // Error, use precedes declaration

int i;

i = 2;

}

void G() {

int j = (j = 1); // Valid

}

void H() {

int a = 1, b = ++a; // Valid

}

}

在上面的方法 F 中，对 i 的第一次赋值时，那个 i 一定不是指在外部范围声明的字段 i。相反，它所引用是局部变量 i，这会导致编译时错误，因为它在文本上位于该变量的声明之前。在方法 G 中，在 j 的声明初始值设定项中使用 j 是有效的，因为并未在局部变量声明符之前使用 j。在方法 H 中，后面的局部变量声明符正确引用在同一局部变量声明内的前面的局部变量声明符中声明的局部变量。

局部变量的范围规则旨在保证表达式上下文中使用的名称的含义在块中总是相同。如果局部变量的范围仅从它的声明扩展到块的结尾，则在上面的示例中，第一次赋值将会分配给实例变量，第二次赋值将会分配给局部变量，如果后来重新排列块的语句，则可能会导致编译时错误。

块中名称的含义可能因该名称的使用上下文而异。在下面的示例中，

using System;

class A {}

class Test

{

static void Main() {

string A = "hello, world";

string s = A; // expression context

Type t = typeof(A); // type context

Console.WriteLine(s); // writes "hello, world"

Console.WriteLine(t); // writes "A"

}

}

名称 A 在表达式上下文中用来引用局部变量 A，在类型上下文中用来引用类 A。


3.7.1名称隐藏.604891

Powered By Gavin http://www.CSharpWeb.com

3.7.1 名称隐藏

实体的范围通常比该实体的声明空间包含更多的程序文本。具体说来，实体的范围可能包含一些声明，它们会引入一些新的声明空间，其中可能含有与该实体同名的新实体。这类声明导致原始的实体变为隐藏的。相反，当实体不是隐藏的时，就说它是可见的。

当范围之间相重叠（或通过嵌套重叠，或通过继承重叠）时会发生名称隐藏。以下各节介绍这两种隐藏类型的特性。


3.7.1.1通过嵌套隐藏.605565

3.7.1.1 通过嵌套隐藏

以下各项活动会导致发生通过嵌套的名称隐藏：在命名空间内嵌套其它命名空间或类型；在类或结构中的嵌套类型；声明参数和局部变量。

在下面的示例中，

class A

{

int i = 0;

void F() {

int i = 1;

}

void G() {

i = 1;

}

}

在方法 F 中，实例变量 i 被局部变量 i 隐藏，但在方法 G 中，i 仍引用该实例变量。

当内部范围中的名称隐藏外部范围中的名称时，它隐藏该名称的所有重载匹配项。在下面的示例中，

class Outer

{

static void F(int i) {}

static void F(string s) {}

class Inner

{

void G() {

F(1); // Invokes Outer.Inner.F

F("Hello"); // Error

}

static void F(long l) {}

}

}

由于 F 的所有外部匹配项都被内部声明隐藏，因此调用 F(1) 调用在 Inner 中声明的 F。由于同样的原因，调用 F("Hello") 导致编译时错误。


3.7.1.2通过继承隐藏.609045

3.7.1.2 通过继承隐藏

当类或结构重新声明从基类继承的名称时，会发生通过继承的名称隐藏。这种类型的名称隐藏采取下列形式之一：

· 类或结构中引入的常数、字段、属性、事件或类型会把其基类中所有的同名的成员隐藏起来。

· 类或结构中引入的方法隐藏所有同名的非方法基类成员，以及所有具有相同签名（方法名称和参数个数、修饰符和类型）的基类方法。

· 类或结构中引入的索引器隐藏所有具有相同签名（参数个数和类型）的基类索引器。

管理运算符声明（第 10.9 节）的规则使派生类不可能声明与基类中的运算符具有相同签名的运算符。因此，运算符从不相互隐藏。

与隐藏外部范围中的名称相反，隐藏继承范围中的可访问名称会导致发出警告。在下面的示例中，

class Base

{

public void F() {}

}

class Derived: Base

{

public void F() {} // Warning, hiding an inherited name

}

Derived 中的 F 声明导致报告一个警告。准确地说，隐藏继承的名称不是一个错误，因为这会限制基类按自身情况进行改进。例如，由于更高版本的 Base 引入了该类的早期版本中不存在的 F 方法，可能会发生上述情况。如果上述情况是一个错误，当基类属于一个单独进行版本控制的类库时，对该基类的“任何”更改都有可能导致它的派生类变得无效。

通过使用 new 修饰符可以消除因隐藏继承的名称导致的警告：

class Base

{

public void F() {}

}

class Derived: Base

{

new public void F() {}

}

new 修饰符指示 Derived 中的 F 是“新的”，并且确实是有意隐藏继承成员。

在声明一个新成员时，仅在该新成员的范围内隐藏被继承的成员。

class Base

{

public static void F() {}

}

class Derived: Base

{

new private static void F() {} // Hides Base.F in Derived only

}

class MoreDerived: Derived

{

static void G() { F(); } // Invokes Base.F

}

在上面的示例中，Derived 中的 F 声明隐藏从 Base 继承的 F，但由于 Derived 中的新 F 具有私有访问，它的范围不扩展到 MoreDerived。因此，MoreDerived.G 中的调用 F() 是有效的并将调用 Base.F。


3.8命名空间和类型名称.614510

3.8 命名空间和类型名称

C# 程序中的若干上下文要求指定命名空间名称或类型名称。两种形式的名称都写为以“.”标记分隔的一个或多个标识符。

namespace-name:（命名空间名称：）

namespace-or-type-name（命名空间或类型名称）

type-name:（类型名：）

namespace-or-type-name（命名空间或类型名称）

namespace-or-type-name:（命名空间或类型名称：）

identifier（标识符）
namespace-or-type-name . identifier（命名空间或类型名称 . 标识符）

“类型名”是一个“命名空间或类型名称”，它引用一个类型。需遵循下述的决策：“类型名”的“命名空间或类型名称”必须引用一个类型，否则将发生编译时错误。

“命名空间名称”是一个“命名空间或类型名称”，它引用一个命名空间。需遵循下述的决策：“命名空间名称”的“命名空间或类型名称”必须引用一个命名空间，否则将发生编译时错误。

“命名空间或类型名称”的含义按下述步骤确定：

· 如果“命名空间或类型名称”由单个标识符组成：

· 如果“命名空间或类型名称”出现在类或结构声明体内，则从该类或结构声明开始查找，遍及每个封闭它的类或结构声明（若它们存在的话），如果具有给定名称的成员存在、可访问且表示类型，则“命名空间或类型名称”引用该成员。请注意，当确定“命名空间或类型名称”的含义时，会忽略非类型成员（常数、字段、方法、属性、索引器、运算符、实例构造函数、析构函数和静态构造函数）。

· 否则，从发生“命名空间或类型名称”的命名空间开始，遍及每个封闭它的命名空间（若它们存在的话），直至全局命名空间结束，对下列步骤进行评估，直到找到实体：

· 如果命名空间包含具有给定名称的命名空间成员，则“命名空间或类型名称”引用该成员，并根据该成员归为命名空间或类型类别。

· 否则，如果命名空间有一个对应的命名空间声明，且“命名空间或类型名称”出现的位置包含在该命名空间声明中，则：

如果该命名空间声明包含一个将给定名称与一个导入的命名空间或类型关联的 using 别名指令，则“命名空间或类型名称”引用该命名空间或类型。

否则，如果该命名空间声明中有一个“using 命名空间指令”，它导入的那个命名空间内含有一个与给定名称完全匹配的类型，则“命名空间或类型名称”引用该类型。

否则，如果该“using 命名空间指令”导入的命名空间包含多个具有给定名称的类型，则“命名空间或类型名称”就被认为是含义不清的，将导致发生错误。

· 否则，“命名空间或类型名称”就被认为是未定义的，导致发生编译时错误。

· 否则，“命名空间或类型名称”的形式为 N.I，其中 N 是由除最右边的标识符以外的所有标识符组成的“命名空间或类型名称”，I 是最右边的标识符。N 最先按“命名空间或类型名称”解析。如果对 N 的解析不成功，则发生编译时错误。否则，N.I 按下面这样解析：

· 如果 N 是一个命名空间而 I 是该命名空间中可访问成员的名称，则 N.I 引用该成员，并根据该成员归为命名空间或类型类别。

· 如果 N 是类或结构类型而 I 是 N 中可访问类型的名称，则 N.I 引用该类型。

· 否则，N.I 是无效的命名空间或类型名称并将发生编译时错误。


3.8.1完全限定名.620980

3.8.1 完全限定名

每个命名空间和类型都具有一个完全限定名，该名称在所有其他命名空间或类型中唯一标识该命名空间或类型。命名空间或类型 N 的完全限定名按下面这样确定：

· 如果 N 是全局命名空间的成员，则它的完全限定名为 N。

· 否则，它的完全限定名为 S.N，其中 S 是声明了 N 的命名空间或类型的完全限定名。

换言之，N 的完全限定名是从全局命名空间开始通向 N 的标识符的完整分层路径。由于命名空间或类型的每个成员都必须具有唯一的名称，因此，如果将这些成员名称置于命名空间或类型的完全限定名之后，这样构成的成员完全限定名一定符合唯一性。

下面的示例显示了若干命名空间和类型声明及其关联的完全限定名。

class A {} // A

namespace X // X

{

class B // X.B

{

class C {} // X.B.C

}

namespace Y // X.Y

{

class D {} // X.Y.D

}

}

namespace X.Y // X.Y

{

class E {} // X.Y.E

}


3.9自动内存管理.624039

3.9 自动内存管理

C# 使用自动内存管理，它使开发人员不再需要以手动方式分配和释放由对象占用的内存。自动内存管理策略由垃圾回收器实现。一个对象的内存管理生存周期如下所示：

1. 当创建对象时，将为其分配内存，运行构造函数，该对象被视为活对象。

2. 如果该对象或它的任何部分在后续执行过程中不再可能被访问了（除了运行它的析构函数），则该对象被视为不再被使用，可以销毁。C# 编译器和垃圾回收器可以通过分析代码，确定哪些对象引用可能在将来被使用。例如，如果范围内的某个局部变量是现有的关于此对象的唯一的引用，但在当前执行点之后的任何后续执行过程中，该局部变量都不再可能被引用，那么垃圾回收器可以（但不是必须）认为该对象不再被使用。

3. 一旦对象符合销毁条件，在稍后某个时间将运行该对象的析构函数（第 10.12 节）（如果有的话）。除非被显式调用所重写，否则对象的析构函数只运行一次。

4. 一旦运行对象的析构函数，如果该对象或它的任何部分无法由任何可能的执行继续（包括运行析构函数）访问，则该对象被视为不可访问，可以回收。

5. 最后，在对象变得符合回收条件后，垃圾回收器将释放与该对象关联的内存。

垃圾回收器维护对象的使用信息，并利用此信息做出内存管理决定，如在内存中的何处安排一个新创建的对象、何时重定位对象以及对象何时不再被使用或不可访问。

与其他假定存在垃圾回收器的语言一样，C# 也被设计成使垃圾回收器可以实现广泛的内存管理策略。例如，C# 并不要求一定要运行析构函数，不要求对象一符合条件就被回收，也不要求析构函数以任何特定的顺序或在任何特定的线程上运行。

垃圾回收器的行为在某种程度上可通过类 System.GC 的静态方法来控制。该类可用于请求执行一次回收操作、运行（或不运行）析构函数，等等。

由于垃圾回收器在决定何时回收对象和运行析构函数方面可以有很大的选择范围，它的一个符合条件的实现所产生的输出可能与下面的代码所显示的不同。程序

using System;

class A

{

~A() {

Console.WriteLine("Destruct instance of A");

}

}

class B

{

object Ref;

public B(object o) {

Ref = o;

}

~B() {

Console.WriteLine("Destruct instance of B");

}

}

class Test

{

static void Main() {

B b = new B(new A());

b = null;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

创建类 A 的一个实例和类 B 的一个实例。当给变量 b 赋值 null 后，这些对象变得符合垃圾回收条件，这是因为从此往后，任何用户编写的代码不可能再访问这些对象。输出可以为

Destruct instance of A

Destruct instance of B

或

Destruct instance of B

Destruct instance of A

这是因为该语言对于对象的垃圾回收顺序没有强加约束。

在微妙的情况中，“符合销毁条件”和“符合回收条件”之间的区别非常重要。例如，

using System;

class A

{

~A() {

Console.WriteLine("Destruct instance of A");

}

public void F() {

Console.WriteLine("A.F");

Test.RefA = this;

}

}

class B

{

public A Ref;

~B() {

Console.WriteLine("Destruct instance of B");

Ref.F();

}

}

class Test

{

public static A RefA;

public static B RefB;

static void Main() {

RefB = new B();

RefA = new A();

RefB.Ref = RefA;

RefB = null;

RefA = null;

// A and B now eligible for destruction

GC.Collect();

GC.WaitForPendingFinalizers();

// B now eligible for collection, but A is not

if (RefA != null)

Console.WriteLine("RefA is not null");

}

}

在上面的程序中，如果垃圾回收器选择在 B 的析构函数之前运行 A 的析构函数，则该程序的输出可能是：

Destruct instance of A

Destruct instance of B

A.F

RefA is not null

请注意，虽然 A 的实例被当作“没有被使用”，并且 A 的析构函数已被运行过了，但仍可能从其他析构函数调用 A 的方法（此例中是指 F）。还请注意，运行析构函数可能导致对象再次从主干程序中变得可用。在此例中，运行 B 的析构函数导致了先前没有被使用的 A 的实例变得可从当前有效的引用 Test.RefA 访问。调用 WaitForPendingFinalizers 后，B 的实例符合回收条件，但由于引用 Test.RefA 的缘故，A 的实例不符合回收条件。

为了避免混淆和意外的行为，好的做法通常是让析构函数只对存储在它们对象本身字段中的数据执行清理，而不对它所引用的其他对象或静态字段执行任何操作。


3.10执行顺序.634259

3.10 执行顺序

C# 程序执行时，在临界执行点保留每个执行线程的副作用。按照定义，副作用是指读写易失性字段、写入非易失性变量、写入外部资源和引发异常。临界执行点（这些副作用的顺序必须保存在其中）是指下列各活动：引用一些易失性字段（第 10.4.3 节）；引用 lock 语句（第 8.12 节）；引用线程的创建与终止。执行环境可以随便更改 C# 程序的执行顺序，但受下列约束限制：

· 在执行线程中需保持数据依赖性。就是说，在计算每个变量的值时，就好像线程中的所有语句都是按原始程序顺序执行的。

· 保留初始化的排序规则（第 10.4.4 节和第 10.4.5 节）。

· 对于易失性读写（第 10.4.3 节）的副作用的顺序需保持不变。此外，执行环境甚至可以不需要计算一个表达式的各个部分，如果它能推断出表达式的值是“不会被使用的”而且不会产生有效的副作用（包括由调用方法或访问易失性字段导致的任何副作用）。当程序执行被异步事件（例如其他线程引发的异常）中断时，它不保证可观察到的副作用以原有的程序顺序出现。


4.类型.636030

4. 类型

C# 语言的类型划分为两大类：值类型和引用类型。

type:（类型：）

value-type（值类型）
reference-type（引用类型）

第三种类型是指针，只能用在不安全代码中。第 A.2 节对此做了进一步的探讨。

值类型与引用类型的不同之处在于：值类型的变量直接包含其数据，而引用类型的变量存储对其数据的引用，后者称为对象。对于引用类型，两个变量可能引用同一个对象，因此对一个变量的操作可能影响另一个变量所引用的对象。对于值类型，每个变量都有自己的数据副本，对一个变量的操作不可能影响另一个变量。

C# 的类型系统是统一的，因此任何类型的值都可以按对象处理。C# 中的每个类型直接或间接地从 object 类类型派生，而 object 是所有类型的最终基类。引用类型的值都被当作“对象”来处理，这是因为这些值可以简单地被视为是属于 object 类型。值类型的值则通过执行装箱和取消装箱操作（第 4.3 节）亦按对象处理。


4.1值类型.637593

4.1 值类型

一个值类型或是结构类型，或是枚举类型。C# 提供称为简单类型的预定义结构类型集。简单类型通过保留字标识。

value-type:（值类型：）

struct-type（结构类型）
enum-type（枚举类型）

struct-type:（结构类型：）

type-name（类型名称）
simple-type（简单类型）

simple-type:（简单类型：）

numeric-type（数值类型）
bool

numeric-type:（数值类型：）

integral-type（整型）
floating-point-type（浮点类型）
decimal

integral-type:（整型：）

sbyte
byte
short
ushort
int
uint
long
ulong
char

floating-point-type:（浮点类型：）

float
double

enum-type:（枚举类型：）

type-name（类型名）

值类型的变量总是包含该类型的值。与引用类型不同，值类型的值不可能为 null，也不可能引用派生程度较大的类型的对象。

值类型的变量赋值会创建所赋的值的一个副本。这不同于引用类型的变量赋值，引用类型的变量赋值复制的是引用而不是由引用标识的对象。


4.1.1System.ValueType类型.640468

Powered By Gavin http://www.CSharpWeb.com

4.1.1 System.ValueType 类型

所有值类型从类 System.ValueType 隐式继承，后者又从类 object 继承。任何类型都不可能从值类型派生，因此，所有值类型都是隐式密封的（第 10.1.1.2 节）。

注意，System.ValueType 本身不是值类型。相反，它属于“类类型”，所有值类型都从它自动派生。


4.1.2默认构造函数.641335

4.1.2 默认构造函数

所有值类型都隐式声明一个称为默认构造函数的公共无参数实例构造函数。默认构造函数返回一个零初始化实例，它就是该值类型的默认值。

· 对于所有“简单类型”，默认值是将其所有位都置零的位模式所形成的值：

· 对于 sbyte、byte、short、ushort、int、uint、long 和 ulong，默认值为 0。

· 对于 char，默认值为 '\x0000'。

· 对于 float，默认值为 0.0f。

· 对于 double，默认值为 0.0d。

· 对于 decimal，默认值为 0.0m。

· 对于 bool，默认值为 false。

· 对于“枚举类型”E，默认值为 0。

· 对于“结构类型”，默认值是通过将所有值类型字段设置为它们的默认值、将所有引用类型字段设置为 null 而产生的值。

与任何其他实例构造函数一样，值类型的默认构造函数也是用 new 运算符调用的。出于效率原因，实际上，不必故意调用它的构造函数。在下面的示例中，变量 i 和 j 都被初始化为零。

class A

{

void F() {

int i = 0;

int j = new int();

}

}

由于每个值类型都隐式地具有一个公共无参数实例构造函数，因此，一个结构类型中不可能包含一个关于无参数构造函数的显式声明。但允许结构类型声明参数化实例构造函数（第 11.3.8 节）。


4.1.3结构类型.645967

Powered By Gavin http://www.CSharpWeb.com

4.1.3 结构类型

结构类型是一种值类型，它可以声明常数、字段、方法、属性、索引器、运算符、实例构造函数、静态构造函数和嵌套类型。有关结构类型的介绍详见第 11 节。


4.1.4简单类型.646364

4.1.4 简单类型

C# 提供称为简单类型的预定义结构类型集。简单类型通过保留字标识，而这些保留字只是 System 命名空间中预定义结构类型的别名，详见下表。

保留字 | 化名的类型
sbyte | System.SByte
byte | System.Byte
short | System.Int16
ushort | System.UInt16
int | System.Int32
uint | System.UInt32
long | System.Int64
ulong | System.UInt64
char | System.Char
float | System.Single
double | System.Double
bool | System.Boolean
decimal | System.Decimal

由于简单类型是结构类型的别名，每个简单类型都具有成员。例如，int 具有在 System.Int32 中声明的成员以及从 System.Object 继承的成员，允许使用下面的语句：

int i = int.MaxValue; // System.Int32.MaxValue constant

string s = i.ToString(); // System.Int32.ToString() instance method

string t = 123.ToString(); // System.Int32.ToString() instance method

简单类型与其他结构类型的不同之处在于：简单类型允许某些附加的操作：

· 大多数简单类型允许通过编写文本（第 2.4.4 节）来创建值。例如，123 是类型 int 的文本，'a' 是类型 char 的文本。C# 没有普遍地为结构类型设置类似的以文本创建值的规则，所以其他结构类型的非默认值最终总是通过这些结构类型的实例构造函数来创建的。

· 当表达式的操作数都是简单类型常数时，编译器可以在编译时计算表达式。这样的表达式称为常数表达式（第 7.15 节）。涉及其他结构类型所定义的运算符的表达式不被视为常数表达式。

· 通过 const 声明可以声明简单类型（第 10.3 节）的常数。常数不可能属于其他结构类型，但 static readonly 字段提供了类似的效果。

· 涉及简单类型的转换可以参与由其他结构类型定义的转换运算符的计算，但用户定义的转换运算符从来不能参与其他用户定义运算符的计算（第 6.4.2 节）。


4.1.5整型.655997

4.1.5 整型

C# 支持九种整型：sbyte、byte、short、ushort、int、uint、long、ulong 和 char。整型具有以下所列的大小和取值范围：

· sbyte 类型表示有符号 8 位整数，其值介于 -128 和 127 之间。

· byte 类型表示无符号 8 位整数，其值介于 0 和 255 之间。

· short 类型表示有符号的 16 位整数，其值介于 -32768 和 32767 之间。

· ushort 类型表示无符号的 16 位整数，其值介于 0 和 65535 之间。

· int 类型表示有符号 32 位整数，其值介于 -2147483648 和 2147483647 之间。

· uint 类型表示无符号 32 位整数，其值介于 0 和 4294967295 之间。

· long 类型表示有符号的 64 位整数，其值介于 -9223372036854775808 和 9223372036854775807 之间。

· ulong 类型表示无符号的 64 位整数，其值介于 0 和 18446744073709551615 之间。

· char 类型表示无符号 16 位整数，其值介于 0 和 65535 之间。char 类型的可能值集与 Unicode 字符集相对应。虽然 char 的表示形式与 ushort 相同，但一种类型上允许实施的所有操作并非都可以用在另一种类型上。

整型一元运算符和二元运算符总是对有符号 32 位精度、无符号的 32 位精度、有符号 64 位精度或无符号 64 位精度进行操作：

· 对于 + 和 ~ 一元运算符，操作数转换为 T 类型，其中 T 是 int、uint、long 和 ulong 中第一个可以完全表示操作数的所有可能值的类型。然后用 T 类型的精度执行运算，结果的类型是 T 类型。

· 对于一元运算符 -，操作数转换为类型 T，其中 T 是 int 和 long 中第一个可以完全表示操作数的所有可能值的类型。然后用 T 类型的精度执行运算，结果的类型是 T 类型。一元运算符 - 不能应用于类型 ulong 的操作数。

· 对于 +、-、*、/、%、&、^、|、==、!=、>、<、>= 和 <= 二元运算符，操作数转换为类型 T，其中 T 是 int、uint、long 和 ulong 中第一个可以完全表示两个操作数的所有可能值的类型。然后用 T 类型的精度执行运算，运算的结果的类型也属于 T（对于关系运算符为 bool）。对于二元运算符，不允许一个操作数为 long 类型而另一个操作数为 ulong 类型。

· 对于 << 和 >> 二元运算符，左操作数转换为 T 类型，其中 T 是 int、uint、long 和 ulong 中第一个可以完全表示操作数的所有可能值的类型。然后用 T 类型的精度执行运算，结果的类型是 T 类型。

char 类型按分类归属为整型类别，但它在以下两个方面不同于其他整型：

· 不存在从其他类型到 char 类型的隐式转换。具体说来，即使 sbyte、byte 和 ushort 类型具有完全可以用 char 类型来表示的值范围，也不存在从 sbyte、byte 或 ushort 到 char 的隐式转换。

· char 类型的常数必须写成字符或带有强制转换为类型 char 的整数。例如，(char)10 与 '\x000A' 是相同的。

checked 和 unchecked 运算符和语句用于控制整型算术运算和转换（第 7.5.12 节）的溢出检查。在 checked 上下文中，溢出产生编译时错误或导致引发 System.OverflowException。在 unchecked 上下文中将忽略溢出，任何与目标类型不匹配的高序位都被放弃。


4.1.6浮点型.666073

4.1.6 浮点型

C# 支持两种浮点型：float 和 double。float 和 double 类型用 32 位单精度和 64 位双精度 IEEE 754 格式来表示，这些格式提供以下几组值：

· 正零和负零。大多数情况下，正零和负零的行为与简单的值零相同，但某些运算会区别对待此两种零（第 7.7.2 节）。

· 正无穷大和负无穷大。无穷大是由非零数字被零除这样的运算产生的。例如，1.0 / 0.0 产生正无穷大，而 –1.0 / 0.0 产生负无穷大。

· 非数字值，常缩写为 NaN。NaN 是由无效的浮点运算（如零被零除）产生的。

· 以 s ´ m ´ 2e 形式表示的非零值的有限集合，其中 s 为 1 或 ?1，m 和 e 由特定的浮点型确定：对于 float，为 0 < m < 224 和 ?149 £ e £ 104，对于 double，则为 0 < m < 253 和 ?1075 £ e £ 970。非标准化的浮点数被视为有效非零值。

float 类型可表示精度为 7 位、在大约 1.5 ´ 10?45 到 3.4 ´ 1038 的范围内的值。

double 类型可表示精度为 15 位或 16 位、在大约 5.0 ´ 10?324 到 1.7 ´ 10308 的范围内的值。

如果二元运算符的一个操作数为浮点型，则另一个操作数必须为整型或浮点型，并且运算按下面这样计算：

· 如果一个操作数为整型，则该操作数转换为与另一个操作数的类型相同的浮点型。

· 然后，如果任一操作数的类型为 double，则另一个操作数转换为 double。至少用 double 范围和精度执行运算，结果的类型为 double（对于关系运算符则为 bool）。

· 否则，至少用 float 范围和精度执行运算，结果的类型为 float（对于关系运算符则为 bool）。

浮点运算符（包括赋值运算符）从来不产生异常。相反，在异常情况下，浮点运算产生零、无穷大或 NaN，如下所述：

· 如果浮点运算的结果对于目标格式太小，则运算结果变成正零或负零。

· 如果浮点运算的结果对于目标格式太大，则运算结果变成正无穷大或负无穷大。

· 如果浮点运算无效，则运算的结果变成 NaN。

· 如果浮点运算的一个或两个操作数为 NaN，则运算的结果变成 NaN。

可以用比运算的结果类型更高的精度来执行浮点运算。例如，某些硬件结构支持比 double 类型具有更大的范围和精度的“extended”或“long double”浮点型，并隐式地使用这种更高精度类型执行所有浮点运算。只有以额外的性能开销为代价，才能使这样的硬件结构用“较低”的精度执行浮点运算。C# 采取的是允许将更高的精度类型用于所有浮点运算，而不是强求执行规定的精度，造成同时损失性能和精度。除了传递更精确的结果外，这样做很少会产生任何可察觉的效果。但是，在 x * y / z 形式的表达式中，如果其中的乘法会产生超出 double 范围的结果，而后面的除法使临时结果返回到 double 范围内，则以更大范围的格式去计算该表达式，可能会产生有限值的结果（本来应是无穷大）。


4.1.7decimal类型.674445

4.1.7 decimal 类型

decimal 类型是适合财务和货币计算的 128 位数据类型。decimal 类型可以表示具有 28 或 29 个有效数字、从 1.0 ´ 10-28 到大约 7.9 ´ 1028 范围内的值。

decimal 类型的有限值集的形式为 (–1)s x c x 10-e，其中符号 s 是 0 或 1，系数 c 由 0 £ c < 296 给定，小数位数 e 满足 0 £ e £ 28。十进制类型不支持有符号的零、无穷大或 NaN。decimal 可用一个 96 位整数配上以 10 的幂标定的单位（定位小数点）来表示。对于绝对值小于 1.0m 的 decimal，它的值最多精确到第 28 位小数。对于绝对值大于或等于 1.0m 的 decimal，它的值精确到 28 或 29 个有效数字。与 float 和 double 数据类型相反，十进制小数数字（如 0.1）可以精确地用 decimal 表示形式来表示。在 float 和 double 表示形式中，这类数字通常变成无限小数，使这些表示形式更容易发生舍入错误。

如果二元运算符的一个操作数为 decimal 类型，则另一个操作数必须为整型或 decimal 类型。如果存在一个整型操作数，它将在执行运算前转换为 decimal。

decimal 类型值的运算结果是这样得出的：先计算一个精确结果（按每个运算符的定义保留小数位数），然后舍入以适合表示形式。结果舍入到最接近的可表示值，当结果同样地接近于两个可表示值时，舍入到最小有效位数位置中为偶数的值（这称为“银行家舍入法”）。零结果总是包含符号 0 和小数位数 0。

如果十进制算术运算产生一个绝对值小于或等于 5 x 10-29 的值，则运算结果变为零。如果 decimal 算术运算产生的值对于 decimal 格式太大，则将引发 System.OverflowException。

与浮点型相比，decimal 类型具有较高的精度，但取值范围较小。因此，从浮点型到 decimal 的转换可能会产生溢出异常，而从 decimal 到浮点型的转换则可能导致精度损失。由于这些原因，在浮点型和 decimal 之间不存在隐式转换，如果没有显式地标出强制转换，就不可能在同一表达式中同时使用浮点操作数和 decimal 操作数。


4.1.8bool类型.679738

Powered By Gavin http://www.CSharpWeb.com

4.1.8 bool 类型

bool 类型表示布尔逻辑量。bool 类型的可能值为 true 和 false。

在 bool 和其他类型之间不存在标准转换。具体说来，bool 类型与整型截然不同，不能用 bool 值代替整数值，反之亦然。

在 C 和 C++ 语言中，零整数或浮点值或空指针可以转换为布尔值 false，非零整数或浮点值或非空指针可以转换为布尔值 true。在 C# 中，这种转换是通过显式地将整数或浮点值与零进行比较，或者显式地将对象引用与 null 进行比较来完成的。


4.1.9枚举类型.680848

Powered By Gavin http://www.CSharpWeb.com

4.1.9 枚举类型

枚举类型是具有命名常数的独特的类型。每个枚举类型都具有一个基础类型，这必须是 byte、sbyte、short、ushort、int、uint、long 或 ulong。枚举类型的值集和它的基础类型的值集相同。枚举类型的值并不只限于那些命名常数的值。枚举类型是通过枚举声明（第 14.1 节）定义的。


4.2引用类型.681664

4.2 引用类型

引用类型是类类型、接口类型、数组类型或委托类型。

reference-type:（引用类型：）

class-type（类类型）
interface-type（接口类型）
array-type（数组类型）
delegate-type（委托类型）

class-type:（类类型：）

type-name（类型名）
object
string

interface-type:（接口类型：）

type-name（类型名）

array-type:（数组类型：）

non-array-type rank-specifiers（非数组类型 秩说明符）

non-array-type:（非数组类型：）

type（类型）

rank-specifiers:（秩说明符：）

rank-specifier（秩说明符）
rank-specifiers rank-specifier（秩说明符 秩说明符）

rank-specifier:（秩说明符：）

[ dim-separators opt ]（[ 维度分隔符可选 ]）

dim-separators:（维度分隔符：）

，
dim-separators ,（维度分隔符 ,）

delegate-type:（委托类型：）

type-name（类型名）

引用类型值是对该类型的某个实例的一个引用，后者称为对象。null 值比较特别，它适用于所有引用类型，用来表示“没有被引用的实例”。


4.2.1类类型.686268

4.2.1 类类型

类类型定义包含数据成员、函数成员和嵌套类型的数据结构，其中数据成员包括常数和字段，函数成员包括方法、属性、事件、索引器、运算符、实例构造函数、析构函数和静态构造函数。类类型支持继承，继承是派生类可用来扩展和专门化基类的一种机制。类类型的实例是用对象创建表达式（第 7.5.10.1 节）创建的。

有关类类型的介绍详见第 10 节。

某些预定义类类型在 C# 语言中有特殊含义，如下表所示。

类类型 | 说明
System.Object | 所有其他类型的最终基类（第 4.2.2 节）。
System.String | C# 语言的字符串类型（第 4.2.3 节）。
System.ValueType | 所有值类型的基类（第 4.1.1 节）。
System.Enum | 所有枚举类型的基类（第 14 节）。
System.Array | 所有数组类型的基类（第 12 节）。
System.Delegate | 所有委托类型的基类（第 15 节）。
System.Exception | 所有异常类型的基类（第 16 节）。


4.2.2对象类型.691282

Powered By Gavin http://www.CSharpWeb.com

4.2.2 对象类型

object 类类型是所有其他类型的最终基类。C# 中的每种类型都是直接或间接从 object 类类型派生的。

关键字 object 只是预定义类 System.Object 的别名。


4.2.3string类型.691877

Powered By Gavin http://www.CSharpWeb.com

4.2.3 string 类型

string 类型是直接从 object 继承的密封类类型。string 类的实例表示 Unicode 字符串。

string 类型的值可以写为字符串（第 2.4.4 节）。

关键字 string 只是预定义类 System.String 的别名。


4.2.4接口类型.692988

Powered By Gavin http://www.CSharpWeb.com

4.2.4 接口类型

一个接口定义一个协定。实现接口的类或结构必须遵守其协定。一个接口可以从多个基接口继承，而一个类或结构可以实现多个接口。

有关接口类型的介绍详见第 13 节。


4.2.5数组类型.693462

Powered By Gavin http://www.CSharpWeb.com

4.2.5 数组类型

数组是一种数据结构，它包含可通过计算索引访问的零个或多个变量。数组中包含的变量（又称数组的元素）具有相同的类型，该类型称为数组的元素类型。

有关数组类型的介绍详见第 12 节。


4.2.6委托类型.693956

Powered By Gavin http://www.CSharpWeb.com

4.2.6 委托类型

委托是一种数据结构，它引用一个或多个方法，对于实例方法，还引用这些方法所对应的对象实例。

在 C 或 C++ 中与委托最接近的是函数指针，但函数指针只能引用静态函数，而委托则既可以引用静态方法，也可以引用实例方法。在后一种情况中，委托不仅存储了一个对该方法入口点的一个引用，还存储了一个对相应的对象实例的引用，该方法就是通过此对象实例被调用的。

有关委托类型的介绍详见第 15 节。


4.3装箱和取消装箱.694714

Powered By Gavin http://www.CSharpWeb.com

4.3 装箱和取消装箱

装箱和取消装箱的概念是 C# 的类型系统的核心。它在“值类型”和“引用类型”之间的架起了一座桥梁，使得任何“值类型”的值都可以转换为 object 类型的值，反过来转换也可以。装箱和取消装箱使我们能够统一地来考察类型系统，其中任何类型的值最终都可以按对象处理。


4.3.1装箱转换.695330

4.3.1 装箱转换

装箱转换允许将值类型隐式转换为引用类型。存在下列装箱转换：

· 从任何“值类型”（包括任何“枚举类型”）到类型 object。

· 从任何“值类型”（包括任何“枚举类型”）到类型 System.ValueType。

· 从任何“值类型”到“值类型”实现的任何“接口类型”。

· 从任何“枚举类型”到 System.Enum 类型。

将值类型的一个值装箱包括以下操作：分配一个对象实例，然后将值类型的值复制到该实例中。

最能说明“值类型”的值的实际装箱过程的办法是，设想有一个为该类型设置的装箱类。对任何“值类型”的 T 而言，装箱类的行为可用下列声明来描述：

sealed class T_Box: System.ValueType

{

T value;

public T_Box(T t) {

value = t;

}

}

T 类型值 v 的装箱过程现在包括执行表达式 new T_Box(v) 和将结果实例作为 object 类型的值返回。因此，下面的语句

int i = 123;

object box = i;

在概念上相当于

int i = 123;

object box = new int_Box(i);

实际上，像上面这样的 T_Box 和 int_Box 并不存在，并且装了箱的值的动态类型也不会真的属于一个类类型。相反，T 类型的装了箱的值属于动态类型 T，若用 is 运算符来检查动态类型的话，也仅能引用类型 T。例如，

int i = 123;

object box = i;

if (box is int) {

Console.Write("Box contains an int");

}

将在控制台上输出字符串“Box contains an int”。

装箱转换隐含着复制一份欲被装箱的值。这不同于从引用类型到 object 类型的转换，在后一种转换中，转换后的值继续引用同一实例，只是将它当作派生程度较小的 object 类型而已。例如，设有下列的声明

struct Point

{

public int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

则下面的语句

Point p = new Point(10, 10);

object box = p;

p.x = 20;

Console.Write(((Point)box).x);

将在控制台上输出值 10，因为将 p 赋值给 box 是一个隐式装箱操作，它将复制 p 的值。如果将 Point 声明为 class，由于 p 和 box 将会引用同一个实例，因此输出值为 20。


4.3.2取消装箱转换.700930

4.3.2 取消装箱转换

取消装箱转换允许将引用类型显式转换为值类型。存在以下取消装箱转换：

· 从类型 object 到任何值类型（包括任何枚举类型）。

· 从类型 System.ValueType 到任何值类型（包括任何枚举类型）。

· 从任何接口类型到实现了该接口类型的任何值类型。

· 从 System.Enum 类型到任何枚举类型。

取消装箱操作包括以下两个步骤：首先检查该对象实例是否是某个给定的值类型的装了箱的值，然后将值从实例中复制出来。

参照前一节中关于假想的装箱类的描述，从对象 box 到值类型 T 的取消装箱转换相当于执行表达式 ((T_Box)box).value。因此，下面的语句

object box = 123;

int i = (int)box;

在概念上相当于

object box = new int_Box(123);

int i = ((int_Box)box).value;

为使到给定值类型的取消装箱转换在运行时取得成功，源操作数的值必须是对某个对象的引用，而该对象先前是通过将该值类型的某个值装箱而创建的。如果源操作数为 null，则将引发 System.NullReferenceException。如果源操作数是对不兼容对象的引用，则将引发 System.InvalidCastException。


5.变量.703452

Powered By Gavin http://www.CSharpWeb.com

5. 变量

变量表示存储位置。每个变量都具有一个类型，它确定哪些值可以存储在该变量中。C# 是一种类型安全的语言，C# 编译器保证存储在变量中的值总是具有合适的类型。通过赋值或使用 ++ 和 ‑‑ 运算符可以更改变量的值。

在可以获取变量的值之前，变量必须已明确赋值（第 5.3 节）。

如下面的章节所述，变量是初始已赋值或初始未赋值。初始已赋值的变量有一个正确定义了的初始值，并且总是被视为已明确赋值。初始未赋值的变量没有初始值。为了使初始未赋值的变量在某个位置被视为已明确赋值，变量赋值必须发生在通向该位置的每个可能的执行路径中。


5.1变量类别.704422

5.1 变量类别

C# 定义了七种变量类别：静态变量、实例变量、数组元素、值参数、引用参数、输出参数以及局部变量。后面的章节将介绍其中的每一种类别。

在下面的示例中，

class A

{

public static int x;

int y;

void F(int[] v, int a, ref int b, out int c) {

int i = 1;

c = a + b++;

}

}

x 是静态变量，y 是实例变量，v[0] 是数组元素，a 是值参数，b 是引用参数，c 是输出参数，i 是局部变量。


5.1.1静态变量.706016

Powered By Gavin http://www.CSharpWeb.com

5.1.1 静态变量

用 static 修饰符声明的字段称为静态变量。静态变量在包含了它的那个类型的静态析构函数（第 10.11 节）执行之前就存在了，在关联的应用程序域终止时终止。

静态变量的初始值是该变量的类型的默认值（第 5.2 节）。

出于明确赋值检查的目的，静态变量被视为初始已赋值。


5.1.2实例变量.706695

Powered By Gavin http://www.CSharpWeb.com

5.1.2 实例变量

未用 static 修饰符声明的字段称为实例变量。


5.1.2.1类中的实例变量.707063

Powered By Gavin http://www.CSharpWeb.com

5.1.2.1 类中的实例变量

类的实例变量在创建该类的新实例时开始存在，在所有对该实例的引用都已终止，并且已执行了该实例的析构函数（若有）时终止。

类实例变量的初始值是该变量的类型的默认值（第 5.2 节）。

出于明确赋值检查的目的，类的实例变量被视为初始已赋值。


5.1.2.2结构中的实例变量.707686

Powered By Gavin http://www.CSharpWeb.com

5.1.2.2 结构中的实例变量

结构的实例变量与它所属的结构变量具有完全相同的生存期。换言之，当结构类型的变量开始存在或停止存在时，该结构的实例变量也随之存在或消失。

结构的实例变量与包含它的结构变量具有相同的初始赋值状态。换言之，当结构变量本身被视为初始已赋值时，它的实例变量也被视为初始已赋值。而当结构变量被视为初始未赋值时，它的实例变量同样被视为未赋值。


5.1.3数组元素.708374

Powered By Gavin http://www.CSharpWeb.com

5.1.3 数组元素

数组的元素在创建数组实例时开始存在，在没有对该数组实例的引用时停止存在。

每个数组元素的初始值都是其数组元素类型的默认值（第 5.2 节）。

出于明确赋值检查的目的，数组元素被视为初始已赋值。


5.1.4值参数.708955

Powered By Gavin http://www.CSharpWeb.com

5.1.4 值参数

未用 ref 或 out 修饰符声明的参数为值参数。

值参数在调用该参数所属的函数成员（方法、实例构造函数、访问器或运算符）（第 7.4 节）时开始存在，并用调用中给定的自变量的值初始化。当返回该函数成员时值参数停止存在。

出于明确赋值检查的目的，值参数被视为初始已赋值。


5.1.5引用参数.709669

5.1.5 引用参数

用 ref 修饰符声明的参数是引用参数。

引用参数不创建新的存储位置。相反，引用参数表示的是那个在对该函数成员调用中被当作“自变量”的变量所表示的同一个存储位置。因此，引用参数的值总是与基础变量相同。

下面的明确赋值规则适用于引用参数。注意第 5.1.6 节中描述的输出参数的不同规则。

· 变量在可以作为引用参数在函数成员调用中传递之前，必须已明确赋值（第 5.3 节）。

· 在函数成员内部，引用参数被视为初始已赋值。

在结构类型的实例方法或实例访问器内部，this 关键字的行为与该结构类型的引用参数完全相同（第 7.5.7 节）。


5.1.6输出参数.711075

5.1.6 输出参数

用 out 修饰符声明的参数是输出参数。

输出参数不创建新的存储位置。相反，输出参数表示的是那个在对该函数成员调用中被当作“自变量”的变量所表示的同一个存储位置。因此，输出参数的值总是与基础变量相同。

下面的明确赋值规则应用于输出参数。注意第 5.1.5 节中描述的引用参数的不同规则。

· 变量在可以作为输出参数在函数成员调用中传递之前不一定要明确赋值。

· 在正常完成函数成员调用之后，每个作为输出参数传递的变量都被认为在该执行路径中已赋值。

· 在函数成员内部，输出参数被视为初始未赋值。

· 函数成员的每个输出参数在该函数成员正常返回前都必须已明确赋值（第 5.3 节）。

在结构类型的实例构造函数内部，this 关键字的行为与结构类型的输出参数完全相同（第 7.5.7 节）。


5.1.7局部变量.712999

5.1.7 局部变量

局部变量是通过局部变量声明来声明的，此声明可以出现在块、for 语句、switch 语句或 using 语句中。

局部变量的生存期是程序执行过程中的某一“段”，在此期间，一定会为该局部变量保留存储。此生存期从进入与它关联的块、for 语句、switch 语句或 using 语句开始，一直延续到对应的块、for 语句、switch 语句或 using 语句的执行以任何方式结束为止。（进入封闭块或调用方法会挂起（但不会结束）当前的块、for 语句、switch 语句或 using 语句的执行。）如果以递归方式进入父块、for 语句、switch 语句或 using 语句，则每次都创建局部变量的新实例，并且重新计算它的局部变量初始值设定项（如果有的话）。

局部变量不自动初始化，因此没有默认值。出于明确赋值检查的目的，局部变量被视为初始未赋值。局部变量声明可包括局部变量初始值设定项，在此情况下变量被视为在它的整个范围内（局部变量初始值设定项中提供的表达式内除外）已明确赋值。

在局部变量的范围内，在局部变量声明符之前的文本位置引用该局部变量是编译时错误。

局部变量的实际生存期依赖于具体实现。例如，编译器可能静态地确定块中的某个局部变量只用于该块的一小部分。根据这种分析，编译器生成的代码可能会提前回收该变量的存储（相对于包含该变量的生存期）。

局部引用变量所引用的存储的回收与该局部引用变量（第 3.9 节）的生存期无关。

foreach 语句和 try 语句的特定 catch 子句也声明局部变量。对于 foreach 语句，局部变量是一个迭代变量（第 8.8.4 节）。对于特定的 catch 子句，局部变量是一个异常变量（第 8.10 节）。foreach 语句或特定的 catch 子句所声明的局部变量被视为在它的整个范围内已明确赋值。


5.2默认值.715085

5.2 默认值

以下类别的变量自动初始化为它们的默认值：

· 静态变量。

· 类实例的实例变量。

· 数组元素。

变量的默认值取决于该变量的类型，并按下面这样确定：

· 对于值类型的变量，默认值与该值类型的默认构造函数（第 4.1.1 节）所计算的值相同。

· 对于“引用类型”的变量，默认值为 null。

初始化为默认值的实现方法一般是让内存管理器或垃圾回收器在分配内存以供使用之前，将内存初始化为“所有位归零”（all-bits-zero）。由于这个原因，使用所有位归零来表示空 (null) 引用很方便。


5.3明确赋值.717034

5.3 明确赋值

在函数成员可执行代码中的给定位置，如果编译器可通过静态流程分析证明变量已自动初始化或已成为至少一个赋值的目标，则称该变量已明确赋值。明确赋值的规则为：

· 初始已赋值的变量（第 5.3.1 节）总是被视为已明确赋值。

· 如果所有可能通向给定位置的执行路径都至少包含以下内容之一，则初始未赋值的变量（第 5.3.2 节）被视为在该位置已明确赋值。

· 将变量作为左操作数的简单赋值（第 7.13.1 节）。

· 将变量作为输出参数传递的调用表达式（第 7.5.5 节）或对象创建表达式（第 7.5.10.1 节）。

· 对于局部变量，包含变量初始值设定项的局部变量声明（第 8.5 节）。

关于对一个结构类型变量的实例变量是否明确赋值，既可个别地也可作为整体进行跟踪。除了上述规则，下面的规则也应用于结构类型变量及其实例变量：

· 如果一个实例变量的包含它的那个结构类型变量被视为已明确赋值，则该实例变量被视为已明确赋值。

· 如果一个结构类型变量的每个实例变量都被视为已明确赋值，则该结构类型变量被视为已明确赋值。

在下列上下文中要求实施明确赋值：

· 变量必须在获取其值的每个位置都已明确赋值。这确保了从来不会出现未定义的值。变量在表达式中出现被视为要获取该变量的值，除非当

· 该变量为简单赋值的左操作数，

· 该变量作为输出参数传递，或者

· 该变量为结构类型变量并作为成员访问的左操作数出现。

· 变量必须在它作为引用参数传递的每个位置都已明确赋值。这确保了被调用的函数成员可以将引用参数视为初始已赋值。

· 函数成员的所有输出参数必须在函数成员返回的每个位置都已明确赋值，返回位置包括通过 return 语句实现的返回，或者通过执行语句到达函数成员体结尾的返回。这确保了函数成员不在输出参数中返回未定义的值，从而使编译器能够把一个对函数成员的调用当作对某些变量的赋值，这些变量在该调用中被当作输出参数传递。

· 结构类型实例构造函数的 this 变量必须在该实例构造函数返回的每个位置明确赋值。


5.3.1初始已赋值变量.721802

5.3.1 初始已赋值变量

以下类别的变量属于初始已赋值变量：

· 静态变量。

· 类实例的实例变量。

· 初始已赋值结构变量的实例变量。

· 数组元素。

· 值参数。

· 引用参数。

· 在 catch 子句或 foreach 语句中声明的变量。


5.3.2初始未赋值变量.723702

5.3.2 初始未赋值变量

以下类别的变量属于初始未赋值变量：

· 初始未赋值结构变量的实例变量。

· 输出参数，包括结构实例构造函数的 this 变量。

· 局部变量，在 catch 子句或 foreach 语句中声明的那些除外。


5.3.3确定明确赋值的细则.724880

5.3.3 确定明确赋值的细则

为了确定每个已使用变量都已明确赋值，编译器必须使用与本节中描述的进程等效的进程。

编译器处理每个具有一个或多个初始未赋值变量的函数成员的体。对于每个初始未赋值的变量 v，编译器在函数成员中的下列每个点上确定 v 的明确赋值状态：

· 在每个语句的开头处

· 在每个语句的结束点（第 8.1 节）

· 在每个将控制转移到另一个语句或语句结束点的 arc 上

· 在每个表达式的开头处

· 在每个表达式的结尾处

v 的明确赋值状态可以是：

· 明确赋值。这表明在能达到该点的所有可能的控制流上，v 都已赋值。

· 未明确赋值。当在 bool 类型表达式结尾处确定变量的状态时，未明确赋值的变量的状态可能（但不一定）属于下列子状态：

· 在 true 表达式后明确赋值。此状态表明如果该布尔表达式计算为 true，则 v 是明确赋值的，但如果布尔表达式计算为 false，则不一定要赋值。

· 在 false 表达式后明确赋值。此状态表明如果该布尔表达式计算为 false，则 v 是明确赋值的，但如果布尔表达式计算为 true，则不一定要赋值。

下列规则控制变量 v 的状态在每个位置是如何确定的。


5.3.3.1一般语句规则.728463

5.3.3.1 一般语句规则

· v 在函数成员体的开头处不是明确赋值的。

· v 在任何无法访问的语句的开头处都是明确赋值的。

· 在任何其他语句开头处，为了确定 v 的明确赋值状态，请检查以该语句开头处为目标的所有控制流转移上的 v 的明确赋值状态。当且仅当 v 在所有此类控制流转移上是明确赋值的时，v 才在该语句的开始处明确赋值。确定可能的控制流转移集的方法与检查语句可访问性的方法（第 8.1 节）相同。

· 在 block、checked、unchecked、if、while、do、for、foreach、lock、using 或 switch 等语句的结束点处，为了确定 v 的明确赋值状态，需检查以该语句结束点为目标的所有控制流转移上的 v 的明确赋值状态。如果 v 在所有此类控制流转移上是明确赋值的，则 v 在该语句结束点明确赋值。否则，v 在语句结束点处不是明确赋值的。确定可能的控制流转移集的方法与检查语句可访问性的方法（第 8.1 节）相同。


5.3.3.2块语句、checked和unchecked语句.731478

Powered By Gavin http://www.CSharpWeb.com

5.3.3.2 块语句、checked 和 unchecked 语句

在指向位于某块中语句列表的第一个语句（如果语句列表为空，则指向该块的结束点）的控制转移上，v 的明确赋值状态与块语句、checked 或 unchecked 语句之前的 v 的明确赋值状态相同。


5.3.3.3表达式语句.732404

Powered By Gavin http://www.CSharpWeb.com

5.3.3.3 表达式语句

对于由表达式 expr 组成的表达式语句 stmt：

· v 在 expr 的开头处与在 stmt 的开头处具有相同的明确赋值状态。

· 如果 v 在 expr 的结尾处明确赋值，则它在 stmt 的结束点也明确赋值；否则，它在 stmt 的结束点也不明确赋值。


5.3.3.4声明语句.734028

Powered By Gavin http://www.CSharpWeb.com

5.3.3.4 声明语句

· 如果 stmt 是不带有初始值设定项的声明语句，则 v 在 stmt 的结束点与在 stmt 的开头处具有相同的明确赋值状态。

· 如果 stmt 是带有初始值设定项的声明语句，则确定 v 的明确赋值状态时可把 stmt 当作一个语句列表，其中每个带有初始值设定项的声明对应一个赋值语句（按声明的顺序）。


5.3.3.5if语句.735520

5.3.3.5 if 语句

对于具有以下形式的 if 语句 stmt：

if ( expr ) then-stmt else else-stmt

· v 在 expr 的开头处与在 stmt 的开头处具有相同的明确赋值状态。

· 如果 v 在 expr 的结尾处明确赋值，则它在指向 then-stmt 和 else-stmt 或指向 stmt 的结束点（如果没有 else 子句）的控制流转移上是明确赋值的。

· 如果 v 在 expr 的结尾处具有“在 true 表达式后明确赋值”状态，则它在指向 then-stmt 的控制流转移上是明确赋值的，在指向 else-stmt 或指向 stmt 的结束点（如果没有 else 子句）的控制流转移上不是明确赋值的。

· 如果 v 在 expr 的结尾处具有“在 false 表达式后明确赋值”状态，则它在指向 else-stmt 的控制流转移上是明确赋值的，在指向 then-stmt 的控制流转移上不是明确赋值的。此后，当且仅当它在 then-stmt 的结束点是明确赋值的时，它在 stmt 的结束点才是明确赋值的。

· 否则，认为 v 在指向 then-stmt 或 else-stmt，或指向 stmt 的结束点（如果没有 else 子句）的控制流转移上都不是明确赋值的。


5.3.3.6switch语句.739749

Powered By Gavin http://www.CSharpWeb.com

5.3.3.6 switch 语句

在带有控制表达式 expr 的 switch 语句 stmt 中：

· 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 在指向可访问的 switch block 语句列表的控制流转移上，v 的明确赋值状态就是它在 expr 结尾处的明确赋值状态。


5.3.3.7while语句.741429

5.3.3.7 while 语句

对于具有以下形式的 while 语句 stmt：

while ( expr ) while-body

· v 在 expr 的开头处与在 stmt 的开头处具有相同的明确赋值状态。

· 如果 v 在 expr 的结尾处明确赋值，则它在指向 while-body 和指向 stmt 结束点的控制流转移上是明确赋值的。

· 如果 v 在 expr 的结尾处具有“在 true 表达式后明确赋值”状态，则它在指向 while-body 的控制流转移上是明确赋值的，但在 stmt 的结束点处不是明确赋值的。

· 如果 v 在 expr 的结尾处具有“在 false 表达式后明确赋值”状态，则它在指向 stmt 的结束点的控制流转移上是明确赋值的，但在指向 while-body 的控制流转移上不是明确赋值的。


5.3.3.8do语句.744480

5.3.3.8 do 语句

对于具有以下形式的 do 语句 stmt：

do do-body while ( expr ) ;

· v 在从 stmt 的开头处到 do-body 的控制流转移上的明确赋值状态与在 stmt 的开头处的状态相同。

· v 在 expr 的开头处与在 do-body 的结束点具有相同的明确赋值状态。

· 如果 v 在 expr 的结尾处是明确赋值的，则它在指向 stmt 的结束点的控制流转移上是明确赋值的。

· 如果 v 在 expr 的结尾处的状态为“在 false 表达式后明确赋值”，则它在指向 stmt 的结束点的控制流转移上是明确赋值的。


5.3.3.9for语句.747159

5.3.3.9 for 语句

对具有以下形式的 for 语句进行的明确赋值检查：

for ( for-initializer ; for-condition ; for-iterator ) embedded-statement

就如执行下列语句一样：

{

for-initializer ;

while ( for-condition ) {

embedded-statement ;

for-iterator ;

}

}

如果 for 语句中省略了 for-condition，则在确定关于明确赋值的状态时，可把上述展开语句列表中的 for-condition 当作 true。


5.3.3.10break、continue和goto语句.749082

Powered By Gavin http://www.CSharpWeb.com

5.3.3.10 break、continue 和 goto 语句

由 break、continue 或 goto 语句引起的控制流转移上的 v 的明确赋值状态与它在该语句开头处的明确赋值状态是一样的。


5.3.3.11throw语句.749924

Powered By Gavin http://www.CSharpWeb.com

5.3.3.11 throw 语句

对于具有以下形式的语句 stmt

throw expr ;

位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。


5.3.3.12return语句.750962

5.3.3.12 return 语句

对于具有以下形式的语句 stmt

return expr ;

· 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 如果 v 是输出参数，则它必须在下列两个位置之一被明确赋值：

· 在 expr 之后

· 或者在包含 return 语句的 try-finally 或 try-catch-finally 的 finally 块的结尾处。


5.3.3.13try-catch语句.753053

5.3.3.13 try-catch 语句

对于具有以下形式的语句 stmt：

try try-block

catch(...) catch-block-1

...

catch(...) catch-block-n

· 位于 try 块开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 位于 catch-block-i（对于所有的 i）开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 当且仅当 v 在 try-block 和每个 catch-block-i（每个 i 从 1 到 n）的结束点明确赋值时，stmt 结束点处的 v 的明确赋值状态才是明确赋值的。


5.3.3.14try-finally语句.756199

5.3.3.14 try-finally 语句

对于具有以下形式的 try 语句 stmt：

try try-block finally finally-block

· 位于 try 块开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 位于 finally 块开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 当且仅当下列条件中至少有一个为真时，位于 stmt 结束点的 v 的明确赋值状态才是明确赋值的：

· v 在 try 块的结束点明确赋值

· v 在 finally 块的结束点明确赋值

如果控制流转移（例如，goto 语句）从 try 块内开始，在 try 块外结束，那么如果 v 在 finally 块的结束点明确赋值，v 也被认为在该控制流转移上明确赋值。（这不是必要条件，如果 v 由于其他原因在该控制流转移上明确赋值，则它仍被视为明确赋值。）


5.3.3.15try-catch-finally语句.759852

5.3.3.15 try-catch-finally 语句

对于具有以下形式的 try-catch-finally 语句：

try try-block

catch(...) catch-block-1

...

catch(...) catch-block-n

finally finally-block

在进行明确赋值分析时，可把该语句当作包含了 try-catch 语句的 try-finally 语句，如下所示：

try {

try try-block

catch(...) catch-block-1

...

catch(...) catch-block-n

}

finally finally-block

下面的示例说明 try 语句（第 8.10 节）的不同块如何影响明确赋值状态。

class A

{

static void F() {

int i, j;

try {

goto LABEL;

// neither i nor j definitely assigned

i = 1;

// i definitely assigned

}

catch {

// neither i nor j definitely assigned

i = 3;

// i definitely assigned

}

finally {

// neither i nor j definitely assigned

j = 5;

// j definitely assigned

}

// i and j definitely assigned

LABEL;

// j definitely assigned

}

}


5.3.3.16foreach语句.764541

5.3.3.16 foreach 语句

对于具有以下形式的 foreach 语句 stmt：

foreach ( type identifier in expr ) embedded-statement

· 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 在指向 embedded-statement 或指向 stmt 结束点处的控制流转移上，v 的明确赋值状态与位于 expr 结尾处的 v 的状态相同。


5.3.3.17using语句.766618

5.3.3.17 using 语句

对于具有以下形式的 using 语句 stmt：

using ( resource-acquisition ) embedded-statement

· 位于 resource-acquisition 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 在指向 embedded-statement 的控制流转移上，v 的明确赋值状态与位于 resource-acquisition 结尾处的 v 的状态相同。


5.3.3.18lock语句.768807

5.3.3.18 lock 语句

对于具有以下形式的 lock 语句 stmt：

lock ( expr ) embedded-statement

· 位于 expr 开头处的 v 的明确赋值状态与位于 stmt 开头处的 v 的明确赋值状态相同。

· 在指向 embedded-statement 的控制流转移上，v 的明确赋值状态与位于 expr 结尾处的 v 的状态相同。


5.3.3.19简单表达式的一般规则.770712

Powered By Gavin http://www.CSharpWeb.com

5.3.3.19 简单表达式的一般规则

下列规则应用于这些表达式：文本（第 7.5.1 节）、简单名称（第 7.5.2 节）、成员访问表达式（第 7.5.4 节）、非索引 base 访问表达式（第 7.5.8 节）和 typeof 表达式（第 7.5.11 节）。

位于此类表达式结尾处的 v 的明确赋值状态与位于表达式开头处的 v 的明确赋值状态相同。


5.3.3.20带有嵌入表达式的表达式的一般规则.771581

5.3.3.20 带有嵌入表达式的表达式的一般规则

下列规则应用于这些表达式：带括号的表达式（第 7.5.3 节），元素访问表达式（第 7.5.6 节），带有索引的 base 访问表达式（第 7.5.8 节），增量和减量表达式（第 7.5.9 节）、（第 7.6.5 节），强制转换表达式（第 7.6.6 节），一元 +、-、~、* 表达式，二元 +、-、*、/、%、<<、>>、<、<=、>、>=、==、!=、is、as、&、|、^ 表达式（第 7.7 节、第 7.8 节、第 7.9 节、第 7.10 节），复合赋值表达式（第 7.13.2 节），checked 和 unchecked 表达式（第 7.5.12 节），数组和委托创建表达式（第 7.5.10 节）。

这些表达式的每一个都有一个和多个按固定顺序无条件计算的子表达式。例如，二元运算符 % 先计算运算符左边的值，然后计算右边的值。索引操作先计算索引表达式，然后按从左到右的顺序计算每个索引表达式。对于具有子表达式 expr1，expr2，...，exprn 的表达式 expr，按下列顺序计算：

· 位于 expr1 开头处的 v 的明确赋值状态与位于 expr 开头处的 v 的明确赋值状态相同。

· 位于 expri（i 大于 1）开头处的 v 的明确赋值状态与位于 expr(i-1) 结尾处的 v 的明确赋值状态相同。

· 位于 expr 结尾处的 v 的明确赋值状态与位于 exprn 结尾处的 v 的明确赋值状态相同。


5.3.3.21调用表达式和对象创建表达式.775360

5.3.3.21 调用表达式和对象创建表达式

对于具有以下形式的调用表达式 expr：

primary-expression ( arg1 , arg2 , ¼ , argn )

或具有以下形式的对象创建表达式：

new type ( arg1 , arg2 , ¼ , argn )

· 对于调用表达式，位于 primary-expression 之前的 v 的明确赋值状态与位于 expr 之前的 v 的状态相同。

· 对于调用表达式，位于 arg1 之前的 v 的明确赋值状态与位于 primary-expression 之后的 v 的明确赋值状态相同。

· 对于对象创建表达式，位于 arg1 之前的 v 的明确赋值状态与位于 expr 之前的 v 的状态相同。

· 对于每一个参数 argi，位于 argi 之后的 v 的明确赋值状态由正则表达式规则决定，其中忽略所有的 ref 或 out 修饰符。

· 对于每一个 i 大于 1 的参数 argi，位于 argi 之前的 v 的明确赋值状态与位于 argi-1 之后的 v 的状态相同。

· 如果变量 v 是被作为 out 参数传送（即，形式为“out v”的参数），则无论将它用作哪一个 argi，在 expr 之后，v 的状态是明确赋值的。否则，位于 expr 之后的 v 的状态与位于 argn 之后的 v 的状态相同。


5.3.3.22简单赋值表达式.780069

Powered By Gavin http://www.CSharpWeb.com

5.3.3.22 简单赋值表达式

对于具有形式 w = expr-rhs 的表达式 expr：

· 位于 expr-rhs 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。

· 如果 w 与 v 是同一变量，则位于 expr 之后的 v 的明确赋值状态是明确赋值的。否则，位于 expr 之后的 v 的明确赋值状态与位于 expr-rhs 之后的 v 的明确赋值状态相同。


5.3.3.24表达式.790455

5.3.3.24 || 表达式

对于形式为 expr-first || expr-second 的表达式 expr：

· 位于 expr-first 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。

· 如果位于 expr-first 之后的 v 的状态是明确赋值的或“在 false 表达式后明确赋值”，则位于 expr-second 之前的 v 的明确赋值状态是明确赋值的。否则，它就不是明确赋值的。

· 位于 expr 之后的 v 的明确赋值状态取决于：

· 如果在 expr-first 之后，v 的状态是明确赋值的，则在 expr 之后的 v 的状态也是明确赋值的。

· 否则，如果位于 expr-second 之后的 v 的状态是明确赋值的，而且位于 expr-first 之后的 v 的状态为“在 true 表达式后明确赋值”，则位于 expr 之后的 v 的状态是明确赋值的。

· 否则，如果位于 expr-second 之后的 v 的状态是明确赋值的或是“在 false 表达式后明确赋值”，则位于 expr 之后的 v 的状态是“在 false 表达式后明确赋值”。

· 否则，如果位于 expr-first 之后的 v 的状态是“在 true 表达式后明确赋值”，而且位于 expr-second 之后的 v 的状态是“在 true 表达式后明确赋值”，则位于 expr 之后的 v 的状态是“在 true 表达式后明确赋值”。

· 否则，在 expr 之后，v 的状态就不是明确赋值的。

在下面的示例中，

class A

{

static void G(int x, int y) {

int i;

if (x >= 0 || (i = y) >= 0) {

// i not definitely assigned

}

else {

// i definitely assigned

}

// i not definitely assigned

}

}

变量 i 被视为在 if 语句的一个嵌入语句中已明确赋值，而在另一个嵌入语句中未明确赋值。在 G 方法中的 if 语句中，由于总是在第二个嵌入语句执行前执行表达式 (i = y)，因此变量 i 在第二个嵌入语句中已明确赋值。相反，在第一个嵌入语句中，变量 i 的状态不是明确赋值的，因为 x >= 0 可能已测试为 true，从而导致变量 i 未赋值。


5.3.3.25!表达式.798447

5.3.3.25 ! 表达式

对于形式为 ! expr-operand 的表达式 expr：

· 位于 expr-operand 之前的 v 的明确赋值状态与位于 expr 之前的 v 的明确赋值状态相同。

· 位于 expr 之后的 v 的明确赋值状态取决于：

· 如果在 expr-operand 之后，v 的状态是明确赋值的，则在 expr 之后，v 的状态也是明确赋值的。

· 如果在 expr-operand 之后，v 的状态不是明确赋值的，则在 expr 之后，v 的状态也不是明确赋值的。

· 如果位于 expr-operand 之后的 v 的状态是“在 false 表达式后明确赋值”，则位于 expr 之后的 v 的状态是“在 true 表达式后明确赋值”。

· 如果位于 expr-operand 之后的 v 的状态是“在 true 表达式后明确赋值”，则位于 expr 之后的 v 的状态是“在 false 表达式后明确赋值”。


5.3.3.26表达式.803338

5.3.3.26 ?: 表达式

对于形式为 expr-cond ? expr-true : expr-false 的表达式 expr：

· 位于 expr-cond 之前的 v 的明确赋值状态与位于 expr 之前的 v 的状态相同。

· 当且仅当位于 expr-cond 之后的 v 的状态是明确赋值的或“在 true 表达式后明确赋值”时，位于 expr-true 之前的 v 的明确赋值状态才是明确赋值的。

当且仅当位于 expr-cond 之后的 v 的状态是明确赋值的或“在 false 表达式后明确赋值”时，位于 expr-false 之前的 v 的明确赋值状态才是明确赋值的。

位于 expr 之后的 v 的明确赋值状态取决于：

· 如果 expr-cond 是值为 true 的常数表达式（第 7.15 节），则位于 expr 之后的 v 的状态与位于 expr-true 之后的 v 的状态相同。

· 否则，如果 expr-cond 是值为 false 的常数表达式（第 7.15 节），则位于 expr 之后的 v 的状态与位于 expr-false 之后的 v 的状态相同。

· 否则，如果位于 expr-true 之后的 v 的状态是明确赋值的，而且位于 expr-false 之后的 v 的状态也是明确赋值的，则位于 expr 之后的 v 的状态是明确赋值的。

· 否则，在 expr 之后，v 的状态不是明确赋值的。


5.4变量引用.809069

5.4 变量引用

“变量引用”是一个“表达式”，它被归类为一个变量。“变量引用”表示一个存储位置，访问它可以获取当前值以及存储新值。

variable-reference:（变量引用：）

expression（表达式）

在 C 和 C++ 中，“变量引用”称为 lvalue。


5.5变量引用的原子性.809858

Powered By Gavin http://www.CSharpWeb.com

5.5 变量引用的原子性

下列数据类型的读写是原子的：bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。除此之外，当枚举类型的基础类型的属于上述类型之一时，对它的读写也是原子的。其它类型的读写，包括 long、ulong、double 和 decimal 以及用户定义类型，都不保证是原子的。除专为该目的设计的库函数以外，对于增量或减量这类操作也不能保证进行原子的读、修改和写。


6.转换.811177

6. 转换

转换使一种类型的表达式可以被视为另一种类型。转换可以是隐式或显式，这将确定是否需要显式地强制转换。例如，从 int 类型到 long 类型的转换是隐式的，因此 int 类型的表达式可隐式地按 long 类型处理。从 long 类型到 int 类型的反向转换是显式的，因此需要显式地强制转换。

int a = 123;

long b = a; // implicit conversion from int to long

int c = (int) b; // explicit conversion from long to int

某些转换由语言定义。程序也可以定义自己的转换（第 6.4 节）。


6.1隐式转换.812317

6.1 隐式转换

下列转换属于隐式转换：

· 标识转换

· 隐式数值转换

· 隐式枚举转换

· 隐式引用转换

· 装箱转换

· 隐式常数表达式转换

· 用户定义的隐式转换

隐式转换可以在各种情况下发生，包括函数成员调用（第 7.4.3 节）、强制转换表达式（第 7.6.6 节）以及赋值（第 7.13 节）。

预定义的隐式转换总是会成功，从来不会导致引发异常。正确设计的用户定义隐式转换同样应表现出这些特性。


6.1.1标识转换.814399

Powered By Gavin http://www.CSharpWeb.com

6.1.1 标识转换

标识转换是在同一类型（可为任何类型）内进行转换。这种转换的存在，仅仅是为了使已具有所需类型的实体可被认为是可转换的（转换为该类型）。


6.1.2隐式数值转换.814892

6.1.2 隐式数值转换

隐式数值转换为：

· 从 sbyte 到 short、int、long、float、double 或 decimal。

· 从 byte 到 short、ushort、int、uint、long、ulong、float double 或 decimal。

· 从 short 到 int、long、float、double 或 decimal。

· 从 ushort 到 int、uint、long、ulong、float、double 或 decimal。

· 从 int 到 long、float、double 或 decimal。

· 从 uint 到 long、ulong、float、double 或 decimal。

· 从 long 到 float、double 或 decimal。

· 从 ulong 到 float、double 或 decimal。

· 从 char 到 ushort、int、uint、long、ulong、float、double 或 decimal。

· 从 float 到 double。

从 int、uint、long 或 ulong 到 float 以及从 long 或 ulong 到 double 的转换可能导致精度损失，但决不会影响到它的数量级。其他的隐式数值转换决不会丢失任何信息。

不存在向 char 类型的隐式转换，因此其它整型的值不会自动转换为 char 类型。


6.1.3隐式枚举转换.820597

Powered By Gavin http://www.CSharpWeb.com

6.1.3 隐式枚举转换

隐式枚举转换允许将十进制整数 0 转换为任何枚举类型。


6.1.4隐式引用转换.821014

6.1.4 隐式引用转换

隐式引用转换为：

· 从任何引用类型到 object。

· 从任何类类型 S 到任何类类型 T（前提是 S 是从 T 派生的）。

· 从任何类类型 S 到任何接口类型 T（前提是 S 实现了 T）。

· 从任何接口类型 S 到任何接口类型 T（前提是 S 是从 T 派生的）。

· 从元素类型为 SE 的数组类型 S 到元素类型为 TE 的数组类型 T（前提是以下所列条件均为真）：

· S 和 T 只是元素类型不同。换言之，S 和 T 具有相同的维数。

· SE 和 TE 都是引用类型。

· 存在从 SE 到 TE 的隐式引用转换。

· 从任何数组类型到 System.Array。

· 从任何委托类型到 System.Delegate。

· 从 null 类型到任何引用类型。

隐式引用转换是指一类引用类型之间的转换，这种转换总是可以成功，因此不需要在运行时进行任何检查。

引用转换无论是隐式的还是显式的，都不会更改被转换的对象的引用标识。换言之，虽然引用转换可能更改引用的类型，但决不会更改所引用对象的类型或值。


6.1.5装箱转换.825445

Powered By Gavin http://www.CSharpWeb.com

6.1.5 装箱转换

装箱转换允许将值类型隐式转换为引用类型。将值类型的一个值装箱包括以下操作：分配一个对象实例，然后将值类型的值复制到该实例中。

有关装箱转换的介绍详见第 4.3.1 节。


6.1.6隐式常数表达式转换.825956

Powered By Gavin http://www.CSharpWeb.com

6.1.6 隐式常数表达式转换

隐式常数表达式转换允许进行以下转换：

· int 类型的常数表达式（第 7.15 节）可以转换为 sbyte、byte、short、ushort、uint 或 ulong 类型（前提是常数表达式的值在目标类型的范围内）。

· long 类型的常数表达式可以转换为 ulong 类型（前提是常数表达式的值不为负）。


6.1.7用户定义的隐式转换.827483

Powered By Gavin http://www.CSharpWeb.com

6.1.7 用户定义的隐式转换

用户定义的隐式转换由以下三部分组成：先是一个标准的隐式转换（可选）；然后是执行用户定义的隐式转换运算符，最后是另一个标准的隐式转换（可选）。计算用户定义的转换的精确规则详见第 6.4.3 节中的说明。


6.2显式转换.828015

6.2 显式转换

下列转换属于显式转换：

· 所有隐式转换。

· 显式数值转换。

· 显式枚举转换。

· 显式引用转换。

· 显式接口转换。

· 取消装箱转换。

· 用户定义的显式转换。

显式转换可在强制转换表达式（第 7.6.6 节）中出现。

显式转换集包括所有隐式转换。这意味着允许使用冗余的强制转换表达式。

不是隐式转换的显式转换是这样的一类转换：它们不能保证总是成功，知道有可能丢失信息，变换前后的类型显著不同，以至值得使用显式表示法。


6.2.1显式数值转换.830259

6.2.1 显式数值转换

显式数值转换是指从一个数值类型到另一个数值转换的转换，此转换不能用已知的隐式数值转换（第 6.1.2 节）实现，它包括：

· 从 sbyte 到 byte、ushort、uint、ulong 或 char。

· 从 byte 到 sbyte 和 char。

· 从 short 到 sbyte、byte、ushort、uint、ulong 或 char。

· 从 ushort 到 sbyte、byte、short 或 char。

· 从 int 到 sbyte、byte、short、ushort、uint、ulong 或 char。

· 从 uint 到 sbyte、byte、short、ushort、int 或 char。

· 从 long 到 sbyte、byte、short、ushort、int、uint、ulong 或 char。

· 从 ulong 到 sbyte、byte、short、ushort、int、uint、long 或 char。

· 从 char 到 sbyte、byte 或 short。

· 从 float 到 sbyte、byte、short、ushort、int、uint、long、ulong、char 或 decimal。

· 从 double 到 sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 decimal。

· 从 decimal 到 sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 double。

由于显式转换包括所有隐式和显式数值转换，因此总是可以使用强制转换表达式（第 7.6.6 节）从任何数值类型转换为任何其他的数值类型。

显式数值转换有可能丢失信息或导致引发异常。显式数值转换按下面所述处理：

· 对于从一个整型到另一个整型的转换，处理取决于该转换发生时的溢出检查上下文（第 7.5.12 节）：

· 在 checked 上下文中，如果源操作数的值在目标类型的范围内，转换就会成功，但如果源操作数的值在目标类型的范围外，则会引发 System.OverflowException。

· 在 unchecked 上下文中，转换总是会成功并按下面所述进行。

如果源类型大于目标类型，则截断源值（截去源值中容不下的最高有效位）。然后将结果视为目标类型的值。

如果源类型小于目标类型，则源值或按符号扩展或按零扩展，以使它的大小与目标类型相同。如果源类型是有符号的，则使用按符号扩展；如果源类型是无符号的，则使用按零扩展。然后将结果视为目标类型的值。

如果源类型的大小与目标类型相同，则源值被视为目标类型的值。

· 对于从 decimal 到整型的转换，源值向零舍入到最接近的整数值，该整数值成为转换的结果。如果转换得到的整数值不在目标类型的范围内，则会引发 System.OverflowException。

· 对于从 float 或 double 到整型的转换，处理取决于发生该转换时的溢出检查上下文（第 7.5.12 节）：

· 在 checked 上下文中，如下所示进行转换：

如果操作数的值是 NaN 或无穷大，则引发 System.OverflowException。

否则，源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型的范围内，则该值就是转换的结果。

否则，引发 System.OverflowException。

· 在 unchecked 上下文中，转换总是会成功并按下面这样继续。

如果操作数的值是 NaN 或 infinite，则转换的结果是目标类型的一个未经指定的值。

否则，源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型的范围内，则该值就是转换的结果。

否则，转换的结果是目标类型的一个未经指定的值。

· 对于从 double 到 float 的转换，double 值舍入到最接近的 float 值。如果 double 值过小，无法表示为 float 值，则结果变成正零或负零。如果 double 值过大，无法表示为 float 值，则结果变成正无穷大或负无穷大。如果 double 值为 NaN，则结果仍然是 NaN。

· 对于从 float 或 double 到 decimal 的转换，源值转换为用 decimal 形式来表示，并且在需要时，将它在第 28 位小数位数上舍入到最接近的数字（第 4.1.7 节）。如果源值过小，无法表示为 decimal，则结果变成零。如果源值为 NaN、无穷大或者太大而无法表示为 decimal 值，则将引发 System.OverflowException。

· 对于从 decimal 到 float 或 double 的转换，decimal 值舍入到最接近的 double 或 float 值。虽然这种转换可能会损失精度，但决不会导致引发异常。


6.2.2显式枚举转换.844317

6.2.2 显式枚举转换

显式枚举转换为：

· 从 sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double 或 decimal 到任何枚举类型。

· 从任何枚举类型到 sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double 或 decimal。

· 从任何枚举类型到任何其他枚举类型。

两种类型之间的显式枚举转换是通过将任何参与的枚举类型都按该枚举类型的基础类型处理，然后在结果类型之间执行隐式或显式数值转换。例如，给定具有 int 基础类型的枚举类型 E，从 E 到 byte 的转换按从 int 到 byte 的显式数值转换（第 6.2.1 节）处理，而从 byte 到 E 的转换按从 byte 到 int 的隐式数值转换（第 6.1.2 节）处理。


6.2.3显式引用转换.847570

6.2.3 显式引用转换

显式引用转换为：

· 从 object 到任何其他引用类型。

· 从任何类类型 S 到任何类类型 T（前提是 S 为 T 的基类）。

· 从任何类类型 S 到任何接口类型 T（前提是 S 未密封并且 S 不实现 T）。

· 从任何接口类型 S 到任何类类型 T（前提是 T 未密封或 T 实现 S）。

· 从任何接口类型 S 到任何接口类型 T（前提是 S 不是从 T 派生的）。

· 从元素类型为 SE 的数组类型 S 到元素类型为 TE 的数组类型 T（前提是以下所列条件均为真）：

· S 和 T 只是元素类型不同。换言之，S 和 T 具有相同的维数。

· SE 和 TE 都是引用类型。

· 存在从 SE 到 TE 的显式引用转换。

· 从 System.Array 以及它实现的接口到任何数组类型。

· 从 System.Delegate 以及它实现的接口到任何委托类型。

显式引用转换是那些需要运行时检查以确保它们正确的引用类型之间的转换。

为了使显式引用转换在运行时成功，源操作数的值必须为 null，或源操作数所引用的对象的实际类型必须是一个可通过隐式引用转换（第 6.1.4 节）转换为目标类型的类型。如果显式引用转换失败，则将引发 System.InvalidCastException。

引用转换无论是隐式的还是显式的，都不会更改被转换的对象的引用标识。换言之，虽然引用转换可能更改引用的类型，但决不会更改所引用对象的类型或值。


6.2.4取消装箱转换.852775

Powered By Gavin http://www.CSharpWeb.com

6.2.4 取消装箱转换

取消装箱转换允许将引用类型显式转换为值类型。一个取消装箱操作包括以下两个步骤：首先检查对象实例是否为给定值类型的一个装了箱的值，然后将该值从实例中复制出来。

有关取消装箱转换的进一步介绍详见第 4.3.2 节。


6.2.5用户定义的显式转换.853360

Powered By Gavin http://www.CSharpWeb.com

6.2.5 用户定义的显式转换

用户定义的显式转换由以下三个部分组成：先是一个标准的显式转换（可选），然后是执行用户定义的隐式或显式转换运算符，最后是另一个标准的显式转换（可选）。计算用户定义的转换的确切规则详见第 6.4.4 节中的说明。


6.3标准转换.853872

Powered By Gavin http://www.CSharpWeb.com

6.3 标准转换

标准转换是那些预先定义的转换，它们可以作为用户定义转换的组成部分出现。


6.3.1标准隐式转换.854275

6.3.1 标准隐式转换

下列隐式转换属于标准隐式转换：

· 标识转换（第 6.1.1 节）。

· 隐式数值转换（第 6.1.2 节）。

· 隐式引用转换（第 6.1.4 节）。

· 装箱转换（第 6.1.5 节）。

· 隐式常数表达式转换（第 6.1.6 节）。

标准隐式转换特别排除了用户定义的隐式转换。


6.3.2标准显式转换.855801

Powered By Gavin http://www.CSharpWeb.com

6.3.2 标准显式转换

标准显式转换包括所有的标准隐式转换以及一个显式转换的子集，该子集是由那些与已知的标准隐式转换反向的转换组成的。换言之，如果存在一个从 A 类型到 B 类型的标准隐式转换，则一定存在与其对应的两个标准显式转换（一个是从 A 类型到 B 类型，另一个是从 B 类型到 A 类型）。


6.4用户定义的转换.856586

Powered By Gavin http://www.CSharpWeb.com

6.4 用户定义的转换

C# 允许通过用户定义的转换来增加预定义的隐式和显式转换。用户定义的转换是通过在类类型和结构类型中声明转换运算符（第 10.9.3 节）引入的。


6.4.1允许的用户定义转换.857055

6.4.1 允许的用户定义转换

C# 只允许声明某些用户定义的转换。具体说来，不可能重新定义已存在的隐式或显式转换。仅当以下条件皆为真时，才允许类或结构声明从源类型 S 到目标类型 T 的转换：

· S 和 T 是不同的类型。

· S 和 T 中有一个属于类类型或结构类型，且包含了运算符的声明。

· S 和 T 都不是 object 类型或接口类型。

· T 不是 S 的基类，S 也不是 T 的基类。

关于用户定义转换的限制在第 10.9.3 节中有进一步讨论。


6.4.2用户定义转换的计算.858980

6.4.2 用户定义转换的计算

用户定义的转换将一个值从它所属的类型（称为源类型）转换为另一个类型（称为目标类型）。用户定义转换的计算集中在查找符合特定的源类型和目标类型的最精确的用户定义转换运算符。此确定过程分为几个步骤：

· 查找考虑从中使用用户定义的转换运算符的类和结构集。此集由源类型及其基类和目标类型及其基类组成（隐式假定只有类和结构可以声明用户定义的运算符，并且不属于类的类型不具有任何基类）。

· 由该类型集确定适用的用户定义转换运算符。一个转换运算符如满足下述条件就是适用的：必须可以通过执行标准转换（第 6.3 节）来使源类型转换为该运算符的操作数所要求的类型，并且必须可以通过执行标准转换来使运算符的结果类型转换为目标类型。

· 由适用的用户定义运算符集，明确地确定哪一个运算符是最精确的。概括地讲，最精确的运算符是操作数类型“最接近”源类型并且结果类型“最接近”目标类型的运算符。后面的章节定义了建立最精确的用户定义转换运算符的确切规则。

确定了最精确的用户定义转换运算符后，用户定义转换的实际执行包括三个步骤：

· 首先，如果需要，执行一个标准转换，将源类型转换为用户定义转换运算符的操作数所要求的类型。

· 然后，调用用户定义转换运算符以执行转换。

· 最后，如果需要，再执行一个标准转换，将用户定义转换运算符的结果类型转换为目标类型。

用户定义转换的计算从不涉及一个以上的用户定义转换运算符。换言之，从 S 类型到 T 类型的转换决不会首先执行从 S 到 X 的用户定义转换，然后执行从 X 到 T 的用户定义转换。

后面的章节给出了用户定义的隐式或显式转换的确切定义。这些定义使用下面的术语：

· 如果存在一个从 A 类型到 B 类型的标准隐式转换（第 6.3.1 节），并且 A 和 B 都不是接口类型，则称 A 被 B 包含、称 B 包含 A。

· 在一个类型集中，包含程度最大的类型是指这样的一个类型：它包含了该类型集中的所有其他类型。如果没有一个类型包含所有其他类型，则集中没有包含程度最大的类型。用更直观的话讲，包含程度最大的类型是集中“最大”的类型，即可将每个其他类型都隐式转换为该类型的一个类型。

· 在一个类型集中，被包含程度最大的类型是指这样一个类型：它被该类型集中的所有其他类型所包含。如果没有一个类型被所有其他类型包含，则集中没有被包含程度最大的类型。用更直观的话讲，被包含程度最大的类型是集中“最小的”类型，即可隐式转换为每个其他类型的一个类型。


6.4.3用户定义的隐式转换.863902

6.4.3 用户定义的隐式转换

从 S 类型到 T 类型的用户定义的隐式转换按下面这样处理：

· 查找类型集 D，将从该类型集考虑用户定义的转换运算符。此集合由 S（如果 S 是类或构造）、S 的基类（如果 S 是类）和 T（如果 T 是类或结构）组成。

· 查找适用的用户定义转换运算符集合 U。集合 U 由用户定义的隐式转换运算符组成，这些运算符是在 D 中的类或结构内声明的，用于从包含 S 的类型转换为被 T 包含的类型。如果 U 为空，则转换未定义并且发生编译时错误。

· 在 U 中查找运算符的最精确的源类型 SX：

· 如果 U 中的所有运算符都从 S 转换，则 SX 为 S。

· 否则，SX 在 U 中运算符的合并目标类型集中是被包含程度最大的类型。如果找不到这样的被包含程度最大的类型，则转换是不明确的，并且发生编译时错误。

· 在 U 中查找运算符的最精确的目标类型 TX：

· 如果 U 中的所有运算符都转换为 T，则 TX 为 T。

· 否则，TX 在 U 中运算符的合并目标类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型，则转换是不明确的，并且发生编译时错误。

· 如果 U 中正好含有一个从 SX 转换到 TX 的用户定义转换运算符，则这就是最精确的转换运算符。如果不存在此类运算符，或者如果存在多个此类运算符，则转换是不明确的，并且发生编译时错误。否则，将应用用户定义的转换：

· 如果 S 不是 SX，则先执行一个从 S 到 SX 的标准隐式转换。

· 调用最精确的用户定义转换运算符，以从 SX 转换到 TX。

· 如果 TX 不是 T，则再执行一个 TX 到 T 的标准隐式转换。


6.4.4用户定义的显式转换.869163

6.4.4 用户定义的显式转换

从 S 类型到 T 类型的用户定义的显式转换按下面这样处理：

· 查找类型集 D，将从该类型集考虑用户定义的转换运算符。该类型集由 S（如果 S 为类或结构）、S 的基类（如果 S 为类）、T（如果 T 为类或结构）和 T 的基类（如果 T 为类）组成。

· 查找适用的用户定义转换运算符集合 U。集合 U 由用户定义的隐式或显式转换运算符组成，这些运算符是在 D 中的类或结构内声明的，用于从包含 S 或被 S 包含的类型转换为包含 T 或被 T 包含的类型。如果 U 为空，则转换未定义并且发生编译时错误。

· 在 U 中查找运算符的最精确的源类型 SX：

· 如果 U 中的所有运算符都从 S 转换，则 SX 为 S。

· 否则，如果 U 中的所有运算符都从包含 S 的类型转换，则 SX 在这些运算符的合并源类型集中是被包含程度最大的类型。如果找不到最直接包含的类型，则转换是不明确的，并且发生编译时错误。

· 否则，SX 在 U 中运算符的合并源类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型，则转换是不明确的，并且发生编译时错误。

· 在 U 中查找运算符的最精确的目标类型 TX：

· 如果 U 中的所有运算符都转换为 T，则 TX 为 T。

· 否则，如果 U 中的所有运算符都转换为被 T 包含的类型，则 TX 在这些运算符的合并源类型集中是包含程度最大的类型。如果找不到这样的包含程度最大的类型，则转换是不明确的，并且发生编译时错误。

· 否则，TX 在 U 中运算符的合并目标类型集中是被包含程度最大的类型。如果找不到最直接包含的类型，则转换是不明确的，并且发生编译时错误。

· 如果 U 中正好含有一个从 SX 转换到 TX 的用户定义转换运算符，则这就是最精确的转换运算符。如果不存在此类运算符，或者如果存在多个此类运算符，则转换是不明确的，并且发生编译时错误。否则，将应用用户定义的转换：

· 如果 S 不是 SX，则先执行一个从 S 到 SX 的标准显式转换。

· 调用最精确的用户定义转换运算符，以从 SX 转换到 TX。

· 如果 TX 不是 T，则再执行一个从 TX 到 T 的标准显式转换。


7.表达式.875528

Powered By Gavin http://www.CSharpWeb.com

7. 表达式

表达式是一个运算符和操作数的序列。本章定义语法、操作数和运算符的计算顺序以及表达式的含义。


7.1表达式的分类.875889

7.1 表达式的分类

一个表达式可归类为下列类别之一：

· 值。每个值都有关联的类型。

· 变量。每个变量都有关联的类型，称为该变量的已声明类型。

· 命名空间。这类表达式只能出现在成员访问（第 7.5.4 节）的左边。在任何其他上下文中，归类为命名空间的表达式将导致编译时错误。

· 类型。这类表达式只能出现在成员访问（第 7.5.4 节）的左边，或作为 as 运算符（第 7.9.10 节）、is 运算符（第 7.9.9 节）或 typeof 运算符（第 7.5.11 节）的操作数。在任何其他上下文中，归类为类型的表达式将导致编译时错误。

· 方法组，它是一组重载方法，是成员查找（第 7.3 节）的结果。方法组可以有关联的实例表达式。当调用实例方法时，实例表达式的计算结果成为由 this（第 7.5.7 节）表示的实例。只能在调用表达式（第 7.5.5 节）或委托创建表达式（第 7.5.10.3 节）中使用方法组。在任何其他上下文中，归类为方法组的表达式将导致编译时错误。

· 属性访问。每个属性访问都有关联的类型，即该属性的类型。此外，属性访问可以有关联的实例表达式。当调用实例属性访问的访问器（get 或 set 块）时，实例表达式的计算结果成为由 this（第 7.5.7 节）表示的实例。

· 事件访问。每个事件访问都有关联的类型，即该事件的类型。此外，事件访问还可以有关联的实例表达式。事件访问可以作为 += 和 -= 运算符（第 7.13.3 节）的左操作数出现。在任何其他上下文中，归类为事件访问的表达式将导致编译时错误。

· 索引器访问。每个索引器访问都有关联的类型，即该索引器的元素类型。此外，索引器访问还可以有关联的实例表达式和关联的参数列表。当调用索引器访问的访问器（get 或 set 块）时，实例表达式的计算结果成为由 this（第 7.5.7 节）表示的实例，而参数列表的计算结果成为调用的参数列表。

· Nothing。这出现在当表达式是调用一个具有 void 返回类型的方法时。Nothing 类别的表达式仅在语句表达式（第 8.6 节）的上下文中有效。

表达式的最终结果绝不会是一个命名空间、类型、方法组或事件访问。恰如以上所述，这些类别的表达式是只能在特定上下文中使用的中间构造。

通过执行 get 访问器或 set 访问器，属性访问或索引器访问总是被重新归类为值。实际涉及哪个访问器由属性访问或索引器访问的上下文确定：如果访问是赋值的目标，则调用 set 访问器以赋新值（第 7.13.1 节）。否则调用 get 访问器以获取当前值（第 7.1.1 节）。


7.1.1表达式的值.880524

7.1.1 表达式的值

大多数含有表达式的构造最后都要求表达式表示一个值。在此情况下，如果实际的表达式表示命名空间、类型、方法组或 Nothing，则将发生编译时错误。但是，如果表达式表示属性访问、索引器访问或变量，则将它们隐式替换为相应的属性、索引器或变量的值：

· 变量的值只是当前存储在该变量所标识的存储位置的值。在可以获取变量的值之前，变量必须被视为已明确赋值（第 5.3 节），否则将发生编译时错误。

· 一个属性访问表达式的值通过调用属性的 get 访问器来获取。如果属性没有 get 访问器，则发生编译时错误。否则将执行一个函数成员调用（第 7.4.3 节），调用的结果成为属性访问表达式的值。

· 一个索引器访问表达式的值通过调用索引器的 get 访问器来获取。如果索引器没有 get 访问器，则发生编译时错误。否则将使用与索引器访问表达式关联的参数列表来执行函数成员调用（第 7.4.3 节），调用的结果成为索引器访问表达式的值。


7.2运算符.882323

7.2 运算符

表达式由操作数和运算符构成。表达式的运算符指示对操作数进行什么样的运算。运算符的示例包括 +、-、*、/ 和 new。操作数的示例包括文本、字段、局部变量和表达式。

有三类运算符：

· 一元运算符。一元运算符带一个操作数并使用前缀表示法（如 –x）或后缀表示法（如 x++）。

· 二元运算符。二元运算符带两个操作数并且全都使用中缀表示法（如 x + y）。

· 三元运算符。只有一个三元运算符 ?: 存在，它带三个操作数并使用中缀表示法 (c? x: y)。

表达式中运算符的计算顺序由运算符的优先级和顺序关联性（第 7.2.1 节）确定。

表达式中的操作数从左到右进行计算。例如，在 F(i) + G(i++) * H(i) 中，使用 i 的旧值调用 F 方法，然后使用 i 的旧值调用 G 方法，最后使用 i 的新值调用 H 方法。这与运算符的优先级无关。

某些运算符可以重载。运算符重载允许指定使用用户定义的运算符来执行某些运算，这些运算的操作数中至少有一个，甚至两个都属于用户定义的类或结构类型（第 7.2.2 节）。


7.2.1运算符的优先级和顺序关联性.884879

7.2.1 运算符的优先级和顺序关联性

当表达式包含多个运算符时，运算符的优先级控制各运算符的计算顺序。例如，表达式 x + y * z 按 x + (y * z) 计算，因为 * 运算符具有的优先级比 + 运算符高。运算符的优先级由运算符的关联语法产生式的定义确定。例如，一个增量表达式由以 + 或 - 运算符分隔的乘法表达式组成，因此给 + 和 - 运算符赋予的优先级比 *、/ 和 % 运算符低。

下表按照从最高到最低的优先级顺序概括了所有的运算符：

章节 | 类别 | 运算符
第 7.5 节 | 基本 | x.y f(x) a[x] x++ x-- new typeof checked unchecked
第 7.6 节 | 一元 | + - ! ~ ++x --x (T)x
第 7.7 节 | 乘法 | * / %
第 7.7 节 | 加法 | + -
第 7.8 节 | 移位 | << >>
第 7.9 节 | 关系和类型检测 | < > <= >= is as
第 7.9 节 | 相等 | == !=
第 7.10 节 | 逻辑 AND | &
第 7.10 节 | 逻辑 XOR | ^
第 7.10 节 | 逻辑 OR | |
第 7.11 节 | 条件 AND | &&
第 7.11 节 | 条件 OR | ||
第 7.12 节 | 条件 | ?:
第 7.13 节 | 赋值 | = *= /= %= += -= <<= >>= &= ^= |=

当操作数出现在具有相同优先级的两个运算符之间时，运算符的顺序关联性控制运算的执行顺序：

· 除了赋值运算符外，所有的二元运算符都向左顺序关联，意思是从左向右执行运算。例如，x + y + z 按 (x + y) + z 计算。

· 赋值运算符和条件运算符 (?:) 向右顺序关联，意思是从右向左执行运算。例如，x = y = z 按 x = (y = z) 计算。

优先级和顺序关联性都可以用括号控制。例如，x + y * z 先将 y 乘以 z 然后将结果与 x 相加，而 (x + y) * z 先将 x 与 y 相加，然后再将结果乘以 z。


7.2.2运算符重载.898806

7.2.2 运算符重载

所有一元和二元运算符都具有可自动用于任何表达式的预定义实现。除了预定义实现外，还可通过在类或结构（第 10.9 节）中设置 operator 声明来引入用户定义的实现。用户定义的运算符实现的优先级总是高于预定义运算符实现：仅当没有适用的用户定义运算符实现时才会考虑预定义运算符实现。

“可重载的一元运算符”有：

+ - ! ~ ++ -- true false

虽然不在表达式中显式使用 true 和 false，但仍将它们视为运算符，因为它们在某些表达式上下文中被调用：如布尔表达式（第 7.16 节）和涉及条件运算符（第 7.12 节）与条件逻辑运算符（第 7.11 节）的表达式。

“可重载的二元运算符”有：

+ - * / % & | ^ << >> == != > < >= <=

只有以上所列的运算符可以重载。具体说来，不可能重载成员访问、方法调用或 =、&&、||、?:、checked、unchecked、new、typeof、as 和 is 运算符。

当重载一个二元运算符时，也会隐式重载相应的赋值运算符（若有）。例如，运算符 * 的重载也是运算符 *= 的重载。第 7.13 节对此有进一步描述。请注意，赋值运算符本身 (=) 不能重载。赋值总是简单地将值按位复制到变量中。

强制转换运算（如 (T)x）通过提供用户定义的转换（第 6.4 节）来重载。

元素访问（如 a[x]）不被视为可重载的运算符。但是，可通过索引器（第 10.8 节）支持用户定义的索引。

在表达式中，使用运算符表示法来引用运算符，而在声明中，使用函数表示法来引用运算符。下表显示了一元运算符和二元运算符的运算符表示法和函数表示法之间的关系。在第一项中，op 表示任何可重载的一元前缀运算符。在第二项中，op 表示 ++ 和 -- 一元后缀运算符。在第三项中，op 表示任何可重载的二元运算符。

运算符表示法 | 函数表示法
op x | operator op(x)
x op | operator op(x)
x op y | operator op(x, y)

用户定义的运算符声明总是要求至少一个参数为包含运算符声明的类或结构类型。因此，用户定义的运算符不可能具有与预定义运算符相同的签名。

用户定义的运算符声明不能修改运算符的语法、优先级或顺序关联性。例如，/ 运算符总是一个二元运算符，总是具有在第 7.2.1 节中指定的优先级，并且总是向左顺序关联。

虽然用户定义的运算符可以执行它想执行的任何计算，但是强烈建议不要采用产生的结果与直觉预期不同的实现。例如，operator == 的实现应比较两个操作数是否相等，然后返回一个适当的 bool 结果。

在从第 7.5 节到第 7.13 节的关于各运算符的说明中，运算符的预定义实现以及应用于各运算符的任何其他规则都有规定。在这些说明中使用了“一元运算符重载决策”、“二元运算符重载决策”和“数值提升”这样的术语，在后面的章节中可以找到它们的定义。


7.2.3一元运算符重载决策.905799

7.2.3 一元运算符重载决策

op x 或 x op 形式的运算（其中 op 是可重载的一元运算符，x 是 X 类型的表达式）按下面这样处理：

· 对于由 X 为运算 operator op(x) 提供的候选的用户定义运算符的集合，应根据第 7.2.5 节中的规则来确定。

· 如果候选的用户定义运算符集合不为空，则它就会成为运算的候选运算符集合。否则，预定义一元 operator op 实现成为关于该运算的候选运算符集合。关于给定运算符的预定义实现，在有关运算符的说明（第 7.5 节和第 7.6 节）中指定。

· 第 7.4.2 节中的重载决策规则应用于候选运算符集合，以选择一个关于参数列表 (x) 的最好的运算符，此运算符将成为重载决策过程的结果。如果重载决策未能选出单个最佳运算符，则发生编译时错误。


7.2.4二元运算符重载决策.907938

7.2.4 二元运算符重载决策

x op y 形式的运算（其中 op 是可重载的二元运算符，x 是 X 类型的表达式，y 是 Y 类型的表达式）按下面这样处理：

· 确定 X 和 Y 为运算 operator op(x, y) 提供的候选用户定义运算符集合。该集合包括由 X 提供的候选运算符和由 Y 提供的候选运算符的联合，每个候选运算符由第 7.2.5 节中的规则确定。如果 X 和 Y 的类型相同，或者 X 和 Y 是从一个公共基类型派生的，则共享的候选运算符只在合并的集合中出现一次。

· 如果候选的用户定义运算符集合不为空，则它就会成为运算的候选运算符集合。否则，预定义二元 operator op 实现将成为该运算的候选运算符集合。关于给定运算符的预定义实现，在有关运算符的说明（第 7.7 节到第 7.13 节）中指定。

· 第 7.4.2 节中的重载决策规则应用于候选运算符集合，以选择一个关于参数列表 (x, y) 的最好的运算符，此运算符将成为重载决策过程的结果。如果重载决策未能选出单个最佳运算符，则发生编译时错误。


7.2.5候选用户定义运算符.910555

7.2.5 候选用户定义运算符

给定一个 T 类型和运算 operator op(A)，其中 op 是可重载的运算符，A 是参数列表，对 T 为 operator op(A) 提供的候选用户定义运算符集合按下面这样确定：

· 对于 T 中的所有 operator op 声明，如果关于参数列表 A 至少有一个运算符是适用的（第 7.4.2.1 节），则候选运算符集合由 T 中所有适用的 operator op 声明组成。

· 否则，如果 T 为 object，则候选运算符集合为空。

· 否则，由 T 提供的候选运算符集合是 T 的直接基类提供的候选运算符集合。


7.2.6数值提升.913186

7.2.6 数值提升

数值提升包括自动为预定义一元和二元数值运算符的操作数执行某些隐式转换。数值提升不是一个独特的机制，而是一种将重载决策应用于预定义运算符所产生的效果。数值提升尤其不影响用户定义运算符的计算，尽管可以实现用户定义运算符以表现类似的效果。

作为数值提升的示例，请看二元运算符 * 的预定义实现：

int operator *(int x, int y);

uint operator *(uint x, uint y);

long operator *(long x, long y);

ulong operator *(ulong x, ulong y);

float operator *(float x, float y);

double operator *(double x, double y);

decimal operator *(decimal x, decimal y);

当重载决策规则（第 7.4.2 节）应用于此运算符集合时，这些运算符中第一个能满足下述条件的运算符会被选中：存在一个从给定的操作数的类型到它声明的类型的隐式转换。例如，对于 b * s 运算（其中 b 为 byte，s 为 short），重载决策选择 operator *(int, int) 作为最好的运算符。因此，效果是 b 和 s 转换为 int，结果的类型为 int。同样，对于 i * d 运算（其中 i 为 int，d 为 double），重载决策选择 operator *(double, double) 作为最好的运算符。


7.2.6.1一元数值提升.915657

Powered By Gavin http://www.CSharpWeb.com

7.2.6.1 一元数值提升

一元数值提升是针对预定义的 +、– 和 ~ 一元运算符的操作数发生的。一元数值提升仅包括将 sbyte、byte、short、ushort 或 char 类型的操作数转换为 int 类型。此外，对于 – 一元运算符，一元数值提升将 uint 类型的操作数转换为 long 类型。


7.2.6.2二元数值提升.916572

7.2.6.2 二元数值提升

二元数值提升是针对预定义的 +、–、*、/、%、&、|、^、==、!=、>、<、>= 和 <= 二元运算符的操作数发生的。二元数值提升隐式地将两个操作数都转换为一个公共类型，如果涉及的是非关系运算符，则此公共类型还成为运算的结果类型。二元数值提升应按下列规则进行（以它们在此出现的顺序）：

· 如果有一个操作数的类型为 decimal，则另一个操作数转换为 decimal 类型；否则，如果另一个操作数的类型为 float 或 double，则发生编译时错误。

· 否则，如果有一个操作数的类型为 double，则另一个操作数转换为 double 类型。

· 否则，如果有一个操作数的类型为 float，则另一个操作数转换为 float 类型。

· 否则，如果有一个操作数的类型为 ulong，则另一个操作数转换为 ulong 类型；否则，如果另一个操作数的类型为 sbyte、short、int 或 long，则发生编译时错误。

· 否则，如果有一个操作数的类型为 long，则另一个操作数转换为 long 类型。

· 否则，如果有一个操作数的类型为 uint 而另一个操作数的类型为 sbyte、short 或 int，则两个操作数都转换为 long 类型。

· 否则，如果有一个操作数的类型为 uint，则另一个操作数转换为 uint 类型。

· 否则，两个操作数都转换为 int 类型。

请注意，第一个规则不允许将 decimal 类型与 double 和 float 类型混用。该规则遵循这样的事实：在 decimal 类型与 double 和 float 类型之间不存在隐式转换。

还需要注意的是，当一个操作数为有符号的整型时，另一个操作数的类型不可能为 ulong。原因是不存在一个既可以表示 ulong 的全部范围，又能表示有符号整数的整型类型。

在以上两种情况下，都可以使用强制转换表达式显式地将一个操作数转换为与另一个操作数兼容的类型。

在下面的示例中，

decimal AddPercent(decimal x, double percent) {

return x * (1.0 + percent / 100.0);

}

由于 decimal 类型不能与 double 类型相乘，因此发生编译时错误。通过将第二个操作数显式转换为 decimal 消除此错误，如下所示：

decimal AddPercent(decimal x, double percent) {

return x * (decimal)(1.0 + percent / 100.0);

}


7.3成员查找.922686

7.3 成员查找

成员查找是确定类型上下文中的名称含义的过程。成员查找可能成为在表达式中计算简称（第 7.5.2 节）或成员访问（第 7.5.4 节）过程的组成部分。

T 类型中的名称 N 的成员查找按下面这样处理：

· 首先，构造一个在 T 和 T 的基类型（第 7.3.1 节）中声明的名为 N 的所有可访问（第 3.5 节）成员的集合。包含 override 修饰符的声明不包括在此集合中。如果名为 N 且可访问的成员不存在，则此查找不产生任何匹配，并且不对下面的步骤进行计算。

· 然后，从该集合中移除被其他成员隐藏的成员。对于该集合中的每个成员 S.M（其中 S 是声明了成员 M 的类型），应用下面的规则：

· 如果 M 是一个常数、字段、属性、事件、类型或枚举成员，则从集合中移除在 S 的基类型中声明的所有成员。

· 如果 M 是一个方法，则从集合中移除在 S 的基类型中声明的所有非方法成员，并从集合中移除与在 S 的基类型中声明的 M 具有相同签名的所有方法。

· 最后，移除了隐藏成员后，按下述规则确定查找的结果：

· 如果集合由一个非方法成员组成，则此成员为查找的结果。

· 否则，如果集合只包含方法，则这组方法为查找的结果。

· 否则，查找失败（无明确的结果），并发生编译时错误（只有对于具有多个直接基接口的接口中的成员查找才会出现这种情况）。

在不是接口的类型和严格单一继承的接口（继承链中的每个接口都只有零个或一个直接基接口）类型中，进行成员查找的规则可以简单地归结为：派生成员隐藏具有相同名称或签名的基成员。这种单一继承查找决不会失败（一定有明确的结果）。有关多重继承中的成员查找可能引起的多义性的介绍详见（第 13.2.5 节）。


7.3.1基类型.926509

7.3.1 基类型

出于成员查找的目的，类型 T 被视为具有下列基类型：

· 如果 T 为 object，则 T 没有基类型。

· 如果 T 为值类型，则 T 的基类型为类类型 object。

· 如果 T 为类类型，则 T 的基类型为 T 的基类，其中包括类类型 object。

· 如果 T 为接口类型，则 T 的基类型为 T 的基接口和类类型 object。

· 如果 T 为数组类型，则 T 的基类型为类类型 System.Array 和 object。

· 如果 T 为委托类型，则 T 的基类型为类类型 System.Delegate 和 object。


7.4函数成员.929119

7.4 函数成员

函数成员是包含可执行语句的成员。函数成员总是类型的成员，不能是命名空间的成员。C# 定义了以下类别的函数成员：

· 方法

· 属性

· 事件

· 索引器

· 用户定义运算符

· 实例构造函数

· 静态构造函数

· 析构函数

除了析构函数和静态构造函数（它们不能被显式调用），函数成员中包含的语句通过函数成员调用执行。编写函数成员调用的实际语法取决于具体的函数成员类别。

函数成员调用中所带的自变量列表（第 7.4.1 节）提供了函数成员参数的实际值或变量引用。

调用方法、索引器、运算符和实例构造函数时，使用重载决策来确定要调用的候选函数成员集。有关此过程的介绍详见第 7.4.2 节。

在编译时（可能通过重载决策）确定了具体的函数成员后，有关运行时调用函数成员的实际过程的介绍详见第 7.4.3 节。

下表概述了在涉及六个可被显式调用的函数成员类别的构造中发生的处理过程。在下表中，e、x、y 和 value 代表变量或值类别的表达式，T 代表类型的表达式，F 是一个方法的简称，P 是一个属性的简称。

构造 | 示例 | 说明
方法调用 | F(x, y) | 应用重载决策以在包含类或结构中选择最佳的方法 F。用参数列表 (x, y) 调用该方法。如果该方法不为 static，则用 this 来表达对应的实例。
方法调用 | T.F(x, y) | 应用重载决策以在类或结构 T 中选择最佳的方法 F。如果该方法不为 static，则发生编译时错误。用参数列表 (x, y) 调用该方法。
方法调用 | e.F(x, y) | 应用重载决策以从 e 所属的类型确定的类、结构或接口中选择最佳的方法 F。如果该方法为 static，则发生编译时错误。用实例表达式 e 和参数列表 (x, y) 调用该方法。
属性访问 | P | 调用包含类或结构中属性 P 的 get 访问器。如果 P 是只写的，则发生编译时错误。如果 P 不为 static，则实例表达式为 this。
属性访问 | P = value | 用参数列表 (value) 调用包含类或结构中的属性 P 的 set 访问器。如果 P 是只读的，则发生编译时错误。如果 P 不为 static，则用 this 来表达对应的实例。
属性访问 | T.P | 调用类或结构 T 中属性 P 的 get 访问器。如果 P 不为 static，或者如果 P 是只写的，则发生编译时错误。
属性访问 | T.P = value | 用参数列表 (value) 调用类或结构 T 中的属性 P 的 set 访问器。如果 P 不为 static，或者如果 P 是只读的，则发生编译时错误。
属性访问 | e.P | 用实例表达式 e 调用由 e 的类型提供的类、结构或接口中属性 P 的 get 访问器。如果 P 为 static，或者如果 P 是只写的，则发生编译时错误。
属性访问 | e.P = value | 用实例表达式 e 和参数列表 (value) 调用 e 的类型给定的类、结构或接口中属性 P 的 set 访问器。如果 P 为 static，或者如果 P 是只读的，则发生编译时错误。
事件访问 | E += value | 调用包含类或结构中的事件 E 的 add 访问器。如果 E 不是静态的，则用 this 来表达对应的实例。
事件访问 | E -= value | 调用包含类或结构中事件 E 的 remove 访问器。如果 E 不是静态的，则用 this 来表达对应的实例。
事件访问 | T.E += value | 调用类或结构 T 中事件 E 的 add 访问器。如果 E 不是静态的，则发生编译时错误。
事件访问 | T.E -= value | 调用类或结构 T 中事件 E 的 remove 访问器。如果 E 不是静态的，则发生编译时错误。
事件访问 | e.E += value | 用实例表达式 e 调用由 e 的类型提供的类、结构或接口中事件 E 的 add 访问器。如果 E 是静态的，则发生编译时错误。
事件访问 | e.E -= value | 用实例表达式 e 调用由 e 的类型给定的类、结构或接口中事件 E 的 remove 访问器。如果 E 是静态的，则发生编译时错误。
索引器访问 | e[x, y] | 应用重载决策以在 e 的类型给定的类、结构或接口中选择最佳的索引器。用实例表达式 e 和参数列表 (x, y) 调用该索引器的 get 访问器。如果索引器是只写的，则发生编译时错误。
索引器访问 | e[x, y] = value | 应用重载决策以在 e 的类型给定的类、结构或接口中选择最佳的索引器。用实例表达式 e 和参数列表 (x, y, value) 调用该索引器的 set 访问器。如果索引器是只读的，则发生编译时错误。
运算符调用 | -x | 应用重载决策以在 x 的类型给定的类或结构中选择最佳的一元运算符。用参数列表 (x) 调用选定的运算符。
运算符调用 | x + y | 应用重载决策以在 x 和 y 的类型给定的类或结构中选择最佳的二元运算符。使用参数列表 (x, y) 调用选定的运算符。
实例构造函数 | new T(x, y) | 应用重载决策以在类或结构 T 中选择最佳的实例构造函数。用参数列表 (x, y) 调用该实例构造函数。


7.4.1参数列表.951637

7.4.1 参数列表

每个函数成员调用均包括一个参数列表，该列表列出了函数成员参数的实际值或变量引用。如何指定函数成员调用的参数列表的语法取决于函数成员类别：

· 对于实例构造函数、方法和委托，参数被指定为参数列表，如下所述。

· 对于属性，当调用 get 访问器时，参数列表是空的；而当调用 set 访问器时，参数列表由指定为赋值运算符的右操作数的表达式组成。

· 对于事件，参数列表由指定为 += 或 -= 运算符的右操作数的表达式组成。

· 对于索引器，参数列表由在索引器访问中的方括号之间指定的表达式组成。当调用 set 访问器时，参数列表还需附加上一个表达式，该表达式被指定为赋值运算符的右操作数。

· 对于用户定义的运算符，参数列表由一元运算符的单个操作数或二元运算符的两个操作数组成。

对于属性（第 10.6 节）、事件（第 10.7 节）、索引器（第 10.8 节）和用户定义的运算符（第 10.9 节）来说，它们的参数总是作为值参数（第 10.5.1.1 节）传递的。这些函数成员类别不支持引用参数和输出参数。

实例构造函数、方法或委托调用的参数按如下的参数列表形式指定：

argument-list:（参数列表：）

argument（参数）
argument-list , argument（参数列表 , 参数）

argument:（参数：）

expression（表达式）
ref variable-reference（ref 变量引用）
out variable-reference（out 变量引用）

参数列表由一个或多个参数组成，各参数之间用逗号隔开。每个参数都可以采用下列形式之一：

· 表达式，指示将该参数作为值参数（第 10.5.1.1 节）传递。

· 后跟变量引用（第 5.3.3 节）的关键字 ref，指示将参数作为引用参数（第 10.5.1.2 节）传递。变量必须已明确赋值（第 5.3 节）后才可作为引用参数传递。易失字段（第 10.4.3 节）不能作为引用参数传递。

· 后跟变量引用（第 5.3.3 节）的关键字 out，指示将参数作为输出参数（第 10.5.1.3 节）传递。在将变量作为输出参数传递的函数成员调用之后，该变量被视为已明确赋值（第 5.3 节）。易失字段（第 10.4.3 节）不能作为输出参数传递。

在一个函数成员调用（第 7.4.3 节）的运行时处理期间，将按顺序从左到右计算参数列表的表达式或变量引用，具体规则如下：

· 对于值参数，计算参数表达式并执行到相应的参数类型的隐式转换（第 6.1 节）。结果值在函数成员调用中成为该值参数的初始值。

· 对于引用参数或输出参数，计算对应的变量引用，所得的存储位置在函数成员调用中成为该参数表示的存储位置。如果作为引用参数或输出参数给定的变量引用是一个引用类型的数组元素，则执行一个运行时检查以确保该数组的元素类型与参数的类型相同。如果检查失败，则引发 System.ArrayTypeMismatchException。

方法、索引器和实例构造函数可以将其最右边的参数声明为参数数组（第 10.5.1.4 节）。是以正常形式还是以展开形式调用这类函数成员取决于哪种形式适用（第 7.4.2.1 节）：

· 当以正常形式调用带有参数数组的函数成员时，为参数数组给定的参数必须是属于某个类型的单个表达式，而该类型可隐式转换（第 6.1 节）为参数数组类型。在此情况下，参数数组的作用与值参数完全一样。

· 当以展开形式调用带有参数数组的函数成员时，调用必须为参数数组指定零个或多个参数，其中每个参数都是某个类型的表达式，而该类型可隐式转换（第 6.1 节）为参数数组的元素类型。在此情况下，调用会创建一个该参数数组类型的实例，其所含的元素个数等于给定的参数个数，再用给定的参数值初始化此数组实例的每个元素，然后将新创建的数组实例用作实参。

参数列表的表达式总是按其书写的顺序来计算。因此，示例

class Test

{

static void F(int x, int y, int z) {

System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);

}

static void Main() {

int i = 0;

F(i++, i++, i++);

}

}

产生输出

x = 0, y = 1, z = 2

如果存在从 B 到 A 的隐式引用转换，则数组协方差规则（第 12.5 节）允许数组类型 A[] 的值成为对数组类型 B[] 的一个实例的引用。由于这些规则，在将引用类型的数组元素作为引用参数或输出参数传递时，需要执行运行时检查以确保该数组的实际元素类型与参数的类型完全一致。在下面的示例中，

class Test

{

static void F(ref object x) {...}

static void Main() {

object[] a = new object[10];

object[] b = new string[10];

F(ref a[0]); // Ok

F(ref b[1]); // ArrayTypeMismatchException

}

}

第二个 F 调用导致引发 System.ArrayTypeMismatchException，原因是 b 的实际元素类型是 string 而不是 object。

当以展开形式调用带有参数数组的函数成员时，其调用处理过程完全类似于如下过程：将带有数组初始值设定项（第 7.5.10.2 节）的数组创建表达式插入到展开的参数所在之处。例如，已知下面的声明

void F(int x, int y, params object[] args);

以下方法的展开形式的调用

F(10, 20);

F(10, 20, 30, 40);

F(10, 20, 1, "hello", 3.0);

完全对应于

F(10, 20, new object[] {});

F(10, 20, new object[] {30, 40});

F(10, 20, new object[] {1, "hello", 3.0});

请特别注意，当为参数数组指定的参数的个数为零时，将创建一个空数组。


7.4.2重载决策.963027

7.4.2 重载决策

重载决策是一种编译时机制，用于在给定了参数列表和一组候选函数成员的情况下，选择一个最佳函数成员来实施调用。在 C# 内，重载决策在下列不同的上下文中选择一个应调用的函数成员：

· 要调用的方法名称出现在调用表达式（第 7.5.5 节）中。

· 要调用的实例构造函数出现在对象创建表达式（第 7.5.10.1 节）中。

· 对一个索引器访问器的调用出现在元素访问（第 7.5.6 节）中。

· 要调用的预定义运算符或用户定义的运算符出现在表达式（第 7.2.3 节和第 7.2.4 节）中。

这些上下文中的每一个都以自己的唯一方式定义候选函数成员集和参数列表，上面列出的章节对此进行了详细说明。例如，方法调用的候选集不包括标记为 override（第 7.3 节）的方法，而且如果派生类中的任何方法可用（第 7.5.5.1 节），则基类中的方法不是候选方法。

一旦确定了候选函数成员和参数列表，对最佳函数成员的选择在所有情况下都相同，都遵循下列规则：

· 如果给定了适用的候选函数成员集，则在其中选出最佳函数成员。

· 如果该集合只包含一个函数成员，则该函数成员为最佳函数成员。

· 否则，最佳函数成员的选择依据是：各成员对给定的参数列表的匹配程度。比所有其他函数成员匹配得更好的那个函数成员就是最佳函数成员，但有一个前提：必须使用第 7.4.2.2 节中的规则将每个函数成员与所有其他函数成员进行比较。

· 如果不是正好有一个函数成员比所有其他函数成员都好，则函数成员调用不明确并发生编译时错误。

下面几节定义有关术语“适用的函数成员”和“更好的函数成员”的准确含义。


7.4.2.1适用的函数成员.966413

7.4.2.1 适用的函数成员

当所有下列条件都为真时，就称函数成员对于参数列表 A 是一个适用的函数成员：

· A 中的参数数目与函数成员声明中的参数数目相同。

· 对于 A 中的每个参数，自变量的参数传递模式（即值、ref 或 out）与相应参数的参数传递模式相同，而且

· 对于值参数或参数数组，存在从自变量类型到相应参数的类型的隐式转换（第 6.1 节），或者

· 对于 ref 或 out 参数，自变量的类型与相应参数的类型相同。ref 或 out 参数毕竟只是传递的自变量的别名。

对于包含参数数组的函数成员，如果按上述规则判定该函数成员是适用的，则称它以正常形式适用。如果包含参数数组的函数成员以正常形式不适用，则该函数成员可能以展开形式适用：

· 构造展开形式的方法是：用参数数组的元素类型的零个或多个值参数替换函数成员声明中的参数数组，使参数列表 A 中的参数数目匹配总的参数数目。如果 A 中的自变量比函数成员声明中的固定参数的数目少，则该函数成员的展开形式无法构造，因而可判定该函数成员不适用。

· 如果声明函数成员的类、结构或接口已经包含另一个与展开形式具有相同签名的适用函数成员，则展开形式不适用。

· 否则，如果对于 A 中的每个自变量，它的参数传递模式与相应参数的参数传递模式相同，并且下列条件成立，则称该成员函数以展开形式适用：

· 对于固定值参数或展开操作所创建的值参数，存在从自变量的类型到相应参数的类型的隐式转换（第 6.1 节），或者

· 对于 ref 或 out 参数，参数的类型与相应参数的类型相同。


7.4.2.2更好的函数成员.970179

7.4.2.2 更好的函数成员

给定一个带有参数类型集 {A1, A2, ..., AN} 的参数列表 A 和带有参数类型 {P1, P2, ..., PN} 和 {Q1, Q2, ..., QN} 的两个可应用的函数成员 MP 和 MQ，则在以下情况中，MP 定义为比 MQ 更好的函数成员：

· 对于每个参数，从 AX 到 PX 的隐式转换都不比从 AX 到 QX 的隐式转换差，并且

· 对于至少一个参数，从 AX 到 PX 的转换比从 AX 到 QX 的转换更好。

当执行此计算时，如果 MP 或 MQ 以展开形式适用，则 PX 或 QX 所代表的是展开形式的参数列表中的参数。


7.4.2.3更好的转换.971981

7.4.2.3 更好的转换

假设有一个从类型 S 转换到类型 T1 的隐式转换 C1，和一个从类型 S 转换到类型 T2 的隐式转换 C2，将按下列规则确定这两个转换中哪个是更好的转换：

· 如果 T1 和 T2 是相同类型，则两个转换都不是更好的转换。

· 如果 S 为 T1，则 C1 为更好的转换。

· 如果 S 为 T2，则 C2 为更好的转换。

· 如果存在从 T1 到 T2 的隐式转换，且不存在从 T2 到 T1 的隐式转换，则 C1 为更好的转换。

· 如果存在从 T2 到 T1 的隐式转换，且不存在从 T1 到 T2 的隐式转换，则 C2 为更好的转换。

· 如果 T1 为 sbyte 而 T2 为 byte、ushort、uint 或 ulong，则 C1 为更好的转换。

· 如果 T2 为 sbyte 而 T1 为 byte、ushort、uint 或 ulong，则 C2 为更好的转换。

· 如果 T1 为 short 而 T2 为 ushort、uint 或 ulong，则 C1 为更好的转换。

· 如果 T2 为 short 而 T1 为 ushort、uint 或 ulong，则 C2 为更好的转换。

· 如果 T1 为 int 而 T2 为 uint 或 ulong，则 C1 为更好的转换。

· 如果 T2 为 int 而 T1 为 uint 或 ulong，则 C2 为更好的转换。

· 如果 T1 为 long 而 T2 为 ulong，则 C1 为更好的转换。

· 如果 T2 为 long 而 T1 为 ulong，则 C2 为更好的转换。

· 否则，两个转换都不是更好的转换。

如果按上述规则，确定了隐式转换 C1 是比隐式转换 C2 更好的转换，则 C2 是比 C1 更差的转换。


7.4.3函数成员调用.979265

7.4.3 函数成员调用

本节描述在运行时发生的调用一个特定的函数成员的进程。这里假定这个要调用的特定成员，已在编译时进程确定了（可能采用重载决策从一组候选函数成员中选出）。

为了描述调用进程，将函数成员分成两类：

· 静态函数成员。包括实例构造函数、静态方法、静态属性访问器和用户定义的运算符。静态函数成员总是非虚拟的。

· 实例函数成员。包括实例方法、实例属性访问器和索引器访问器。实例函数成员不是非虚拟的就是虚拟的，并且总是在特定的实例上调用。该实例由实例表达式计算，并且在函数成员内可以以 this（第 7.5.7 节）的形式对它进行访问。

函数成员调用的运行时处理包括以下步骤（其中 M 是函数成员，如果 M 是实例成员，则 E 是实例表达式）：

· 如果 M 是静态函数成员，则：

· 它的参数列表按照第 7.4.1 节中的说明进行计算。

· 调用 M。

· 如果 M 是在值类型中声明的实例函数成员，则：

· 计算 E。如果该计算导致异常，则不执行进一步的操作。

· 如果 E 没有被归类为一个变量，则创建一个与 E 同类型的临时局部变量，并将 E 的值赋给该变量。这样，E 就被重新归类为对该临时局部变量的一个引用。该临时变量在 M 中可以以 this 的形式被访问，但不能以任何其他形式。因此，仅当 E 是真正的变量时，调用方才可能观察到 M 对 this 所做的更改。

· 参数列表按照第 7.4.1 节中的说明进行计算。

· 调用 M。E 引用的变量成为 this 引用的变量。

· 如果 M 是在引用类型中声明的实例函数成员，则：

· 计算 E。如果该计算导致异常，则不执行进一步的操作。

· 参数列表按照第 7.4.1 节中的说明进行计算。

· 如果 E 的类型为值类型，则执行装箱转换（第 4.3.1 节）以将 E 转换为 object 类型，并且在下列步骤中，E 被视为 object 类型。这种情况下，M 只能是 System.Object 的成员。

· 检查 E 的值是否有效。如果 E 的值为 null，则引发 System.NullReferenceException，并且不执行进一步的操作。

· 要调用的函数成员实现按以下规则确定：

如果 E 的编译时类型是接口，则调用的函数成员是 M 的实现，此实现是由 E 引用的实例在运行时所属的类型提供的。确定此函数成员时，应用接口映射规则（第 13.4.2 节）确定由 E 引用的实例运行时类型提供的 M 实现。

否则，如果 M 是虚函数成员，则调用的函数成员是由 E 引用的实例运行时类型提供的 M 实现。确定此函数成员时，应用“确定 M 的派生程度最大的实现”的规则（第 10.5.3 节）（相对于 E 引用的实例的运行时类型）。

否则，M 是非虚函数成员，调用的函数成员是 M 本身。

· 调用在上一步中确定的函数成员实现。E 引用的对象成为 this 引用的对象。


7.4.3.1已装箱实例上的调用.986760

7.4.3.1 已装箱实例上的调用

在下列情况中，一个在值类型中实现的函数成员，可以通过该值类型的已装箱的实例来调用：

· 当该函数成员是从 object 类型继承的，且具有 override 修饰符，并通过 object 类型的实例表达式被调用时。

· 当函数成员是接口函数成员的实现并且通过接口类型的实例表达式被调用时。

· 当函数成员通过委托被调用时。

在这些情况中，已装箱实例被视为包含值类型的一个变量，并且此变量在函数成员调用中成为 this 引用的变量。具体说来，这表示当调用已装箱实例的函数成员时，该函数成员可以修改已装箱实例中包含的值。


7.5基本表达式.988544

7.5 基本表达式

基本表达式包括最简单的表达式形式。

primary-expression:（基本表达式：）

primary-no-array-creation-expression（非数组创建基本表达式）
array-creation-expression（数组创建表达式）

primary-no-array-creation-expression:（非数组创建基本表达式：）

literal（文本）
simple-name（简称）
parenthesized-expression（带括号的表达式）
member-access（成员访问）
invocation-expression（调用表达式）
element-access（元素访问）
this-access（this 访问）
base-access（base 访问）
post-increment-expression（后增量表达式）
post-decrement-expression（后减量表达式）
object-creation-expression（对象创建表达式）
delegate-creation-expression（委托创建表达式）
typeof-expression（typeof 表达式）
checked-expression（checked 表达式）
unchecked-expression（unchecked 表达式）

基本表达式分为数组创建表达式和非数组创建基本表达式。采用这种方式处理数组创建表达式（不允许它和其他简单的表达式并列），使语法能够禁止可能的代码混乱，如

object o = new int[3][1];

被另外解释为

object o = (new int[3])[1];


7.5.1文本.992095

Powered By Gavin http://www.CSharpWeb.com

7.5.1 文本

由文本（第 2.4.4 节）组成的基本表达式属于值类别。


7.5.2简称.992518

7.5.2 简称

简称由单个标识符组成。

simple-name:（简称：）

identifier（标识符）

简称按下面这样计算和分类：

· 如果简称在一个块内出现，而且该块（或封闭块）的局部变量声明空间（第 3.3 节）内含有一个以给定的简称命名的局部变量或参数，则该简称引用该局部变量或参数并归为变量类别。

· 否则，对于每个类型 T（此 T 从该简称的直接封闭类、结构或枚举声明开始，从里到外，遍历所有外部的封闭类或结构声明），在 T 中对简称实施“成员查找”，如果产生一个匹配，则按下述进行：

· 如果 T 为直接封闭类或结构类型，且该成员查找标识了一个或多个方法，则结果是一个具有关联实例表达式 this 的方法组。

· 如果 T 为直接封闭类或结构类型，查找标识出了一个实例成员，并且引用在实例构造函数、实例方法或实例访问器的块中发生，则结果与 this.E（其中 E 为简称）形式的成员访问（第 7.5.4 节）相同。

· 否则，结果与 T.E 形式（其中 E 为简称）的成员访问（第 7.5.4 节）相同。在此情况下，会因使用简称引用了一个实例成员而导致一个编译时错误。

· 否则，从出现简称的命名空间开始，然后是该命名空间外部的每个封闭命名空间（若有），最后以全局命名空间结束，按下列步骤进行计算，直到找到实体：

· 如果命名空间包含有给定名称的命名空间成员，则简称引用该成员，并根据该成员归为命名空间或类型类别。

· 否则，如果该命名空间具有一个相应的命名空间声明，将简称出现的位置包含在其中，则：

· 如果该命名空间声明包含一个 using 别名指令，它使给定的简称与导入的命名空间或类型相关联，则简称引用此命名空间或类型。

· 否则，如果该命名空间声明中包含多个“using 命名空间”指令，而它们导入的命名空间只包含一个具有给定名称的类型，则简称引用此类型。

· 否则，如果在上述导入的命名空间中，包含有多个具有给定名称的类型，则简称不明确并发生编译时错误。

· 否则，由简称给定的名称被认为是未经定义的，并导致编译时错误。


7.5.2.1块中的固定含义.997296

7.5.2.1 块中的固定含义

对于表达式中以简称形式给定的标识符的每个匹配项，直接封闭块（第 8.2 节）或 swith 块（第 8.7.2 节）内的表达式中同一标识符的每个其他匹配项都必须引用相同的实体。此规则确保了表达式上下文中的名称含义在一个块内始终相同。

在以下示例中：

class Test

{

double x;

void F(bool b) {

x = 1.0;

if (b) {

int x;

x = 1;

}

}

}

将产生编译时错误，这是因为 x 引用外部块（其范围包括 if 语句中的嵌套块）中的不同实体。相反，示例

class Test

{

double x;

void F(bool b) {

if (b) {

x = 1.0;

}

else {

int x ;

x = 1;

}

}

}

是允许的，这是因为在外部块中从未使用过名称 x。

注意 固定含义的规则仅适用于简称。同一标识符在作为简称时有一种意义，在作为一个成员访问（第 7.5.4 节）的右操作数时具有另一种意义，这是完全合法的。例如：

struct Point

{

int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

上面的示例阐释了一个将字段名用作实例构造函数中的参数名的通用模式。在该示例中，简称 x 和 y 引用参数，但这并不妨碍成员访问表达式 this.x 和 this.y 访问字段。


7.5.3带括号的表达式.1001570

7.5.3 带括号的表达式

带括号的表达式由一个用括号括起来的表达式组成。

parenthesized-expression:（带括号的表达式：）

( expression )（( 表达式 )）

通过计算括号内的“表达式”计算带括号的表达式。如果括号内的表达式表示命名空间、类型或方法组，则发生编译时错误。否则，带括号的表达式的结果为所含表达式的计算结果。


7.5.4成员访问.1002836

7.5.4 成员访问

成员访问包括一个基本表达式或预定义类型，后跟“.”标记，再跟一个标识符。

member-access:（成员访问：）

primary-expression . identifier（基本表达式 . 标识符）
predefined-type . identifier（预定义类型 . 标识符）

predefined-type: one of（预定义类型：下列之一）

bool byte char decimal double float int long
object sbyte short string uint ulong ushort

E.I 形式（其中 E 是一个基本表达式或预定义类型，I 是标识符）的成员访问按下面这样计算和分类：

· 如果 E 是命名空间，而 I 是该命名空间的可访问成员的名称，则结果为该成员，并且根据该成员归为命名空间或类型类别。

· 如果 E 是一个预定义类型或一个被归类为类型的基本表达式，并且 E 中的 I 成员查找（第 7.3 节）产生一个匹配，则 E.I 按下面这样计算和分类：

· 如果 I 标识类型，则结果为该类型。

· 如果 I 标识一个或多个方法，则结果为一个没有关联的实例表达式的方法组。

· 如果 I 标识一个 static 属性，则结果为一个没有关联的实例表达式的属性访问。

· 如果 I 标识 static 字段，则：

如果该字段为 readonly 并且引用发生在声明该字段的类或结构的静态构造函数外，则结果为值，即 E 中静态字段 I 的值。

否则，结果为变量，即 E 中的静态字段 I。

· 如果 I 标识 static 事件，则：

如果引用发生在声明了该事件的类或结构内，并且事件不是用事件访问器声明（第 10.7 节）声明的，则完全将 I 视为静态字段来处理 E.I。

否则，结果为没有关联的实例表达式的事件访问。

· 如果 I 标识常数，则结果为值，即该常数的值。

· 如果 I 标识枚举成员，则结果为值，即该枚举成员的值。

· 否则，E.I 是无效成员引用，并且发生编译时错误。

· 如果 E 为属性访问、索引器访问、变量或值，设它们都属于类型为 T，并且 T 中的 I 成员查找（第 7.3 节）产生一个匹配，则 E.I 按下面这样计算和分类：

· 首先，如果 E 为属性访问或索引器访问，则获取属性访问或索引器访问的值（第 7.1.1 节），并且将 E 重新划为值类别。

· 如果 I 标识一个或多个方法，则结果为具有 E 的关联实例表达式的方法组。

· 如果 I 标识实例属性，则结果为具有 E 的关联实例表达式的属性访问。

· 如果 T 为类类型并且 I 标识此类类型的一个实例字段，则：

如果 E 的值为 null，则引发 System.NullReferenceException。

否则，如果字段为 readonly 并且引用发生在声明字段的类的实例构造函数外，则结果为值，即 E 引用的对象中字段 I 的值。

否则，结果为变量，即 E 引用的对象中的字段 I。

· 如果 T 为结构类型并且 I 标识此结构类型的实例字段，则：

如果 E 为值，或者如果字段为 readonly 并且引用发生在声明字段的结构的实例构造函数外，则结果为值，即 E 给定的结构实例中字段 I 的值。

否则，结果为变量，即 E 给定的结构实例中的字段 I。

· 如果 I 标识实例事件，则：

如果引用发生在声明事件的类或结构内，并且事件不是用事件访问器声明（第 10.7 节）声明的，则完全将 I 视为实例字段来处理 E.I。

否则，结果为具有 E 的关联实例表达式的事件访问。

· 否则，E.I 是无效成员引用，并且发生编译时错误。


7.5.4.1相同的简称和类型名称.1013325

7.5.4.1 相同的简称和类型名称

在 E.I 形式的成员访问中，如果 E 为单个标识符，E 可能有两种含义：作为简称（第 7.5.2 节）的 E，作为类型名（第 3.8 节）的 E。只要前者所标识的对象实体（无论是常数、字段、属性、局部变量或参数）所属的类型就是以后者命名的类型，则 E 的这两种可能的含义都是允许。在此规则下，E.I 可能有两种含义，但它们永远是明确的，因为在两种情况下，I 都必须一定是类型 E 的成员。换言之，此规则在访问 E 的静态成员和嵌套类型时，能简单地避免本来可能发生的编译时错误。例如：

struct Color

{

public static readonly Color White = new Color(...);

public static readonly Color Black = new Color(...);

public Color Complement() {...}

}

class A

{

public Color Color; // Field Color of type Color

void F() {

Color = Color.Black; // References Color.Black static member

Color = Color.Complement(); // Invokes Complement() on Color field

}

static void G() {

Color c = Color.White; // References Color.White static member

}

}

在类 A 中，引用 Color 类型的 Color 标识符的那些匹配项带下划线，而引用 Color 字段的那些匹配项不带下划线。


7.5.5调用表达式.1016874

7.5.5 调用表达式

调用表达式用于调用方法。

invocation-expression:（调用表达式：）

primary-expression ( argument-list opt )（基本表达式 ( 参数列表可选 )）

调用表达式的基本表达式必须是方法组或委托类型的值。如果基本表达式是方法组，则调用表达式为方法调用（第 7.5.5.1 节）。如果基本表达式是委托类型的值，则调用表达式为委托调用（第 7.5.5.2 节）。如果基本表达式既不是方法组也不是委托类型的值，则发生编译时错误。

可选的参数列表（第 7.4.1 节）列出的值或变量引用在调用时传递给方法中的参数。

调用表达式的计算结果按下面这样分类：

· 如果调用表达式调用的方法或委托返回 void，则结果为 Nothing。Nothing 类别的表达式不能是任何运算符的操作数，并且只能在语句表达式（第 8.6 节）的上下文中使用。

· 否则，结果是由方法或委托返回的类型的值。


7.5.5.1方法调用.1019338

7.5.5.1 方法调用

对于方法调用，调用表达式的基本表达式必须是方法组。方法组标识要调用的一个方法，或者标识从中选择要调用的特定方法的一个重载方法集。在后一种情况中，具体调用那个方法取决于参数列表中的自变量的类型所提供的上下文。

M(A) 形式（其中 M 是方法组，A 是可选的参数列表）的方法调用的编译时处理包括以下步骤：

· 构造方法调用的候选方法集。从与 M 关联的、由以前的成员查找（第 7.3 节）找到的方法集合开始，将该集合缩减为对于参数列表 A 适用的那些方法。集合缩减操作包括将下列规则应用于集合中的每个 T.N 方法（其中 T 是声明方法 N 的类型）：

· 如果对于 A（第 7.4.2.1 节），N 不适用，则从集合中移除 N。

· 如果对于 A（第 7.4.2.1 节），N 适用，则从集合中移除在 T 的基类型中声明的所有方法。

· 如果得到的候选方法集为空，则不存在适用的方法，并发生编译时错误。如果候选方法并非都声明为相同的类型，则方法调用不明确，并发生编译时错误（只有对于具有多个直接基接口的接口中的方法调用才会出现后一种情况，详见（第 13.2.5 节）中的介绍）。

· 使用第 7.4.2 节的重载决策规则确定候选方法集的最佳方法。如果无法确定单个最佳方法，则方法调用不明确，并发生编译时错误。

· 假设有一个最佳方法，方法调用在方法组的上下文中进行验证：如果最佳方法为静态方法，则方法组必须是由一个简称或一个通过类型的成员访问所产生的。如果最佳方法为实例方法，则方法组必须由简称、通过变量或值的成员访问或基访问产生。如果两个要求都不满足，则发生编译时错误。

通过以上步骤在编译时选定并验证了方法后，将根据第 7.4.3 节中说明的函数成员调用规则处理实际的运行时调用。

上述决策规则的直觉效果如下：为找到方法调用所调用的特定方法，从方法调用指示的类型开始，在继承链中一直向上查找，直到至少找到一个适用的、可访问的、非重写的方法声明。然后对该类型中声明的适用的、可访问的、非重写的方法集执行重载决策，并调用由此选定的方法。


7.5.5.2委托调用.1023057

7.5.5.2 委托调用

对于委托调用，调用表达式的基本表达式必须是委托类型的值。另外，将委托类型视为与委托类型具有相同的参数列表的函数成员，委托类型对于调用表达式的参数列表必须是适用的（第 7.4.2.1 节）。

D(A) 形式（其中 D 是委托类型的基本表达式，A 是可选的参数列表）的委托调用的运行时处理包括以下步骤：

· 计算 D。如果此计算导致异常，则不执行进一步的操作。

· 检查 D 的值是否有效。如果 D 的值为 null，则引发 System.NullReferenceException，并且不再执行进一步的操作。

· 否则，D 是一个对委托实例的引用。对该委托的调用列表中的每个可调用实体上，执行函数成员调用（第 7.4.3 节）。对于由实例和实例方法组成的可调用实体，用于调用的实例是包含在可调用实体中的实例。


7.5.6元素访问.1025091

7.5.6 元素访问

一个元素访问由一个“非数组创建基本表达式”，后面依次跟着“[”标记、表达式列表和“]”标记组成。表达式列表由一个或多个用逗号分隔的表达式组成。

element-access:（元素访问：）

primary-no-array-creation-expression [ expression-list ]（非数组创建基本表达式 [ 表达式列表 ]）

expression-list:（表达式列表：）

expression（表达式）
expression-list , expression（表达式列表 , 表达式）

如果元素访问的非数组创建基本表达式是一个数组类型的值，则该元素访问是一个数组访问（第 7.5.6.1 节）。否则，该非数组创建基本表达式必须是具有一个或多个索引器成员的类、结构或接口类型的变量或值，在这种情况下，元素访问为索引器访问（第 7.5.6.2 节）。


7.5.6.1数组访问.1027683

7.5.6.1 数组访问

对于数组访问，元素访问的非数组创建基本表达式必须是数组类型的值。表达式列表中的表达式数目必须与数组类型的秩相同，并且每个表达式都必须是 int、uint、long、ulong 等类型，或者是可以隐式转换为这些类型中的一个或多个类型的类型。

数组访问的计算结果是数组的元素类型的变量，即由表达式列表中表达式的值选定的数组元素。

P[A] 形式（其中 P 是数组类型的非数组创建基本表达式，A 是表达式列表）的数组访问运行时处理包括以下步骤：

· 计算 P。如果此计算导致异常，则不执行进一步的操作。

· 表达式列表的索引表达式按从左到右的顺序计算。计算每个索引表达式后，执行到下列类型之一的隐式转换（第 6.1 节）：int、uint、long、ulong。选择此列表中第一个存在相应隐式转换的类型。例如，如果索引表达式是 short 类型，则执行到 int 的隐式转换，这是因为可以执行从 short 到 int 和从 short 到 long 的隐式转换。如果计算索引表达式或后面的隐式转换时导致异常，则不再进一步计算索引表达式，并且不再执行进一步的操作。

· 检查 P 的值是否有效。如果 P 的值为 null，则引发 System.NullReferenceException，并且不再执行进一步的操作。

· 针对由 P 引用的数组实例的每个维度的实际界限，检查“表达式列表”中每个表达式的值。如果一个或多个值超出了范围，则引发 System.IndexOutOfRangeException，并且不再执行进一步的操作。

· 计算由索引表达式给定的数组元素的位置，此位置将成为数组访问的结果。


7.5.6.2索引器访问.1031159

7.5.6.2 索引器访问

对于索引器访问，元素访问的非数组创建基本表达式必须是类、结构或接口类型的变量或值，并且此类型必须实现了一个或多个对于元素访问的表达式列表适用的索引器。

P[A] 形式（其中 P 是类、结构或接口类型 T 的一个非数组创建基本表达式，A 是表达式列表）的索引器访问编译时处理包括以下步骤：

· 构造由 T 提供的索引器集。该集合由 T 或 T 的基类型中的所有符合下列条件的索引器组成：它们不是经 override 声明的，并且在当前上下文（第 3.5 节）中可以访问。

· 将该集合缩减为那些适用的并且不被其他索引器隐藏的索引器。对该集合中的每个索引器 S.I（其中 S 为声明索引器 I 的类型）应用下列规则：

· 如果对于 A（第 7.4.2.1 节），I 不适用，则从该集合中移除 I。

· 如果对于 A（第 7.4.2.1 节），I 适用，则从该集合中移除在 S 的基类型中声明的所有索引器。

· 如果结果候选索引器集为空，则不存在适用的索引器，并发生编译时错误。如果这些候选索引器不是在同一个类型内声明的，则索引器访问不明确，并发生编译时错误（只有对于具有多个直接基接口的接口实例的索引器访问才会发生后一种情况）。

· 使用第 7.4.2 节的重载决策规则确定候选索引器集的最佳索引器。如果无法确定单个最佳索引器，则索引器访问不明确，并发生编译时错误。

· 表达式列表的索引表达式按从左到右的顺序计算。索引器访问的处理结果是属于索引器访问类别的表达式。索引器访问表达式引用在上一步骤中确定的索引器，并具有 P 的关联实例表达式和 A 的关联参数列表。

根据索引器访问的使用上下文，索引器访问导致调用该索引器的 get 访问器或 set 访问器。如果索引器访问是赋值的目标，则调用 set 访问器以赋新值（第 7.13.1 节）。在其他所有情况中，调用 get 访问器以获取当前值（第 7.1.1 节）。


7.5.7This访问.1035099

7.5.7 This 访问

this 访问由保留字 this 组成。

this-access:（this 访问：）

this

this 访问只能在实例构造函数、实例方法或实例访问器的“块”中使用。它具有下列含义之一：

· 当 this 在类的实例构造函数内的基本表达式中使用时，它属于值类别。此时，该值的类型是出现该表达式的类，并且它的值就是对所构造的对象的引用。

· 当 this 在类的实例方法或实例访问器内的基本表达式中使用时，它属于值类别。此时，该值的类型是出现该表达式的类，并且它的值就是对为其调用方法或访问器的对象的引用。

· 当 this 在结构的实例构造函数内的基本表达式中使用时，它属于变量类别。该变量的类型是此表达式所在的结构，并且该变量表示的正是所构造的结构。结构实例构造函数的 this 变量的行为与结构类型的 out 参数完全一样，具体说来，这表示该变量在实例构造函数的每个执行路径中必须已明确赋值。

· 当 this 在结构的实例方法或实例访问器内的基本表达式中使用时，它属于变量类别。该变量的类型是此表达式所在的结构，并且该变量表示的是为其调用方法或访问器的结构。在结构实例方法中，this 变量的行为与结构类型的 ref 参数完全一样。

在以上列出的上下文以外的上下文内的基本表达式中使用 this 是编译时错误。具体说就是不能在静态方法、静态属性访问器中或字段声明的“变量初始值设定项”中引用 this。


7.5.8Base访问.1037956

7.5.8 Base 访问

base 访问由保留字 base，后跟一个“.”标记和一个标识符或一个用方括号括起来的表达式列表组成：

base-access:（base 访问：）

base . identifier（base . 标识符）
base [ expression-list ]（base [ 表达式列表 ]）

“base 访问”用于访问被当前类或结构中名称相似的成员隐藏的基类成员。base 访问只能在实例构造函数、实例方法或实例访问器的块中使用。当 base.I 出现在类或结构中时，I 必须表示该类或结构的基类的一个成员。同样，当 base[E] 出现在一个类中时，该类的基类中必须存在适用的索引器。

在编译时，base.I 和 base[E] 形式的 base 访问表达式完全等价于 ((B)this).I 和 ((B)this)[E]（其中 B 是所涉及的类或结构的基类），计算方法是一样的。因此，base.I 和 base[E] 与 this.I 和 this[E] 相对应，但 this 被视为基类的实例。

当某个 base 访问引用虚函数成员（方法、属性或索引器）时，确定在运行时（第 7.4.3 节）调用哪个函数成员的规则有一些更改。确定调用哪一个函数成员的方法是，查找该函数成员相对于 B（而不是相对于 this 的运行时类型，在非 base 访问中通常如此）的派生程度最大的实现（第 10.5.3 节）。因此，在 virtual 函数成员的 override 中，可以使用 base 访问调用该函数成员的被继承了的实现。如果 base 访问引用的函数成员是抽象的，则发生编译时错误。


7.5.9后缀增量和后缀减量运算符.1041649

7.5.9 后缀增量和后缀减量运算符

post-increment-expression:（后增量表达式：）

primary-expression ++（基本表达式 ++）

post-decrement-expression:（后减量表达式：）

primary-expression --（基本表达式 --）

后缀增量或后缀减量运算符的操作数必须是属于变量、属性访问或索引器访问类别的表达式。该运算的结果是与操作数类型相同的值。

如果后缀增量或后缀减量的操作数为属性或索引器访问，则该属性或索引器必须同时具有 get 和 set 访问器。如果不是这样，则发生编译时错误。

一元运算符重载决策（第 7.2.3 节）适用于选择特定的运算符实现。对于下列类型存在着预定义的 ++ 和 -- 运算符：sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 和任何枚举类型。预定义 ++ 运算符返回的结果值为操作数加上 1，预定义 -- 运算符返回的结果值为操作数减去 1。

x++ 或 x-- 形式的后缀增量或后缀减量运算的运行时处理包括以下步骤：

· 如果 x 属于变量：

· 计算 x 以产生变量。

· 保存 x 的值。

· 调用选定的运算符，将所保存的 x 值作为参数。

· 运算符返回的值存储在由 x 的计算结果给定的位置中。

· x 的保存值成为运算结果。

· 如果 x 属于属性或索引器访问：

· 计算与 x 关联的实例表达式（如果 x 不是 static）和参数列表（如果 x 是索引器访问），结果用于后面的 get 和 set 访问器调用。

· 调用 x 的 get 访问器并保存返回的值。

· 调用选定的运算符，将 x 的保存值作为参数。

· 调用 x 的 set 访问器，将运算符返回的值作为 value 参数。

· x 的保存值成为运算结果。

++ 和 -- 运算符还支持前缀符，详见第 7.6.5 节中的介绍。x++ 或 x-- 的结果是运算“之前”x 的值，而 ++x 或 --x 的结果是运算“之后”x 的值。在任何一种情况下，运算后 x 本身都具有相同的值。

operator ++ 或 operator -- 的实现既可以用后缀表示法调用，也可以用前缀表示法调用。但是，不能让这两种表示法分别去调用该运算符的不同的实现。


7.5.10new运算符.1048562

7.5.10 new 运算符

new 运算符用于创建类型的新实例。

有三种形式的 new 表达式：

· 对象创建表达式用于创建类类型和值类型的新实例。

· 数组创建表达式用于创建数组类型的新实例。

· 委托创建表达式用于创建委托类型的新实例。

new 运算符表示创建类型的一个实例，但并非暗示要为它动态分配内存。具体说来，值类型的实例不要求在表示它的变量以外有额外的内存，因而，在使用 new 创建值类型的实例时不发生动态分配。


7.5.10.1对象创建表达式.1050015

7.5.10.1 对象创建表达式

对象创建表达式用于创建类类型或值类型的新实例。

object-creation-expression:（对象创建表达式：）

new type ( argument-list opt )（new 类型 ( 参数列表可选 )）

对象创建表达式的类型必须是类类型或值类型。该类型不能是 abstract 类类型。

仅当类型为类类型或结构类型时才允许使用可选的参数列表（第 7.4.1 节）。

new T(A) 形式（其中 T 是类类型或值类型，A 是可选参数列表）的对象创建表达式的编译时处理包括以下步骤：

· 如果 T 是值类型且 A 不存在：

· 对象创建表达式是默认构造函数调用。对象创建表达式的结果是 T 类型的一个值，即在第 4.1.1 节中定义的 T 的默认值。

· 否则，如果 T 是类类型或结构类型：

· 如果 T 为 abstract 类类型，则发生编译时错误。

· 使用第 7.4.2 节的重载决策规则确定要调用的实例构造函数。候选实例构造函数集由所有在 T中声明的可访问实例构造函数组成，这些构造函数对于 A（第 7.4.2.1 节）是适用的。如果候选实例构造函数集合为空，或者无法标识单个最佳实例构造函数，则发生编译时错误。

· 对象创建表达式的结果是 T 类型的值，即由调用在上面的步骤中确定的实例构造函数所产生的值。

· 否则，对象创建表达式无效，并发生编译时错误。

new T(A) 形式（其中 T 是类类型或结构类型，A 是可选参数列表）的对象创建表达式的运行时处理包括以下步骤：

· 如果 T 是类类型：

· 为 T 类的一个新实例分配存储位置。如果没有足够的可用内存来为新实例分配存储位置，则引发 System.OutOfMemoryException，并且不执行进一步的操作。

· 新实例的所有字段初始化为它们的默认值（第 5.2 节）。

· 根据函数成员调用（第 7.4.3 节）的规则来调用实例构造函数。对新分配的实例的引用会自动传递给实例构造函数，因而，可以从实例构造函数中用 this 来访问将该实例。

· 如果 T 是结构类型：

· 通过分配一个临时局部变量来创建类型 T 的实例。由于要求结构类型的实例构造函数为所创建的实例的每个字段明确赋值，因此不需要初始化此临时变量。

· 根据函数成员调用（第 7.4.3 节）的规则来调用实例构造函数。对新分配的实例的引用会自动传递给实例构造函数，因而，可以从实例构造函数中用 this 来访问将该实例。


7.5.10.2数组创建表达式.1056984

7.5.10.2 数组创建表达式

数组创建表达式用于创建数组类型的新实例。

array-creation-expression:（数组创建表达式：）

new non-array-type [ expression-list ] rank-specifiers opt array-initializer opt（new 非数组类型 [ 表达式列表 ] 秩说明符可选 数组初始值设定项可选）
new array-type array-initializer（new 数组类型 数组初始值设定项）

第一种形式的数组创建表达式分配一个数组实例，其类型是从表达式列表中删除每个表达式所得到的类型。例如，数组创建表达式 new int[10, 20] 产生 int[,] 类型的数组实例，数组创建表达式 new int[10][,] 产生 int[][,] 类型的数组。表达式列表中的每个表达式必须属于 int、uint、long 或 ulong 类型，或者属于可以隐式转换为一种或多种这些类型的类型。每个表达式的值确定新分配的数组实例中相应维度的长度。由于数组维度的长度必须非负，因此，当表达式列表中出现带有负值的常数表达式时，将出现一个编译时错误。

除了在不安全的上下文（第 A.1 节）中外，数组的布局是未指定的。

如果第一种形式的数组创建表达式包含数组初始值设定项，则表达式列表中的每个表达式必须是常数，并且表达式列表指定的秩和维度长度必须匹配数组初始值设定项的秩和维度长度。

在第二种形式的数组创建表达式中，指定数组类型的秩必须匹配数组初始值设定项的秩。各维度长度从数组初始值设定项的每个对应嵌套层数中的元素数推断出。因此，表达式

new int[,] {{0, 1}, {2, 3}, {4, 5}}

完全对应于

new int[3, 2] {{0, 1}, {2, 3}, {4, 5}}

数组初始值设定项的介绍详见第 12.6 节。

数组创建表达式的计算结果属于值类别，即对新分配的数组实例的一个引用。数组创建表达式的运行时处理包括以下步骤：

· 表达式列表的维度长度表达式按从左到右的顺序计算。计算每个表达式之后，执行一个到下列类型之一的隐式转换（第 6.1 节）：int、uint、long、ulong。选择此列表中第一个存在相应隐式转换的类型。如果表达式计算或后面的隐式转换导致异常，则不计算其他表达式，并且不执行其他步骤。

· 维度长度的计算值按下面这样验证。如果一个或多个值小于零，则引发 System.OverflowException 并且不执行其他步骤。

· 分配具有给定维度长度的数组实例。如果没有足够的可用内存来分配新实例，则引发 System.OutOfMemoryException，并且不执行进一步的操作。

· 将新数组实例的所有元素初始化为它们的默认值（第 5.2 节）。

· 如果数组创建表达式包含数组初始值设定项，则计算数组初始值设定项中的每个表达式的值，并将该值赋值给它的相应数组元素。计算和赋值按数组初始值设定项中各表达式的写入顺序执行，换言之，按递增的索引顺序初始化元素，最右边的维度首先增加。如果给定表达式的计算或其面向相应数组元素的赋值导致异常，则不初始化其他元素（剩余的元素将因此具有它们的默认值）。

数组创建表达式允许实例化一个数组，并且它的元素也属于数组类型，但必须手动初始化这类数组的元素。例如，语句

int[][] a = new int[100][];

创建一个包含 100 个 int[] 类型的元素的一维数组。每个元素的初始值为 null。想让数组创建表达式同时也实例化它所指定的子数组是不可能的，因而，语句

int[][] a = new int[100][5]; // Error

会导致编译时错误。实例化子数组必须改为手动执行，如下所示

int[][] a = new int[100][];

for (int i = 0; i < 100; i++) a[i] = new int[5];

当数组的数组具有“矩形”形状时，即当子数组全都具有相同的长度时，使用多维数组更有效。在上面的示例中，实例化一个数组的数组时，实际上创建了 101 个对象（1 个外部数组和 100 个子数组）。相反，

int[,] = new int[100, 5];

只创建单个对象（即一个二维数组）并在单个语句中完成分配。


7.5.10.3委托创建表达式.1064739

7.5.10.3 委托创建表达式

委托创建表达式用于创建委托类型的新实例。

delegate-creation-expression:（委托创建表达式：）

new delegate-type ( expression )（new 委托类型 ( 表达式 )）

委托创建表达式的参数必须是方法组（第 7.1 节）或委托类型的值。如果参数是方法组，则它标识方法和（对于实例方法）为其创建委托的对象。如果参数是委托类型的值，则它标识为其创建副本的委托实例。

new D(E) 形式（其中 D 是委托类型，E 是表达式）的委托创建表达式的编译时处理包括以下步骤：

· 如果 E 是方法组：

· 由 E 标识的方法集必须正好包括一个与 D 兼容（第 15.1 节）的方法，并且此方法成为新创建的委托引用的方法。如果不存在匹配的方法，或者如果存在多个匹配的方法，则发生编译时错误。如果选定的方法是实例方法，则与 E 关联的实例表达式确定委托的目标对象。

· 与方法调用中一样，选定的方法也必须与方法组的上下文兼容：如果该方法是静态方法，则方法组必须是由一个简称或关于类型的一个成员访问产生的。如果该方法是实例方法，则方法组必须是由一个简称或关于变量或值的一个成员访问产生的。如果选定的方法不匹配方法组的上下文，则发生编译时错误。

· 结果是类型 D 的值，即一个引用选定方法和目标对象的新创建的委托。

· 否则，如果 E 是委托类型的值：

· D 和 E 必须兼容（第 15.1 节）；否则将发生编译时错误。

· 结果是类型 D 的值，即一个引用与 E 具有相同调用列表的新创建的委托。

· 否则，委托创建表达式无效，并发生编译时错误。

new D(E) 形式（其中 D 是委托类型，E 是表达式）的委托创建表达式的运行时处理包括以下步骤：

· 如果 E 是方法组：

· 如果在编译时选择的方法是静态方法，则委托的目标对象为 null。否则，选定的方法是实例方法，并且从 E 的关联实例表达式确定委托的目标对象：

计算实例表达式。如果此计算导致异常，则不执行进一步的操作。

如果实例表达式为引用类型，则由实例表达式计算的值成为目标对象。如果目标对象为 null，则引发 System.NullReferenceException 并且不执行其他步骤。

如果实例表达式为值类型，则执行装箱操作（第 4.3.1 节）以将值转换为对象，并且此对象成为目标对象。

· 为委托类型 D 的一个新实例分配存储位置。如果没有足够的可用内存来为新实例分配存储位置，则引发 System.OutOfMemoryException，并且不执行进一步的操作。

· 用对在编译时确定的方法的引用和对上面计算的目标对象的引用初始化新委托实例。

· 如果 E 是委托类型的值：

· 计算 E。如果此计算导致异常，则不执行进一步的操作。

· 如果 E 的值为 null，则引发 System.NullReferenceException，并且不执行进一步的操作。

· 分配委托类型 D 的新实例。如果没有足够的可用内存来为新实例分配存储位置，则引发 System.OutOfMemoryException，并且不执行进一步的操作。

· 用与 E 给定的委托实例相同的调用列表初始化新委托实例。

委托的调用列表在实例化委托时确定并在委托的整个生存期期间保持不变。换句话说，一旦创建了委托，就不可能更改它的可调用目标实体。当组合两个委托或从一个委托中移除另一个委托（第 15.1 节）时，将产生新委托；现有委托的内容不更改。

不可能创建引用属性、索引器、用户定义的运算符、实例构造函数、析构函数或静态构造函数的委托。

如上所述，当从方法组创建一个委托时，需根据该委托的形参表和返回类型来确定要选择的重载方法。在下面的示例中，

delegate double DoubleFunc(double x);

class A

{

DoubleFunc f = new DoubleFunc(Square);

static float Square(float x) {

return x * x;

}

static double Square(double x) {

return x * x;

}

}

A.f 字段将由引用第二个 Square 方法的委托初始化，因为该方法与 DoubleFunc 的形参表和返回类型完全匹配。如果第二个 Square 方法不存在，则将发生编译时错误。


7.5.11typeof运算符.1074935

7.5.11 typeof 运算符

typeof 运算符用于获取类型的 System.Type 对象。

typeof-expression:（typeof 表达式：）

typeof ( type )（typeof ( 类型 )）
typeof ( void )

typeof 表达式的第一种形式由 typeof 关键字，后跟带括号的“类型”组成。这种形式的表达式的结果是与给定的类型对应的 System.Type 对象。任何给定的类型都只有一个 System.Type 对象。这意味着对类型 T 而言，typeof(T) == typeof(T) 始终为 true。

typeof 表达式的第二种形式由 typeof 关键字，后跟带括号的 void 关键字组成。这种形式的表达式的结果是一个表示“类型不存在”的 System.Type 对象。这种通过 typeof(void) 返回的类型对象与为任何类型返回的类型对象截然不同。这种特殊的类型对象在这样的类库中很有用：它允许在源语言中能仔细考虑一些方法，希望有一种方式以用 System.Type 的实例来表示任何方法（包括 void 方法）的返回类型。

示例

using System;

class Test

{

static void Main() {

Type[] t = {

typeof(int),

typeof(System.Int32),

typeof(string),

typeof(double[]),

typeof(void)

};

for (int i = 0; i < t.Length; i++) {

Console.WriteLine(t[i].FullName);

}

}

}

产生下列输出：

System.Int32

System.Int32

System.String

System.Double[]

System.Void

注意 int 和 System.Int32 是相同的类型。


7.5.12checked和unchecked运算符.1080109

7.5.12 checked 和 unchecked 运算符

checked 和 unchecked 运算符用于控制上下文中是否实施关于整型算术运算和转换的溢出检查。

checked-expression:（checked 表达式：）

checked ( expression )（checked ( 表达式 )）

unchecked-expression:（unchecked 表达式：）

unchecked ( expression )（unchecked ( 表达式 )）

checked 运算符在 checked 上下文中计算所包含的表达式，unchecked 运算符在 unchecked 上下文中计算所包含的表达式。除了在给定的溢出检查上下文中计算所包含的表达式外，checked 表达式或 unchecked 表达式与带括号的表达式（第 7.5.3 节）完全对应。

也可以通过 checked 和 unchecked 语句（第 8.11 节）控制溢出检查上下文。

下列运算符受由 checked 和 unchecked 运算符和语句所确定的溢出检查上下文影响：

· 预定义的 ++ 和 -- 一元运算符（第 7.5.9 节）和（第 7.6.5 节）（当操作数为整型时）。

· 预定义的 - 一元运算符（第 7.6.2 节）（当操作数为整型时）。

· 预定义的 +、-、* 和 / 二元运算符（第 7.7 节）（当两个操作数均为整型时）。

· 从一个整型到另一个整型或从 float 或 double 到整型的显式数值转换（第 6.2.1 节）。

当上面的运算之一产生的结果太大，无法用目标类型表示时，执行运算的上下文控制由此引起的行为：

· 在 checked 上下文中，如果运算发生在一个常数表达式（第 7.15 节）中，则发生编译时错误。否则，当在运行时执行运算时，引发 System.OverflowException。

· 在 unchecked 上下文中，计算的结果被截断，放弃不适合目标类型的任何高序位。

对于不用任何 checked 或 unchecked 运算符或语句括起来的非常数表达式（在运行时计算的表达式），除非外部因素（如编译器开关和执行环境配置）要求 checked 计算，否则默认溢出检查上下文为 unchecked。

对于常数表达式（可以在编译时完全计算的表达式），默认溢出检查上下文总是 checked。除非将常数表达式显式放置在 unchecked 上下文中，否则在表达式的编译时计算期间发生的溢出总是导致编译时错误。

在下面的示例中，

class Test

{

static readonly int x = 1000000;

static readonly int y = 1000000;

static int F() {

return checked(x * y); // Throws OverflowException

}

static int G() {

return unchecked(x * y); // Returns -727379968

}

static int H() {

return x * y; // Depends on default

}

}

由于在编译时没有表达式可以计算，所以不报告编译时错误。在运行时，F 方法引发 System.OverflowException，G 方法返回 -727379968（从超出范围的结果中取较低的 32 位）。H 方法的行为取决于编译时设定的默认溢出检查上下文，但它不是与 F 相同就是与 G 相同。

在下面的示例中，

class Test

{

const int x = 1000000;

const int y = 1000000;

static int F() {

return checked(x * y); // Compile error, overflow

}

static int G() {

return unchecked(x * y); // Returns -727379968

}

static int H() {

return x * y; // Compile error, overflow

}

}

在计算 F 和 H 中的常数表达式时发生的溢出导致报告编译时错误，原因是表达式是在 checked 上下文中计算的。在计算 G 中的常数表达式时也发生溢出，但由于计算是在 unchecked 上下文中发生的，所以不报告溢出。

checked 和 unchecked 运算符只影响原文包含在“(”和“)”标记中的那些运算的溢出检查上下文。这些运算符不影响因计算包含的表达式而调用的函数成员。在下面的示例中，

class Test

{

static int Multiply(int x, int y) {

return x * y;

}

static int F() {

return checked(Multiply(1000000, 1000000));

}

}

在 F 中使用 checked 不影响 Multiply 中的 x * y 计算，因此 x * y 在默认溢出检查上下文中计算。

当以十六进制表示法编写有符号整型的常数时，unchecked 运算符很方便。例如：

class Test

{

public const int AllBits = unchecked((int)0xFFFFFFFF);

public const int HighBit = unchecked((int)0x80000000);

}

上面的两个十六进制常数均为 uint 类型。因为这些常数超出了 int 范围，所以如果不使用 unchecked 运算符，强制转换到 int 将产生编译时错误。

checked 和 unchecked 运算符和语句允许程序员控制一些数值计算的某些方面。当然，某些数值运算符的行为取决于其操作数的数据类型。例如，两个小数相乘总是导致溢出异常，即使是在显式 unchecked 结构内。同样，两个浮点数相乘从不会导致溢出异常，即使是在显式 checked 结构内。另外，其他运算符“从不”受检查模式（不管是默认的还是显式的）的影响。


7.6一元运算符.1094295

7.6 一元运算符

+、-、!、~、++、-- 和强制转换运算符被称为一元运算符。

unary-expression:（一元表达式：）

primary-expression（基本表达式）
+ unary-expression（+ 一元表达式）
- unary-expression（- 一元表达式）
! unary-expression（! 一元表达式）
~ unary-expression（~ 一元表达式）
pre-increment-expression（前增量表达式）
pre-decrement-expression（前减量表达式）
cast-expression（强制转换表达式）


7.6.1一元加运算符.1096932

7.6.1 一元加运算符

对于 +x 形式的运算，应用一元运算符重载决策（第 7.2.3 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果类型是该运算符的返回类型。预定义的一元加运算符为：

int operator +(int x);

uint operator +(uint x);

long operator +(long x);

ulong operator +(ulong x);

float operator +(float x);

double operator +(double x);

decimal operator +(decimal x);

对于这些运算符，结果只是操作数的值。


7.6.2一元减运算符.1098024

7.6.2 一元减运算符

对于 –x 形式的运算，应用一元运算符重载决策（第 7.2.3 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果类型是该运算符的返回类型。预定义的否定运算符为：

· 整数否定：

int operator –(int x);

long operator –(long x);

通过从 0 中减去 x 来计算结果。如果 x 的值是操作数类型的最小可表示值（对 int 是 ?231，对 long 是 ?263），则 x 的算术否定在操作数类型中不可表示。如果这种情况发生在 checked 上下文中，则引发 System.OverflowException；如果它发生在 unchecked 上下文中，则结果是操作数的值而且不报告溢出。

如果否定运算符的操作数为 uint 类型，则它转换为 long 类型，并且结果的类型为 long。例外是允许将 int 值 ?2147483648 (?231) 写为十进制整数（第 2.4.4.2 节）的规则。

如果否定运算符的操作数为 ulong 类型，则发生编译时错误。例外是允许将 long 值 ?9223372036854775808 (?263) 写为十进制整数（第 2.4.4.2 节）的规则。

· 浮点否定：

float operator –(float x);

double operator –(double x);

结果是符号被反转的 x 的值。如果 x 为 NaN，则结果也为 NaN。

· 小数否定：

decimal operator –(decimal x);

通过从 0 中减去 x 来计算结果。小数否定等效于使用 System.Decimal 类型的一元减运算符。


7.6.3逻辑否定运算符.1101444

Powered By Gavin http://www.CSharpWeb.com

7.6.3 逻辑否定运算符

对于 !x 形式的运算，应用一元运算符重载决策（第 7.2.3 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果类型是该运算符的返回类型。只存在一个预定义的逻辑否定运算符：

bool operator !(bool x);

此运算符计算操作数的逻辑否定：如果操作数为 true，则结果为 false。如果操作数为 false，则结果为 true。


7.6.4按位求补运算符.1102310

7.6.4 按位求补运算符

对于 ~x 形式的运算，应用一元运算符重载决策（第 7.2.3 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果类型是该运算符的返回类型。预定义的按位求补运算符为：

int operator ~(int x);

uint operator ~(uint x);

long operator ~(long x);

ulong operator ~(ulong x);

对于这些运算符，运算结果为 x 的按位求补。

每个 E 枚举类型都隐式地提供下列按位求补运算符：

E operator ~(E x);

x（其中 x 是具有基础类型 U 的枚举类型 E 的表达式）的计算结果与 (E)(~(U)x) 的计算结果完全相同。


7.6.5前缀增量和减量运算符.1104114

7.6.5 前缀增量和减量运算符

pre-increment-expression:（前增量表达式：）

++ unary-expression（++ 一元表达式）

pre-decrement-expression:（前减量表达式：）

-- unary-expression（-- 一元表达式）

前缀增量或减量运算的操作数必须是属于变量、属性访问或索引器访问类别的表达式。该运算的结果是与操作数类型相同的值。

如果前缀增量或减量运算的操作数是属性或索引器访问，则属性或索引器必须同时具有 get 和 set 访问器。如果不是这样，则发生编译时错误。

一元运算符重载决策（第 7.2.3 节）被用于选择一个特定的运算符实现。存在着适用于下列类型的预定义 ++ 和 -- 运算符：sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 和任何枚举类型。预定义 ++ 运算符返回的结果值为操作数加上 1，预定义 -- 运算符返回的结果值为操作数减去 1。

++x 或 --x 形式的前缀增量或减量运算的运行时处理包括以下步骤：

· 如果 x 属于变量：

· 计算 x 以产生变量。

· 调用选定的运算符，将 x 的值作为参数。

· 运算符返回的值存储在由 x 的计算结果给定的位置中。

· 运算符返回的值成为该运算的结果。

· 如果 x 属于属性或索引器访问：

· 计算与 x 关联的实例表达式（如果 x 不是 static）和参数列表（如果 x 是索引器访问），结果用于后面的 get 和 set 访问器调用。

· 调用 x 的 get 访问器。

· 调用选定的运算符，将 get 访问器返回的值作为参数。

· 调用 x 的 set 访问器，将运算符返回的值作为 value 参数。

· 运算符返回的值成为该运算的结果。

++ 和 -- 运算符也支持后缀表示法（第 7.5.9 节）。x++ 或 x-- 的结果是运算“之前”x 的值，而 ++x 或 --x 的结果是运算“之后”x 的值。在任何一种情况下，运算后 x 本身都具有相同的值。

operator ++ 或 operator -- 的实现既可以用后缀表示法调用，也可以用前缀表示法调用。但是，不能让这两种表示法分别去调用该运算符的不同的实现。


7.6.6强制转换表达式.1110681

7.6.6 强制转换表达式

强制转换表达式用于将表达式显式转换为给定类型。

cast-expression:（强制转换表达式：）

( type ) unary-expression（( 类型 ) 一元表达式）

(T)E 形式（其中 T 是“类型”，E 是一元表达式）的强制转换表达式执行把 E 的值转换到类型 T 的显式转换（第 6.2 节）。如果不存在从 E 的类型到 T 的显式转换，则发生编译时错误。否则，结果为显式转换产生的值。即使 E 表示变量，结果也总是为值类别。

强制转换表达式的文法表示可能导致某些语法多义性。例如，表达式 (x)-y 既可以按强制转换表达式解释（-y 到类型 x 的强制转换），也可以按结合了带括号的表达式的增量表达式解释（计算 x - y 的值）。

下列规则用于解决强制转换表达式的多义性：仅当下列条件至少有一个为真时，在括号中的由一个或多个标记（第 2.4 节）排列起来的序列才被视为强制转换表达式的开始：

· 标记的序列是关于类型的正确的文法表示，但对于表达式则不是。

· 标记的序列是关于类型的正确的文法表示，而且紧跟在右括号后面的标记是标记“~”、标记“!”、标记“(”、标识符（第 2.4.1 节）、文本（第 2.4.4 节）或除 as 和 is 外的任何关键字（第 2.4.3 节）。

上面出现的术语“正确的文法表示”仅指标记的序列必须符合特定的文法产生式。它并没有特别考虑任何构成标识符的实际含义。例如，如果 x 和 y 是标识符，则 x.y 对于类型是正确的文法表示，即使 x.y 实际并不表示类型。

从上述消除歧义规则可以导出下述结论：如果 x 和 y 是标识符，则 (x)y、(x)(y) 和 (x)(-y) 为强制转换表达式，但 (x)-y 不是，即使 x 标识的是类型。然而，如果 x 是一个标识预定义类型（如 int）的关键字，则所有四种形式均为强制转换表达式（因为这种关键字本身不可能是表达式）。


7.7算术运算符.1115221

7.7 算术运算符

*、/、%、+ 和 - 运算符称为算术运算符。

multiplicative-expression:（乘法表达式：）

unary-expression（一元表达式）
multiplicative-expression * unary-expression（乘法表达式 * 一元表达式）
multiplicative-expression / unary-expression（乘法表达式 / 一元表达式）
multiplicative-expression % unary-expression（乘法表达式 % 一元表达式）

additive-expression:（增量表达式：）

multiplicative-expression（乘法表达式）
additive-expression + multiplicative-expression（加法表达式 + 乘法表达式）
additive-expression – multiplicative-expression（加法表达式 – 乘法表达式）


7.7.1乘法运算符.1119308

7.7.1 乘法运算符

对于 x * y 形式的运算，应用二元运算符重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

下面列出了预定义的乘法运算符。这些运算符均计算 x 和 y 的乘积。

· 整数乘法：

int operator *(int x, int y);

uint operator *(uint x, uint y);

long operator *(long x, long y);

ulong operator *(ulong x, ulong y);

在 checked 上下文中，如果乘积超出结果类型的范围，则引发 System.OverflowException。在 unchecked 上下文中，不报告溢出并且结果类型范围外的任何有效高序位都被放弃。

· 浮点乘法：

float operator *(float x, float y);

double operator *(double x, double y);

根据 IEEE 754 算法法则计算乘积。下表列出了非零有限值、零、无限值和 NaN 的所有可能的组合结果。在表中，x 和 y 是正有限值，z 是 x * y 的结果。如果结果对目标类型而言太大，则 z 为无穷大。如果结果对目标类型而言太小，则 z 为零。

 | +y | –y | +0 | –0 | +∞ | –∞ | NaN
+x | +z | –z | +0 | –0 | +8 | –8 | NaN
–x | –z | +z | –0 | +0 | –8 | +8 | NaN
+0 | +0 | –0 | +0 | –0 | NaN | NaN | NaN
–0 | –0 | +0 | –0 | +0 | NaN | NaN | NaN
+∞ | +8 | –8 | NaN | NaN | +8 | –8 | NaN
–∞ | –8 | +8 | NaN | NaN | –8 | +8 | NaN
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN

· 小数乘法：

decimal operator *(decimal x, decimal y);

如果结果值太大，不能用 decimal 格式表示，则引发 System.OverflowException。如果结果值太小，不能用 decimal 格式表示，则结果为零。在进行任何舍入之前，结果的小数位数是两个操作数的小数位数的和。

小数乘法等效于使用 System.Decimal 类型的乘法运算符。


7.7.2除法运算符.1138117

7.7.2 除法运算符

对于 x / y 形式的运算，应用二元运算符重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

下面列出了预定义的除法运算符。这些运算符均计算 x 和 y 的商。

· 整数除法：

int operator /(int x, int y);

uint operator /(uint x, uint y);

long operator /(long x, long y);

ulong operator /(ulong x, ulong y);

如果右操作数的值为零，则引发 System.DivideByZeroException。

除法将结果舍入到零，并且结果的绝对值是小于两个操作数的商的绝对值的最大可能整数。当两个操作数符号相同时，结果为零或正；当两个操作数符号相反时，结果为零或负。

如果左操作数为最小可表示 int 或 long 值，右操作数为 -1，则发生溢出。无论操作是在 checked 还是在 unchecked 上下文中发生，此时总是引发 System.OverflowException。

· 浮点除法：

float operator /(float x, float y);

double operator /(double x, double y);

根据 IEEE 754 算法法则计算商。下表列出了非零的有限值、零、无穷大和 NaN 的所有可能的组合结果。在表中，x 和 y 是正有限值，z 是 x / y 的结果。如果结果对目标类型而言太大，则 z 为无穷大。如果结果对目标类型而言太小，则 z 为零。

 | +y | –y | +0 | –0 | +∞ | –∞ | NaN
+x | +z | –z | +8 | –8 | +0 | –0 | NaN
–x | –z | +z | –8 | +8 | –0 | +0 | NaN
+0 | +0 | –0 | NaN | NaN | +0 | –0 | NaN
–0 | –0 | +0 | NaN | NaN | –0 | +0 | NaN
+∞ | +8 | –8 | +8 | –8 | NaN | NaN | NaN
–∞ | –8 | +8 | –8 | +8 | NaN | NaN | NaN
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN

· 小数除法：

decimal operator /(decimal x, decimal y);

如果右操作数的值为零，则引发 System.DivideByZeroException。如果结果值太大，不能用 decimal 格式表示，则引发 System.OverflowException。如果结果值太小，不能用 decimal 格式表示，则结果为零。结果的小数位数是最小的小数位数，它保留等于最接近真实算术结果的可表示小数值的结果。

小数除法等效于使用 System.Decimal 类型的除法运算符。


7.7.3余数运算符.1157536

7.7.3 余数运算符

对于 x % y 形式的运算，应用二元运算符重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

下面列出了预定义的余数运算符。这些运算符均计算 x 除以 y 的余数。

· 整数余数：

int operator %(int x, int y);

uint operator %(uint x, uint y);

long operator %(long x, long y);

ulong operator %(ulong x, ulong y);

x % y 的结果是表达式 x – (x / y) * y 的值。如果 y 为零，则引发 System.DivideByZeroException。余数运算符从不导致溢出。

· 浮点余数：

float operator %(float x, float y);

double operator %(double x, double y);

下表列出了非零的有限值、零、无穷大和 NaN 的所有可能组合的结果。在表中，x 和 y 是正有限值，z 是 x % y 的结果并按 x – n * y（其中 n 是小于或等于 x / y 的最大可能整数）计算。这种计算余数的方法类似于用于整数操作数的方法，但不同于 IEEE 754 定义（在此定义中，n 是最接近 x / y 的整数）。

 | +y | –y | +0 | –0 | +∞ | –∞ | NaN
+x | +z | +z | NaN | NaN | x | x | NaN
–x | –z | –z | NaN | NaN | –x | –x | NaN
+0 | +0 | +0 | NaN | NaN | +0 | +0 | NaN
–0 | –0 | –0 | NaN | NaN | –0 | –0 | NaN
+∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN
–∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN

· 小数余数：

decimal operator %(decimal x, decimal y);

如果右操作数的值为零，则引发 System.DivideByZeroException。在进行任何舍入之前，结果的小数位数是两个操作数中较大的小数位数，而且结果的符号与 x 的相同（如果非零）。

小数余数等效于使用 System.Decimal 类型的余数运算符。


7.7.4加法运算符.1177824

7.7.4 加法运算符

对于 x + y 形式的运算，应用二元运算符重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

下面列出了预定义的加法运算符。对于数值和枚举类型，预定义的加法运算符计算两个操作数的和。当一个或两个操作数为 string 类型时，预定义的加法运算符把两个操作数的字符串表示形式串联起来。

· 整数加法：

int operator +(int x, int y);

uint operator +(uint x, uint y);

long operator +(long x, long y);

ulong operator +(ulong x, ulong y);

在 checked 上下文中，如果和超出结果类型的范围，则引发 System.OverflowException。在 unchecked 上下文中，不报告溢出并且结果类型范围外的任何有效高序位都被放弃。

· 浮点加法：

float operator +(float x, float y);

double operator +(double x, double y);

根据 IEEE 754 算法法则计算和。下表列出了非零有限值、零、无限值和 NaN 的所有可能组合的结果。在表中，x 和 y 是非零的有限值，z 是 x + y 的结果。如果 x 和 y 的绝对值相同但符号相反，则 z 为正零。如果 x + y 太大，不能用目标类型表示，则 z 是与 x + y 具有相同符号的无穷大。

 | y | +0 | –0 | +∞ | –∞ | NaN
x | z | x | x | +8 | –8 | NaN
+0 | y | +0 | +0 | +8 | –8 | NaN
–0 | y | +0 | –0 | +8 | –8 | NaN
+∞ | +8 | +8 | +8 | +8 | NaN | NaN
–∞ | –8 | –8 | –8 | NaN | –8 | NaN
NaN | NaN | NaN | NaN | NaN | NaN | NaN

· 小数加法：

decimal operator +(decimal x, decimal y);

如果结果值太大，不能用 decimal 格式表示，则引发 System.OverflowException。在进行任何舍入之前，结果的小数位数是两个操作数中较大的小数位数。

小数加法等效于使用 System.Decimal 类型的加法运算符。

· 枚举加法。每个枚举类型都隐式提供下列预定义运算符，其中 E 为枚举类型，U 为 E 的基础类型：

E operator +(E x, U y);

E operator +(U x, E y);

这些运算符严格按 (E)((U)x + (U)y) 计算。

· 字符串串联：

string operator +(string x, string y);

string operator +(string x, object y);

string operator +(object x, string y);

当一个或两个操作数为 string 类型时，二元 + 运算符执行字符串串联。在字符串串联运算中，如果它的一个操作数为 null，则用空字符串来替换此操作数。否则，任何非字符串参数都通过调用从 object 类型继承的虚 ToString 方法，转换为它的字符串表示形式。如果 ToString 返回 null，则替换成空字符串。

using System;

class Test

{

static void Main() {

string s = null;

Console.WriteLine("s = >" + s + "<"); // displays s = ><

int i = 1;

Console.WriteLine("i = " + i); // displays i = 1

float f = 1.2300E+15F;

Console.WriteLine("f = " + f); // displays f = 1.23+E15

decimal d = 2.900m;

Console.WriteLine("d = " + d); // displays d = 2.900

}

}

字符串串联运算符的结果是一个字符串，由左操作数的字符后跟右操作数的字符组成。字符串串联运算符从不返回 null 值。如果没有足够的内存可用于分配结果字符串，则可能引发 System.OutOfMemoryException。

· 委托组合。每个委托类型都隐式提供以下预定义运算符，其中 D 是委托类型：

D operator +(D x, D y);

当两个操作数均为某个委托类型 D 时，二元 + 运算符执行委托组合。（如果操作数具有不同的委托类型，则发生编译时错误。）如果第一个操作数为 null，则运算结果为第二个操作数的值（即使此操作数也为 null）。否则，如果第二个操作数为 null，则运算结果为第一个操作数的值。否则，运算结果是一个新委托实例，该实例在被调用时调用第一个操作数，然后调用第二个操作数。有关委托组合的示例，请参见第 7.7.5 节和第 15.3 节。由于 System.Delegate 不是委托类型，因此不为它定义 operator +。


7.7.5减法运算符.1197845

7.7.5 减法运算符

对于 x – y 形式的运算，应用二元运算符重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

下面列出了预定义的减法运算符。这些运算符均从 x 中减去 y。

· 整数减法：

int operator –(int x, int y);

uint operator –(uint x, uint y);

long operator –(long x, long y);

ulong operator –(ulong x, ulong y);

在 checked 上下文中，如果差超出结果类型的范围，则引发 System.OverflowException。在 unchecked 上下文中，不报告溢出并且结果类型范围外的任何有效高序位都被放弃。

· 浮点减法：

float operator –(float x, float y);

double operator –(double x, double y);

根据 IEEE 754 算术法则计算差。下表列出了非零的有限值、零、无穷大和 NaN 的所有可能组合的结果。在表中，x 和 y 是非零的有限值，z 是 x – y 的结果。如果 x 和 y 相等，则 z 为正零。如果 x – y 太大，不能用目标类型表示，则 z 是与 x – y 具有相同符号的无穷大。

 | y | +0 | –0 | +∞ | –∞ | NaN
x | z | x | x | –8 | +8 | NaN
+0 | –y | +0 | +0 | –8 | +8 | NaN
–0 | –y | –0 | +0 | –8 | +8 | NaN
+∞ | +8 | +8 | +8 | NaN | +8 | NaN
–∞ | –8 | –8 | –8 | –8 | NaN | NaN
NaN | NaN | NaN | NaN | NaN | NaN | NaN

· 小数减法：

decimal operator –(decimal x, decimal y);

如果结果值太大，不能用 decimal 格式表示，则引发 System.OverflowException。在进行任何舍入之前，结果的小数位数是两个操作数中较大的小数位数。

小数减法等效于使用 System.Decimal 类型的减法运算符。

· 枚举减法。每个枚举类型都隐式提供下列预定义运算符，其中 E 为枚举类型，U 为 E 的基础类型：

U operator –(E x, E y);

该运算符严格按 (U)((U)x – (U)y) 计算。换言之，运算符计算 x 和 y 的有序值之间的差，结果类型是枚举的基础类型。

E operator –(E x, U y);

该运算符严格按 (E)((U)x – y) 计算。换言之，该运算符从枚举的基础类型中减去一个值，得到枚举的值。

· 委托移除。每个委托类型都隐式提供以下预定义运算符，其中 D 是委托类型：

D operator –(D x, D y);

当两个操作数均为某个委托类型 D 时，二元 – 运算符执行委托移除。如果操作数具有不同的委托类型，则发生编译时错误。如果第一个操作数为 null，则运算结果为 null。否则，如果第二个操作数为 null，则运算结果为第一个操作数的值。否则，两个操作数都表示包含一项或多项的调用列表（第 15.1 节），并且只要第二个操作数列表是第一个操作数列表的适当的邻接的子列表，那么结果就是从第一个操作数的调用列表中移除了第二个操作数的调用列表所含各项后的一个新调用列表。（为确定子列表是否相等，用委托相等运算符比较相对应的项，请参见第 7.9.8 节）。否则，结果为左操作数的值。在此过程中两个操作数的列表均未被更改。如果第二个操作数的列表与第一个操作数的列表中的多个邻接项子列表相匹配，则移除最右边的那个匹配邻接项的子列表。如果移除导致空列表，则结果为 null。例如：

delegate void D(int x);

class C

{

public static void M1(int i) { /* ... */ }

public static void M2(int i) { /* ... */ }

}

class Test

{

static void Main() {

D cd1 = new D(C.M1);

D cd2 = new D(C.M2);

D cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1

cd3 -= cd1; // => M1 + M2 + M2

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1

cd3 -= cd1 + cd2; // => M2 + M1

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1

cd3 -= cd2 + cd2; // => M1 + M1

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1

cd3 -= cd2 + cd1; // => M1 + M2

cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1

cd3 -= cd1 + cd1; // => M1 + M2 + M2 + M1

}

}


7.8移位运算符.1219787

7.8 移位运算符

<< 和 >> 运算符用于执行移位运算。

shift-expression:（移位表达式：）

additive-expression（加法表达式）
shift-expression << additive-expression（移位表达式 << 加法表达式）
shift-expression >> additive-expression（移位表达式 >> 加法表达式）

对于 x << count 或 >> count 形式的运算，应用二元运算符重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

当声明重载移位运算符时，第一个操作数的类型必须总是包含运算符声明的类或结构，并且第二个操作数的类型必须总是 int。

下面列出了预定义的移位运算符。

· 左移位：

int operator <<(int x, int count);

uint operator <<(uint x, int count);

long operator <<(long x, int count);

ulong operator <<(ulong x, int count);

<< 运算符将 x 向左位移若干个位，具体计算方法如下所述。

放弃 x 中经移位后会超出结果类型范围的那些高序位，将其余的位向左位移，将空出来的低序位均设置为零。

· 右移位：

int operator >>(int x, int count);

uint operator >>(uint x, int count);

long operator >>(long x, int count);

ulong operator >>(ulong x, int count);

>> 运算符将 x 向右位移若干个位，具体计算方法如下所述。

当 x 为 int 或 long 类型时，放弃 x 的低序位，将剩余的位向右位移，如果 x 非负，则将高序空位位置设置为零，如果 x 为负，则将其设置为 1。

当 x 为 uint 或 ulong 类型时，放弃 x 的低序位，将剩余的位向右位移，并将高序空位位置设置为零。

对于预定义运算符，位移的位数按下面这样计算：

· 当 x 的类型为 int 或 uint 时，位移计数由 count 的低序的 5 位给出。换言之，位移计数由 count & 0x1F 计算出。

· 当 x 的类型为 long 或 ulong 时，位移计数由 count 的低序的 6 位给出。换言之，位移计数由 count & 0x3F 计算出。

如果计算位移计数的结果为零，则移位运算符只返回 x 的值。

移位运算从不会导致溢出，并且在 checked 和 unchecked 上下文中产生的结果相同。

当 >> 运算符的左操作数为有符号的整型时，该运算符执行算术右移位，在此过程中，操作数的最有效位（符号位）的值扩展到高序空位位置。当 >> 运算符的左操作数为无符号的整型时，该运算符执行逻辑右移位，在此过程中，高序空位位置总是设置为零。若要执行与由操作数类型确定的不同的移位运算，可以使用显式强制转换。例如，如果 x 是 int 类型的变量，则 unchecked((int)((uint)x >> y)) 运算执行 x 的逻辑右移位。


7.9关系和类型测试运算符.1227091

7.9 关系和类型测试运算符

==、!=、<、>、<=、>=、is 和 as 运算符称为关系和类型测试运算符。

relational-expression:（关系表达式：）

shift-expression（移位表达式）
relational-expression < shift-expression（关系表达式 < 移位表达式）
relational-expression > shift-expression（关系表达式 > 移位表达式）
relational-expression <= shift-expression（关系表达式 <= 移位表达式）
relational-expression >= shift-expression（关系表达式 >= 移位表达式）
relational-expression is type（关系表达式 is 类型）
relational-expression as type（关系表达式 as 类型）

equality-expression:（相等表达式：）

relational-expression（关系表达式）
equality-expression == relational-expression（相等表达式 == 关系表达式）
equality-expression != relational-expression（相等表达式 != 关系表达式）

is 运算符的介绍详见第 7.9.9 节，as 运算符的介绍详见第 7.9.10 节。

==、!=、<、>、<= 和 >= 运算符是比较运算符。对于 x op y 形式为（其中 op 为比较运算符）的运算，应用重载决策（第 7.2.4 节）以选择特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

预定义的比较运算符详见下面各节的介绍。所有预定义的比较运算符都返回 bool 类型的结果，详见下表。

运算 | 结果
x == y | 如果 x 等于 y，则为 true，否则为 false
x != y | 如果 x 不等于 y，则为 true，否则为 false
x < y | 如果 x 小于 y，则为 true，否则为 false
x > y | 如果 x 大于 y，则为 true，否则为 false
x <= y | 如果 x 小于或等于 y，则为 true，否则为 false
x >= y | 如果 x 大于或等于 y，则为 true，否则为 false


7.9.1整数比较运算符.1238870

7.9.1 整数比较运算符

预定义的整数比较运算符为：

bool operator ==(int x, int y);

bool operator ==(uint x, uint y);

bool operator ==(long x, long y);

bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);

bool operator !=(uint x, uint y);

bool operator !=(long x, long y);

bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);

bool operator <(uint x, uint y);

bool operator <(long x, long y);

bool operator <(ulong x, ulong y);

bool operator >(int x, int y);

bool operator >(uint x, uint y);

bool operator >(long x, long y);

bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);

bool operator <=(uint x, uint y);

bool operator <=(long x, long y);

bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);

bool operator >=(uint x, uint y);

bool operator >=(long x, long y);

bool operator >=(ulong x, ulong y);

这些运算符比较两个整数操作数的数值并返回一个 bool 值，该值指示特定的关系是 true 还是 false。


7.9.2浮点比较运算符.1243358

7.9.2 浮点比较运算符

预定义的浮点比较运算符为：

bool operator ==(float x, float y);

bool operator ==(double x, double y);

bool operator !=(float x, float y);

bool operator !=(double x, double y);

bool operator <(float x, float y);

bool operator <(double x, double y);

bool operator >(float x, float y);

bool operator >(double x, double y);

bool operator <=(float x, float y);

bool operator <=(double x, double y);

bool operator >=(float x, float y);

bool operator >=(double x, double y);

这些运算符根据 IEEE 754 标准法则比较操作数：

· 如果两个操作数中的任何一个为 NaN，则对于除 !=（对于此运算符，结果为 true）外的所有运算符，结果为 false。对于任何两个操作数，x != y 总是产生与 !(x == y) 相同的结果。然而，当一个操作数或两个操作数为 NaN 时，<、>、<= 和 >= 运算符不产生与其对应的反向运算符的逻辑否定相同的结果。例如，如果 x 和 y 中的任何一个为 NaN，则 x < y 为 false，而 !(x >= y) 为 true。

· 当两个操作数都不为 NaN 时，这些运算符就按下列排序来比较两个浮点操作数的值

–8 < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +8

这里的 min 和 max 是可以用给定浮点格式表示的最小和最大正有限值。这样排序的显著特点是：

· 负零和正零被视为相等。

· 负无穷大被视为小于所有其他值，但等于其他负无穷大。

· 正无穷大被视为大于所有其他值，但等于其他正无穷大。


7.9.3小数比较运算符.1248718

7.9.3 小数比较运算符

预定义的小数比较运算符为：

bool operator ==(decimal x, decimal y);

bool operator !=(decimal x, decimal y);

bool operator <(decimal x, decimal y);

bool operator >(decimal x, decimal y);

bool operator <=(decimal x, decimal y);

bool operator >=(decimal x, decimal y);

这些运算符中每一个都比较两个小数类型的操作数的数值并返回一个 bool 值，该值指示特定的关系是 true 还是 false。各小数比较等效于使用 System.Decimal 类型的相应关系运算符或相等运算符。


7.9.4布尔相等运算符.1249902

7.9.4 布尔相等运算符

预定义的布尔相等运算符为：

bool operator ==(bool x, bool y);

bool operator !=(bool x, bool y);

如果 x 和 y 都为 true，或者如果 x 和 y 都为 false，则 == 的结果为 true。否则，结果为 false。

如果 x 和 y 都为 true，或者 x 和 y 都为 false，则 != 的结果为 false。否则，结果为 true。当操作数为 bool 类型时，!= 运算符产生与 ^ 运算符相同的结果。


7.9.5枚举比较运算符.1252153

7.9.5 枚举比较运算符

每种枚举类型都隐式提供下列预定义的比较运算符：

bool operator ==(E x, E y);

bool operator !=(E x, E y);

bool operator <(E x, E y);

bool operator >(E x, E y);

bool operator <=(E x, E y);

bool operator >=(E x, E y);

x op y（其中 x 和 y 是具有基础类型 U 的枚举类型 E 的表达式，op 是比较运算符之一）的计算结果与 ((U)x) op ((U)y) 的计算结果完全相同。换言之，枚举类型比较运算符只比较两个操作数的基础整数值。


7.9.6引用类型相等运算符.1254239

7.9.6 引用类型相等运算符

预定义的引用类型相等运算符为：

bool operator ==(object x, object y);

bool operator !=(object x, object y);

这些运算符返回两个引用是相等还是不相等的比较结果。

由于预定义的引用类型相等运算符接受 object 类型的操作数，因此它们适用于所有那些没有为自己声明适用的 operator == 和 operator != 成员的类型。相反，任何适用的用户定义的相等运算符都有效地隐藏上述预定义的引用类型相等运算符。

预定义引用类型相等运算符要求操作数是引用类型值或 null 值；此外，它们还要求存在从一种操作数类型到另一种操作数类型的标准隐式转换（第 6.3.1 节）。除非这两个条件都为真，否则将发生编译时错误。这些规则中值得注意的含义是：

· 使用预定义的引用类型相等运算符比较两个在编译时已能确定是不相同的引用时，会导致编译时错误。例如，如果操作数的编译时类型分属两个类类型 A 和 B，并且 A 和 B 都不从对方派生，则两个操作数不可能引用同一对象。因此，此运算被认为是编译时错误。

· 预定义的引用类型相等运算符不允许比较值类型操作数。因此，除非结构类型声明自己的相等运算符，否则不可能比较该结构类型的值。

· 预定义的引用类型相等运算符从不会导致对它们的操作数执行装箱操作。执行此类装箱操作毫无意义，这是因为对新分配的已装箱实例的引用必将不同于所有其他引用。

对于 x == y 或 x != y 形式的运算，如果存在任何适用的 operator == 或 operator !=，运算符重载决策（第 7.2.4 节）规则将选择该运算符而不是上述的预定义的引用类型相等运算符。不过，始终可以通过将一个或两个操作数显式强制转换为 object 类型来选择预定义的引用类型相等运算符。在以下示例中：

using System;

class Test

{

static void Main() {

string s = "Test";

string t = string.Copy(s);

Console.WriteLine(s == t);

Console.WriteLine((object)s == t);

Console.WriteLine(s == (object)t);

Console.WriteLine((object)s == (object)t);

}

}

产生输出

True

False

False

False

变量 s 和 t 引用两个包含相同字符的不同 string 实例。第一个比较输出 True，这是因为是在两个操作数都为 string 类型时选定预定义的字符串相等运算符（第 7.9.7 节）。其余的比较全都输出 False，这是因为是在一个或两个操作数为 object 类型时选定预定义的引用类型相等运算符。

注意，以上技术对值类型没有意义。在以下示例中：

class Test

{

static void Main() {

int i = 123;

int j = 123;

System.Console.WriteLine((object)i == (object)j);

}

}

输出 False，这是因为强制转换创建对已装箱 int 值的两个单独实例的引用。


7.9.7字符串相等运算符.1260817

7.9.7 字符串相等运算符

预定义的字符串相等运算符为：

bool operator ==(string x, string y);

bool operator !=(string x, string y);

当下列条件中有一个为真时，两个 string 值被视为相等：

· 两个值都为 null。

· 两个值都是对字符串实例的非空引用，这两个字符串不仅具有相同的长度，而且在每个字符位置上的字符亦都彼此相同。

字符串相等运算符比较的是字符串值而不是对字符串的引用。当两个单独的字符串实例包含完全相同的字符序列时，字符串的值相等，但引用不相同。正如第 7.9.6 节中所描述的那样，引用类型相等运算符可用于比较字符串引用而不是字符串值。


7.9.8委托相等运算符.1262422

7.9.8 委托相等运算符

每个委托类型都隐式地提供下列预定义的比较运算符：

bool operator ==(System.Delegate x, System.Delegate y);

bool operator !=(System.Delegate x, System.Delegate y);

两个委托实例按下面这样被视为相等：

· 如果两个委托实例中有一个为 null，则当且仅当它们都为 null 时相等。

· 如果两个委托实例中有一个具有包含一个项的调用列表（第 15.1 节），则当且仅当另一个委托实例也具有包含一个项的调用列表且符合下列条件之一时，两个委托实例相等：

· 两者引用同一静态方法，或

· 两者都引用同一目标对象的同一非静态方法。

· 如果两个委托实例中有一个具有包含两项或更多项的调用列表，则当且仅当它们的调用列表长度相同，并且一个实例的调用列表中的每项依次等于另一个的调用列表中的相应项时，这两个委托实例相等。

注意，根据上面的定义，只要委托具有相同的返回类型和参数类型，即使它们的类型不同也被视为相等。


7.9.9is运算符.1264847

7.9.9 is 运算符

is 运算符用于动态检查对象的运行时类型是否与给定类型兼容。e is T 运算（其中 e 为表达式，T 为类型）的结果是布尔值，表示 e 的类型是否可通过引用转换、装箱转换或取消装箱转换成功地转换为类型 T。运算按下面这样计算：

· 如果 e 的编译时类型与 T 相同，或存在从 e 的编译时类型到 T 的隐式引用转换（第 6.1.4 节）或装箱转换（第 6.1.5 节）：

· 如果 e 为引用类型，则运算结果等效于计算 e != null。

· 如果 e 为值类型，则运算结果为 true。

· 否则，如果存在从 e 的编译时类型到 T 的显式引用转换（第 6.2.3 节）或取消装箱转换（第 6.2.4 节），则执行动态类型检查：

· 如果 e 的值为 null，则结果为 false。

· 否则，假设 R 为 e 引用的实例的运行时类型。如果 R 和 T 的类型相同，或者如果 R 为引用类型且存在从 R 到 T 的隐式引用转换，或者如果 R 为值类型且 T 为 R 实现的接口类型，则结果为 true。

· 否则，结果为 false。

· 否则，不可能实现从 e 到类型 T 的引用转换或装箱转换，且运算结果为 false。

注意 is 运算符只考虑引用转换、装箱转换和取消装箱转换。其他转换（如用户定义的转换）不在 is 运算符考虑之列。


7.9.10as运算符.1269084

7.9.10 as 运算符

as 运算符用于将一个值显式地转换（使用引用转换或装箱转换）为一个给定的引用类型。与强制转换表达式（第 7.6.6 节）不同，as 运算符从不引发异常。它采用的是：如果指定的转换不可能实施，则运算结果为 null。

在 e as T 形式的运算中，e 必须是一个表达式，T 必须是一个引用类型。该运算的结果属于类型 T，且总是可归类为值类别。运算按下面这样计算：

· 如果 e 的编译时类型与 T 相同，则结果就是 e 的值。

· 否则，如果存在从 e 的编译时类型到 T 的隐式引用转换（第 6.1.4 节）或装箱转换（第 6.1.5 节），则执行该转换，且该转换的结果就是运算结果。

· 否则，如果存在从 e 的编译时类型到 T 的显式引用转换（第 6.2.3 节），则执行动态类型检查：

· 如果 e 的值为 null，则结果为具有编译时类型 T 的值 null。

· 否则，假设 R 为 e 引用的实例的运行时类型。如果 R 和 T 的类型相同，或者如果 R 为引用类型且存在从 R 到 T 的隐式引用转换，或者如果 R 为值类型且 T 是由 R 实现的一个接口类型，则结果为由 e 给定的具有编译时类型 T 的引用。

· 否则，结果为具有编译时类型 T 的值 null。

· 否则，指定的转换根本不可能实现，且发生编译时错误。

注意，as 运算符只执行引用转换和装箱转换。不可能使用 as 运算符执行其他转换（如用户定义的转换），应改为使用强制转换表达式来执行这些转换。


7.10逻辑运算符.1273097

7.10 逻辑运算符

&、^ 和 | 运算符称为逻辑运算符。

and-expression:（与表达式：）

equality-expression（相等表达式）
and-expression & equality-expression（与表达式 & 相等表达式）

exclusive-or-expression:（异或表达式：）

and-expression（与表达式）
exclusive-or-expression ^ and-expression（异或表达式 ^ 与表达式）

inclusive-or-expression:（或表达式：）

exclusive-or-expression（异或表达式）
inclusive-or-expression | exclusive-or-expression（异或表达式 | 异或表达式）

对于 x op y 形式的运算（其中 op 为一个逻辑运算符），应用重载决策（第 7.2.4 节）以选择一个特定的运算符实现。操作数转换为所选运算符的参数类型，结果的类型是该运算符的返回类型。

下列章节介绍了预定义的逻辑运算符。


7.10.1整数逻辑运算符.1276406

7.10.1 整数逻辑运算符

预定义的整数逻辑运算符为：

int operator &(int x, int y);

uint operator &(uint x, uint y);

long operator &(long x, long y);

ulong operator &(ulong x, ulong y);

int operator |(int x, int y);

uint operator |(uint x, uint y);

long operator |(long x, long y);

ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);

uint operator ^(uint x, uint y);

long operator ^(long x, long y);

ulong operator ^(ulong x, ulong y);

& 运算符计算两个操作数的按位逻辑 AND，| 运算符计算两个操作数的按位逻辑 OR，而 ^ 运算符计算两个操作数的按位逻辑 XOR。这些运算不可能产生溢出。


7.10.2枚举逻辑运算符.1279026

7.10.2 枚举逻辑运算符

每个枚举类型 E 都隐式地提供下列预定义的逻辑运算符：

E operator &(E x, E y);

E operator |(E x, E y);

E operator ^(E x, E y);

x op y（其中 x 和 y 是具有基础类型 U 的枚举类型 E 的表达式，op 是一个逻辑运算符）的计算结果与 (E)((U)x op (U)y) 的计算结果完全相同。换言之，枚举类型逻辑运算符直接对两个操作数的基础类型执行逻辑运算。


7.10.3布尔逻辑运算符.1280660

7.10.3 布尔逻辑运算符

预定义的布尔逻辑运算符为：

bool operator &(bool x, bool y);

bool operator |(bool x, bool y);

bool operator ^(bool x, bool y);

如果 x 和 y 均为 true，则 x & y 的结果为 true。否则，结果为 false。

如果 x 或 y 为 true，则 x | y 的结果为 true。否则，结果为 false。

如果 x 为 true 而 y 为 false，或者 x 为 false 而 y 为 true，则 x ^ y 的结果为 true。否则，结果为 false。当操作数为 bool 类型时，^ 运算符计算结果与 != 运算符相同。


7.11条件逻辑运算符.1284017

7.11 条件逻辑运算符

&& 和 || 运算符称为条件逻辑运算符。也称为“短路”逻辑运算符。

conditional-and-expression:（条件与表达式：）

inclusive-or-expression（或表达式）
conditional-and-expression && inclusive-or-expression（条件与表达式 && 或表达式）

conditional-or-expression:（条件或表达式：）

conditional-and-expression（条件与表达式）
conditional-or-expression || conditional-and-expression（条件或表达式 || 条件与表达式）

&& 和 || 运算符是 & 和 | 运算符的条件版本：

· x && y 运算对应于 x & y 运算，但仅当 x 为 true 时才计算 y。

· x || y 运算对应于 x | y 运算，但仅当 x 为 false 时才计算 y。

x && y 或 x || y 形式的运算通过采用重载决策（第 7.2.4 节）按运算被写为 x & y 或 x | y 来处理。然后，

· 如果重载决策未能找到单个最佳运算符，或者重载决策选择一个预定义的整数逻辑运算符，则发生编译时错误。

· 否则，如果选定的运算符是一个预定义的布尔逻辑运算符（第 7.10.2 节），则运算按（第 7.11.1 节）中所描述的那样进行处理。

· 否则，选定的运算符为用户定义的运算符，且运算按（第 7.11.2 节）中所描述的那样进行处理。

不可能直接重载条件逻辑运算符。不过，由于条件逻辑运算符按通常的逻辑运算符计算，因此通常的逻辑运算符的重载，在某些限制条件下，也被视为条件逻辑运算符的重载。（第 7.11.2 节）对此有进一步说明。


7.11.1布尔条件逻辑运算符.1289204

Powered By Gavin http://www.CSharpWeb.com

7.11.1 布尔条件逻辑运算符

当 && 或 || 的操作数为 bool 类型时，或者当操作数的类型本身未定义适用的 operator & 或 operator |，但确实定义了到 bool 的隐式转换时，运算按下面这样处理：

· 运算 x && y 的求值过程相当于 x ? y : false。换言之，首先计算 x 并将其转换为 bool 类型。然后，如果 x 为 true，则计算 y 并将其转换为 bool 类型，并且这成为运算结果。否则，运算结果为 false。

· 运算 x || y 的求值过程相当于 x ? true : y。换言之，首先计算 x 并将其转换为 bool 类型。然后，如果 x 为 true，则运算结果为 true。否则，计算 y 并将其转换为 bool 类型，并且这成为运算结果。


7.11.2用户定义的条件逻辑运算符.1291806

7.11.2 用户定义的条件逻辑运算符

当 && 或 || 的操作数所属的类型声明了适用的用户定义的 operator & 或 operator | 时，下列两个条件必须都为真（其中 T 是声明的选定运算符的类型）：

· 选定运算符的返回类型和每个参数的类型都必须为 T。换言之，该运算符必须计算类型为 T 的两个操作数的逻辑 AND 或逻辑 OR，且必须返回类型为 T 的结果。

· T 必须包含关于 operator true 和 operator false 的声明。

如果这两个要求中有一个未满足，则发生编译时错误。如果这两个要求都满足，则通过将用户定义的 operator true 或 operator false 与选定的用户定义的运算符组合在一起来计算 && 或 || 运算：

· x && y 运算按 T.false(x) ? x : T.&(x, y) 进行计算，其中 T.false(x) 是 T 中声明的 operator false 的调用，T.&(x, y) 是选定 operator & 的调用。换言之，首先计算 x，并对结果调用 operator false 以确定 x 是否肯定为假。然后，如果 x 肯定为假，则运算结果为先前为 x 计算的值。否则将计算 y，并对先前为 x 计算的值和为 y 计算的值调用选定的 operator & 以产生运算结果。

· x || y 运算按 T.true(x) ? x : T.|(x, y) 进行计算，其中 T.true(x) 是 T 中声明的 operator true 的调用，T.|(x, y) 是选定 operator | 的调用。换言之，首先计算 x，并对结果调用 operator true 以确定 x 是否肯定为真。然后，如果 x 肯定为真，则运算结果为先前为 x 计算的值。否则将计算 y，并对先前为 x 计算的值和为 y 计算的值调用选定的 operator | 以产生运算结果。

在这两个运算中，x 给定的表达式只计算一次，y 给定的表达式要么不计算，要么只计算一次。

有关实现 operator true 和 operator false 的类型的示例，请参见第 11.4.2 节。


7.12条件运算符.1298675

7.12 条件运算符

?: 运算符称为条件运算符。有时，它也称为三元运算符。

conditional-expression:（条件表达式：）

conditional-or-expression（条件或表达式）
conditional-or-expression ? expression : expression（条件或表达式 ? 表达式 : 表达式）

b ? x : y 形式的条件表达式首先计算条件 b。然后，如果 b 为 true，则计算 x，并且它成为运算结果。否则计算 y，并且它成为运算结果。条件表达式从不同时计算 x 和 y。

条件运算符向右关联，表示运算从右到左分组。例如，a ? b : c ? d : e 形式的表达式按 a ? b : (c ? d : e) 计算。

?: 运算符的第一个操作数必须是可以隐式转换为 bool 的类型的表达式，或者是实现 operator true 的类型的表达式。如果两个要求都不满足，则发生编译时错误。

?: 运算符的第二个和第三个操作数决定了条件表达式的类型。设 X 和 Y 为第二个和第三个操作数所属的类型。然后，

· 如果 X 和 Y 的类型相同，则此类型为该条件表达式的类型。

· 否则，如果存在从 X 到 Y 的隐式转换（第 6.1 节），但不存在从 Y 到 X 的隐式转换，则 Y 为条件表达式的类型。

· 否则，如果存在从 Y 到 X 的隐式转换（第 6.1 节），但不存在从 X 到 Y 的隐式转换，则 X 为条件表达式的类型。

· 否则，无法确定条件表达式的类型，且发生编译时错误。

b ? x : y 形式的条件表达式的运行时处理包括以下步骤：

· 首先计算 b，并确定 b 的 bool 值：

· 如果存在从 b 的类型到 bool 的隐式转换，则执行该隐式转换以产生 bool 值。

· 否则，调用 b 的类型中定义的 operator true 以产生 bool 值。

· 如果以上步骤产生的 bool 值为 true，则计算 x 并将其转换为条件表达式的类型，且这成为条件表达式的结果。

· 否则，计算 y 并将其转换为条件表达式的类型，且这成为条件表达式的结果。


7.13赋值运算符.1305284

7.13 赋值运算符

赋值运算符为变量、属性、事件或索引器元素赋新值。

assignment:（赋值：）

unary-expression assignment-operator expression（一元表达式 赋值运算符 表达式）

assignment-operator: one of（赋值运算符：下列之一）

= += -= *= /= %= &= |= ^= <<= >>=

赋值的左操作数必须是属于变量、属性访问、索引器访问或事件访问类别的表达式。

= 运算符称为简单赋值运算符。它将右操作数的值赋予左操作数给定的变量、属性或索引器元素。简单赋值运算符的左操作数一般不可能是一个事件访问（第 10.7.1 节中描述的例外）。简单赋值运算符的介绍详见第 7.13.1 节。

除 = 运算符以外的赋值运算符称为复合赋值运算符。这些运算符对两个操作数执行指示的运算，然后将结果值赋予左操作数指定的变量、属性或索引器元素。复合赋值运算符的介绍详见第 7.13.2 节。

以事件访问表达式作为左操作数的 += 和 -= 运算符称为事件赋值运算符。当左操作数是一个事件访问时，其他赋值运算符都是无效的。事件赋值运算符在第 7.13.3 节中介绍。

赋值运算符为向右关联，即此类运算从右到左分组。例如，a = b = c 形式的表达式按 a = (b = c) 计算。


7.13.1简单赋值.1308945

7.13.1 简单赋值

= 运算符称为简单赋值运算符。在简单赋值中，右操作数表达式所属的类型必须可隐式地转换为左操作数所属的类型。运算将右操作数的值赋予左操作数指定的变量、属性或索引器元素。

简单赋值表达式的结果是赋予左操作数的值。结果的类型与左操作数相同，且始终为值类别。

如果左操作数为属性或索引器访问，则该属性或索引器必须具有 set 访问器。如果不是这样，则发生编译时错误。

x = y 形式的简单赋值的运行时处理包括以下步骤：

· 如果 x 属于变量：

· 计算 x 以产生变量。

· 如果要求，则计算 y，必要时还需通过隐式转换（第 6.1 节）将其转换为 x 的类型。

· 如果 x 给定的变量是引用类型的数组元素，则执行运行时检查以确保为 y 计算的值与以 x 为其元素的那个数组实例兼容。如果 y 为 null，或存在从 y 引用的实例的实际类型到包含 x 的数组实例的实际元素类型的隐式引用转换（第 6.1.4 节），则检查成功。否则，引发 System.ArrayTypeMismatchException。

· y 的计算和转换后所产生的值存储在 x 的计算所确定的位置中。

· 如果 x 属于属性或索引器访问：

· 计算与 x 关联的实例表达式（如果 x 不是 static）和参数列表（如果 x 是索引器访问），结果用于后面的对 set 访问器调用。

· 计算 y，必要时还需通过隐式转换（第 6.1 节）将其转换为 x 的类型。

· 调用 x 的 set 访问器，并将 y 的上述结果值作为该访问器的 value 参数。

如果存在从 B 到 A 的隐式引用转换，则数组协方差规则（第 12.5 节）允许数组类型 A[] 的值成为对数组类型 B[] 的实例的引用。由于这些规则，对引用类型的数组元素的赋值需要运行时检查以确保所赋的值与数组实例兼容。在下面的示例中，

string[] sa = new string[10];

object[] oa = sa;

oa[0] = null; // Ok

oa[1] = "Hello"; // Ok

oa[2] = new ArrayList(); // ArrayTypeMismatchException

最后的赋值导致引发 System.ArrayTypeMismatchException，这是因为 ArrayList 的实例不能存储在 string[] 的元素中。

当“结构类型”中声明的属性或索引器是赋值的目标时，与属性或索引器访问关联的实例表达式必须为变量类别。如果该实例表达式归类为值类别，则发生编译时错误。由于第 7.5.4 节中所说明的原因，同样的规则也适用于字段。

给定下列声明：

struct Point

{

int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

public int X {

get { return x; }

set { x = value; }

}

public int Y {

get { return y; }

set { y = value; }

}

}

struct Rectangle

{

Point a, b;

public Rectangle(Point a, Point b) {

this.a = a;

this.b = b;

}

public Point A {

get { return a; }

set { a = value; }

}

public Point B {

get { return b; }

set { b = value; }

}

}

在下面的示例中，

Point p = new Point();

p.X = 100;

p.Y = 100;

Rectangle r = new Rectangle();

r.A = new Point(10, 10);

r.B = p;

由于 p 和 r 为变量，因此允许对 p.X、p.Y、r.A 和 r.B 赋值。但是，在以下示例中

Rectangle r = new Rectangle();

r.A.X = 10;

r.A.Y = 10;

r.B.X = 100;

r.B.Y = 100;

由于 r.A 和 r.B 不是变量，所以赋值全部无效。


7.13.2复合赋值.1318585

7.13.2 复合赋值

x op= y 形式的运算是这样来处理的：先将二元运算符重载决策（第 7.2.4 节）应用于运算 x op y。然后，

· 如果选定的运算符的返回类型可“隐式”转换为 x 的类型，则运算按 x = x op y 计算，但 x 只计算一次。

· 否则，如果选定运算符是预定义的运算符，选定运算符的返回类型可“显式”转换为 x 的类型，并且 y 可“隐式”转换为 x 的类型，则运算按 x = (T)(x op y) 计算（其中 T 是 x 的类型），但 x 只计算一次。

· 否则，复合赋值无效，且发生编译时错误。

术语“只计算一次”表示：在 x op y 的计算中，任何 x 的要素表达式的计算结果都临时保存起来，然后在执行对 x 的赋值时重用这些结果。例如，在计算赋值 A()[B()] += C() 时（其中 A 为返回 int[] 的方法，B 和 C 为返回 int 的方法），按 A、B、C 的顺序只调用一次这些方法。

当复合赋值的左操作数为属性访问或索引器访问时，属性或索引器必须同时具有 get 访问器和 set 访问器。如果不是这样，则发生编译时错误。

上面的第二条规则允许在某些上下文中将 x op= y 按 x = (T)(x op y) 计算。按此规则，当左操作数为 sbyte、byte、short、ushort 或 char 类型时，预定义的运算符可用来构造复合运算符。甚至当两个参数都为这些类型之一时，预定义的运算符也产生 int 类型的结果，详见第 7.2.6.2 节中的介绍。因此，不进行强制转换，就不可能把结果赋值给左操作数。

此规则对预定义运算符的直观效果只是：如果同时允许 x op y 和 x = y，则允许 x op= y。在下面的示例中

byte b = 0;

char ch = '\0';

int i = 0;

b += 1; // Ok

b += 1000; // Error, b = 1000 not permitted

b += i; // Error, b = i not permitted

b += (byte)i; // Ok

ch += 1; // Error, ch = 1 not permitted

ch += (char)1; // Ok

每个错误的直观理由是对应的简单赋值也发生错误。


7.13.3事件赋值.1324363

7.13.3 事件赋值

如果 += 或 -= 运算符的左操作数属于事件访问类别，则表达式如下计算：

· 计算事件访问的实例表达式（如果有）。

· 计算 += 或 -= 运算符的右操作数，如果需要，通过隐式转换（第 6.1 节）转换为左操作数的类型。

· 调用该事件的事件访问器，所需的参数列表由右操作数（经过计算和必要的转换后）组成。如果运算符是 +=，则调用 add 访问器。如果运算符是 -=，则调用 remove 访问器。

事件赋值表达式不产生值。因此，事件赋值表达式只在语句表达式（第 8.6 节）的上下文中是有效的。


7.14表达式.1326020

7.14 表达式

一个“表达式”不是一个条件表达式就是一个赋值。

expression:（表达式：）

conditional-expression（条件表达式）
assignment（赋值）


7.15常数表达式.1326692

7.15 常数表达式

常数表达式是在编译时可以完全计算出结果的表达式。

constant-expression:（常数表达式：）

expression（表达式）

常数表达式的类型可以为下列之一：sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、任何枚举类型或空类型。常数表达式中允许下列构造：

· 文本（包括 null）。

· 对类和结构类型的 const 成员的引用。

· 对枚举类型的成员的引用。

· 带括号的子表达式，其自身是常量表达式。

· 强制转换表达式（前提是目标类型为以上列出的类型之一）。

· 预定义的 +、–、! 和 ~ 一元运算符。

· 预定义的 +、–、*、/、%、<<、>>、&、|、^、&&、||、==、!=、<、>、<= 和 >= 二元运算符（前提是每个操作数都为上面列出的类型）。

· ?: 条件运算符。

只要表达式属于上面列出的类型之一且只包含上面列出的构造，就在编译时计算该表达式。即使该表达式是另一个包含有非常数构造的较大表达式的子表达式，亦是如此。

常数表达式的编译时计算使用与非常数表达式的运行时计算相同的规则，区别仅在于：当出现错误时，运行时计算引发异常，而编译时计算导致发生编译时错误。

除非常数表达式被显式放置在 unchecked 上下文中，否则在表达式的编译时计算期间，整型算术运算和转换中发生的溢出总是导致编译时错误（第 7.5.12 节）。

常数表达式出现在以下列出的上下文中。在这些上下文中，如果无法在编译时充分计算表达式，则发生编译时错误。

· 常数声明（第 10.3 节）。

· 枚举成员声明（第 14.3 节）。

· switch 语句的 case 标签（第 8.7.2 节）。

· goto case 语句（第 8.9.3 节）。

· 包含初始值设定项的数组创建表达式（第 7.5.10.2 节）中的维度长度。

· 属性（第 17 节）。

只要常数表达式的值在目标类型的范围内，隐式常数表达式转换（第 6.1.6 节）就允许将 int 类型的常数表达式转换为 sbyte、byte、short、ushort、uint 或 ulong。


7.16布尔表达式.1333627

7.16 布尔表达式

“布尔表达式”是一种产生 bool 类型结果的表达式。

boolean-expression:（布尔表达式：）

expression（表达式）

if 语句（第 8.7.1 节）、while 语句（第 8.8.1 节）、do 语句（第 8.8.2 节）或 for 语句（第 8.8.3 节）的控制条件表达式都是“布尔表达式”。?: 运算符（第 7.12 节）的控制条件表达式遵守与“布尔表达式”相同的规则，但由于运算符优先级的缘故，被归为“条件或表达式”。

要求“布尔表达式”的类型或者可隐式地转换为 bool 的类型，或者实现了 operator true 的类型。如果两个要求都不满足，则发生编译时错误。

当布尔表达式的类型不能隐式转换为 bool 但它的确实现了 operator true 时，则在完成表达式计算后，会调用该类型提供的 operator true 以产生 bool 值。

第 11.4.2 节中的 DBBool 结构类型提供了一个实现了 operator true 和 operator false 的类型的示例。


8.语句.1336079

8. 语句

C# 提供了各种不同的语句。使用 C 和 C++ 编过程序的开发人员对这些语句中的大多数将会非常熟悉。

statement:（语句：）

labeled-statement（标记语句）
declaration-statement（声明语句）
embedded-statement（嵌入语句）

embedded-statement:（嵌入语句：）

block（块）
empty-statement（空语句）
expression-statement（表达式语句）
selection-statement（选择语句）
iteration-statement（迭代语句）
jump-statement（跳转语句）
try-statement（try 语句）
checked-statement（checked 语句）
unchecked-statement（unchecked 语句）
lock-statement（lock 语句）
using-statement（using 语句）

非结束性“嵌入语句”用于在其他语句内出现的语句。在使用一般“语句”的地方使用“嵌入语句”，排除在这些上下文中使用声明语句和标记语句的可能。在以下示例中：

void F(bool b) {

if (b)

int i = 44;

}

将导致编译时错误，原因是 if 语句的 if 分支要求“嵌入语句”而不是“语句”。上述代码的效果是：声明了变量 i，却永远无法使用它。但是请注意，如果是将 i 的声明放置在一个块中，该示例就是有效的。


8.1结束点和可到达性.1339888

8.1 结束点和可到达性

每个语句都有一个结束点。直观地讲，语句的结束点是紧跟在语句后面的那个位置。复合语句（包含嵌入语句的语句）的执行规则规定了当控制到达一个嵌入语句的结束点时所采取的操作。例如，当控制到达块中某个语句的结束点时，控制就转到该块中的下一个语句。

如果执行流程可能到达某个语句，则称该语句是可到达的。相反，如果某个语句不可能被执行，则称该语句是不可到达的。

在下面的示例中

void F() {

Console.WriteLine("reachable");

goto Label;

Console.WriteLine("unreachable");

Label:

Console.WriteLine("reachable");

}

第二个 Console.WriteLine 调用是不可到达的，这是因为不可能执行该语句。

如果编译器确定某个语句是不可到达的，将会报出警告。准确地说，语句不可到达不算是错误。

为了确定某个特定的语句或结束点是否可到达，编译器根据为各语句定义的可到达性规则进行控制流分析。控制流分析会考虑那些能控制语句行为的常数表达式（第 7.15 节）的值，但不考虑非常数表达式的可能值。换句话说，出于控制流分析的目的，给定类型的非常数表达式被认为具有该类型的任何可能值。

在下面的示例中

void F() {

const int i = 1;

if (i == 2) Console.WriteLine("unreachable");

}

if 语句的布尔表达式是常数表达式，原因是 == 运算符的两个操作数都是常数。由于该常数表达式在编译时进行计算并产生值 false，所以 Console.WriteLine 调用被认为是不可到达的。但是，如果 i 更改为局部变量

void F() {

int i = 1;

if (i == 2) Console.WriteLine("reachable");

}

则 Console.WriteLine 调用被认为是可到达的，即使它实际上永远不会被执行。

函数成员的块始终被认为是可到达的。通过依次计算块中各语句的可到达性规则，可以确定任何给定语句的可到达性。

在下面的示例中

void F(int x) {

Console.WriteLine("start");

if (x < 0) Console.WriteLine("negative");

}

第二个 Console.WriteLine 的可到达性按下面这样确定：

· 第一个 Console.WriteLine 表达式语句是可到达的，原因是 F 方法的块是可到达的。

· 第一个 Console.WriteLine 表达式语句的结束点是可到达的，原因是该语句是可到达的。

· if 语句是可到达的，原因是第一个 Console.WriteLine 表达式语句的结束点是可到达的。

· 第二个 Console.WriteLine 表达式语句是可到达的，原因是 if 语句的布尔表达式不是常数值 false。

在下列两种情况下，如果某个语句的结束点是可以到达的，则会出现编译时错误：

· 由于 switch 语句不允许一个 switch 节“贯穿”到下一个 switch 节，因此如果一个 switch 节的语句列表的结束点是可到达的，则会出现编译时错误。如果发生此错误，通常，它表明该处遗漏了一个 break 语句。

· 如果一个计算某个值的函数成员的块的结束点是可到达的，则会出现编译时错误。如果发生此错误，通常，它表明该处遗漏了一个 return 语句。


8.2块.1346396

8.2 块

块用于在只能使用单个语句的上下文中编写多个语句。

block:（块：）

{ statement-list opt }（{ 语句列表可选 }）

块由一个扩在大括号内的可选语句列表（第 8.2.1 节）组成。如果没有此语句列表，则称块是空的。

块可以包含声明语句（第 8.5 节）。在一个块中声明的局部变量或常数的范围就是该块本身。

在一个块内，在表达式上下文中使用的名称的意义必须始终相同（第 7.5.2.1 节）。

块按下述规则执行：

· 如果块是空的，控制转到块的结束点。

· 如果块不是空的，控制转到语句列表。当（如果）控制到达语句列表的结束点时，控制转到块的结束点。

如果块本身是可到达的，则块的语句列表是可到达的。

如果块是空的或者如果语句列表的结束点是可到达的，则块的结束点是可到达的。


8.2.1语句列表.1348582

8.2.1 语句列表

语句列表由一个或多个按顺序编写的语句组成。语句列表出现在块（第 8.2 节）和 switch 块（第 8.7.2 节）中。

statement-list:（语句列表：）

statement（语句）
statement-list statement（语句列表 语句）

执行一个语句列表就是将控制转到该列表中的第一个语句。当（如果）控制到达某条语句的结束点时，控制将转到下一个语句。当（如果）控制到达最后一个语句的结束点时，控制将转到语句列表的结束点。

如果下列条件中至少一个为真，则语句列表中的一个语句是可到达的：

· 该语句是第一个语句且语句列表本身是可到达的。

· 前一个语句的结束点是可到达的。

· 该语句本身是一个标记语句，并且该标签已被一个可到达的 goto 语句引用。

如果列表中最后一个语句的结束点是可到达的，则语句列表的结束点是可到达的。


8.3空语句.1350747

8.3 空语句

空语句什么都不做。

empty-statement:（空语句：）

;

当在要求有语句的上下文中不执行任何操作时，使用空语句。

执行一个空语句就是将控制转到该语句的结束点。这样，如果空语句是可到达的，则空语句的结束点也是可到达的。

当编写一个语句体为空的 while 语句时，可以使用空语句：

bool ProcessMessage() {...}

void ProcessMessages() {

while (ProcessMessage())

;

}

此外，空语句还可以用于在块的结束符“}”前声明标签：

void F() {

...

if (done) goto exit;

...

exit: ;

}


8.4标记语句.1352815

8.4 标记语句

标记语句可以给语句加上一个标签作为前缀。标记语句可以出现在块中，但是不允许它们作为嵌入语句。

labeled-statement:（标记语句：）

identifier : statement（标识符 : 语句）

标记语句声明了一个标签，它由一个标识符来命名。标签的范围为在其中声明了该标签的整个块，包括任何嵌套块。两个同名的标签若具有重叠的范围，则会产生一个编译时错误。

一个标签可以在该标签的范围内被 goto 语句（第 8.9.3 节）引用。这意味着 goto 语句可以在它所在的块内转移控制，也可以将控制转到该块外部，但是永远不能将控制转入该块所含的嵌套块的内部。

标签具有自己的声明空间，并不影响其他标识符。在以下示例中：

int F(int x) {

if (x >= 0) goto x;

x = -x;

x: return x;

}

是有效的，尽管它将 x 同时用作参数和标签的名称。

执行一个标记语句就是执行该标签后的那个语句。

除由正常控制流程提供的可到达性外，如果一个标签由一个可到达的 goto 语句引用，则该标记语句是可到达的。（例外：如果 goto 语句在一个包含了 finally 块的 try 中，标记语句本身在 try 之外，而且 finally 块的结束点不可到达，则从该 goto 语句不可到达上述的标记语句。）


8.5声明语句.1355494

8.5 声明语句

声明语句声明局部变量或常数。声明语句可以出现在块中，但不允许它们作为嵌入语句使用。

declaration-statement:（声明语句：）

local-variable-declaration ;（局部变量声明 ;）
local-constant-declaration ;（局部常数声明 ;）


8.5.1局部变量声明.1356730

8.5.1 局部变量声明

局部变量声明声明一个或多个局部变量。

local-variable-declaration:（局部变量声明：）

type local-variable-declarators（类型 局部变量声明符）

local-variable-declarators:（局部变量声明符：）

local-variable-declarator（局部变量声明符）
local-variable-declarators , local-variable-declarator（局部变量声明符 , 局部变量声明符）

local-variable-declarator:（局部变量声明符：）

identifier（标识符）
identifier = local-variable-initializer（标识符 = 局部变量初始值设定项）

local-variable-initializer:（局部变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设定项）

局部变量声明中的类型指定由该声明引入的变量的类型。此类型后跟一个局部变量声明符列表，其中每个声明符引入一个新变量。局部变量声明符由命名变量的标识符组成，根据需要此标识符后可跟一个“=”标记和一个赋予变量初始值的局部变量初始值设定项。

可以在表达式中通过简单名称（第 7.5.2 节）来获取局部变量的值，还可以通过赋值（第 7.13 节）来修改局部变量的值。在使用局部变量的每个地方必须先明确对其赋值（第 5.3 节），然后才可使用它的值。

在局部变量声明中声明的局部变量范围是该声明所在的块。在一个局部变量的局部变量声明符之前的文本位置中引用该局部变量是错误的。在一个局部变量的范围内声明其他具有相同名称的局部变量或常数是编译时错误。

声明了多个变量的局部变量声明等效于多个同一类型的单个变量的声明。另外，局部变量声明中的变量初始值设定项完全对应于紧跟该声明后插入的赋值语句。

示例

void F() {

int x = 1, y, z = x * 2;

}

完全对应于

void F() {

int x; x = 1;

int y;

int z; z = x * 2;

}


8.5.2局部常数声明.1361450

8.5.2 局部常数声明

局部常数声明用于声明一个或多个局部常数。

local-constant-declaration:（局部常数声明：）

const type constant-declarators（const 类型 常数声明符）

constant-declarators:（常数声明符：）

constant-declarator（常数声明符）
constant-declarators , constant-declarator（常数声明符 , 常数声明符）

constant-declarator:（常数声明符：）

identifier = constant-expression（标识符 = 常数表达式）

局部常数声明的类型指定由该声明引入的常数的类型。此类型后跟一个常数声明符列表，其中每个常数声明符引入一个新常数。常数声明符包含一个命名常数的标识符，后跟一个“=”标记，然后是一个对该常数赋值的常数表达式（第 7.15 节）。

局部常数声明的类型和常数表达式必须遵循与常数成员声明（第 10.3 节）一样的规则。

可以通过在表达式中使用一个简单名称（第 7.5.2 节）来获取局部常数的值。

局部常数的范围是在其中声明了该常数的块。在某常数声明符之前的文本位置中引用该局部常数是错误的。在局部常数的范围内声明其他具有相同名称的局部变量或常数是编译时错误。

声明多个常数的局部常数声明等效于多个同一类型的单个常数的声明。


8.6表达式语句.1364885

8.6 表达式语句

表达式语句用于计算所给定的表达式。由此表达式计算出来的值（如果有的话）被丢弃。

expression-statement:（表达式语句：）

statement-expression ;（语句表达式 ;）

statement-expression:（语句表达式：）

invocation-expression（调用表达式）
object-creation-expression（对象创建表达式）
assignment（赋值）
post-increment-expression（后递增表达式）
post-decrement-expression（后递减表达式）
pre-increment-expression（前递增表达式）
pre-decrement-expression（前递减表达式）

不是所有的表达式都允许作为语句使用。具体说来，不允许像 x + y 和 x == 1 这样只计算一个值（此值将被放弃）的表达式作为语句使用。

执行一个表达式语句就是对它所包含的表达式进行计算，然后将控制转到该表达式语句的结束点。如果一个表达式语句是可到达的，则其结束点也是可到达的。


8.7选择语句.1367434

8.7 选择语句

选择语句会根据表达式的值从若干个给定的语句中选择一个来执行。

selection-statement:（选择语句：）

if-statement（if 语句）
switch-statement（switch 语句）


8.7.1if语句.1368246

8.7.1 if 语句

if 语句根据布尔表达式的值选择要执行的语句。

if-statement:（if 语句：）

if ( boolean-expression ) embedded-statement（if ( 布尔表达式 ) 嵌入语句）
if ( boolean-expression ) embedded-statement else embedded-statement（if ( 布尔表达式 ) 嵌入语句 else 嵌入语句）

boolean-expression:（布尔表达式：）

expression（表达式）

else 部分与语法允许的、词法上最相近的上一个 if 语句相关联。因而，下列形式的 if 语句

if (x) if (y) F(); else G();

等效于

if (x) {

if (y) {

F();

}

else {

G();

}

}

if 语句按如下规则执行：

· 计算布尔表达式（第 7.16 节）。

· 如果布尔表达式产生 true，则控制转到第一个嵌入语句。当（如果）控制到达那条语句的结束点时，控制将转到 if 语句的结束点。

· 如果布尔表达式产生 false 且如果存在 else 部分，则控制转到第二个嵌入语句。当（如果）控制到达那条语句的结束点时，控制将转到 if 语句的结束点。

· 如果布尔表达式产生 false 且如果不存在 else 部分，则控制转到 if 语句的结束点。

如果 if 语句是可到达的且布尔表达式不具有常数值 false，则 if 语句的第一个嵌入语句是可到达的。

如果 if 语句是可到达的且布尔表达式不具有常数值 true，则 if 语句的第二个嵌入语句（如果存在的话）是可到达的。

如果 if 语句的至少一个嵌入语句的结束点是可到达的，则 if 语句的结束点是可到达的。此外，对于不具有 else 部分的 if 语句，如果 if 语句是可到达的且布尔表达式不具有常数值 true，则该 if 语句的结束点是可到达的。


8.7.2switch语句.1374707

8.7.2 switch 语句

switch 语句选择一个要执行的语句列表，此列表具有一个相关联的 switch 标签，它对应于 switch 表达式的值。

switch-statement:（switch 语句：）

switch ( expression ) switch-block（switch ( 表达式 ) switch 块）

switch-block:（switch 块：）

{ switch-sections opt }（{ switch 节可选 }）

switch-sections:（switch 节：）

switch-section（switch 节）
switch-sections switch-section（switch 节 switch 节）

switch-section:（switch 节：）

switch-labels statement-list（switch 标签 语句列表）

switch-labels:（switch 标签：）

switch-label（switch 标签）
switch-labels switch-label（switch 标签 switch 标签）

switch-label:（switch 标签：）

case constant-expression :（case 常数表达式 :）
default :（default :）

switch 语句包含关键字 switch，后跟带括号的表达式（称为 switch 表达式），然后是一个 switch 块。switch 块包含零个或多个括在大括号内的 switch 节。每个 switch 节包含一个或多个 switch 标签，后跟一个语句列表（第 8.2.1 节）。

switch 语句的主导类型由 switch 表达式建立。如果 switch 表达式的类型为 sbyte、byte、short、ushort、int、uint、long、ulong、char、string 或枚举类型，那么这就是 switch 语句的主导类型。否则，必须有且只有一个用户定义的从 switch 表达式的类型到下列某个可能的主导类型的隐式转换（第 6.4 节）：sbyte、byte、short、ushort、int、uint、long、ulong、char、string。如果不存在这样的隐式转换，或者存在一个以上这样的隐式转换，将发生编译时错误。

每个 case 标签的常数表达式的值必须属于这种类型：它可以隐式转换（第 6.1 节）为 switch 语句的主导类型。如果同一 switch 语句中的两个或更多个 case 标签指定同一个常数值，则会导致编译时错误。

一个 switch 语句中最多只能有一个 default 标签。

switch 语句按如下规则执行：

· 计算 switch 表达式并将其转换为主导类型。

· 如果在该 switch 语句的 case 标签中，有一个指定的常数恰好等于 switch 表达式的值，控制将转到匹配的 case 标签后的语句列表。

· 如果在该 switch 语句的 case 标签中，指定的常数都不等于 switch 表达式的值，且如果存在一个 default 标签，则控制将转到 default 标签后的语句列表。

· 如果在该 switch 语句的 case 标签中，指定的常数都不等于 switch 表达式的值，且如果不存在 default 标签，则控制将转到 switch 语句的结束点。

如果 switch 节的语句列表的结束点是可到达的，将发生编译时错误。这称为“无贯穿”规则。在以下示例中：

switch (i) {

case 0:

CaseZero();

break;

case 1:

CaseOne();

break;

default:

CaseOthers();

break;

}

是有效的，这是因为没有一个 switch 节的结束点是可到达的。与 C 和 C++ 不同，执行一个 switch 节的过程不能“贯穿”到下一个 switch 节，示例

switch (i) {

case 0:

CaseZero();

case 1:

CaseZeroOrOne();

default:

CaseAny();

}

导致编译时错误。如果要在执行一个 switch 节后继续执行另一个 switch 节，则必须使用显式的 goto case 或 goto default 语句：

switch (i) {

case 0:

CaseZero();

goto case 1;

case 1:

CaseZeroOrOne();

goto default;

default:

CaseAny();

break;

}

在一个 switch 节中允许有多个标签。示例

switch (i) {

case 0:

CaseZero();

break;

case 1:

CaseOne();

break;

case 2:

default:

CaseTwo();

break;

}

是有效的。此示例不违反“非贯穿”规则，这是因为标签 case 2: 和 default: 属于同一个 switch 节。

“非贯穿”规则防止了在 C 和 C++ 中由不经意地漏掉了 break 语句而引起的一类常见错误。另外，由于这个规则，switch 语句的各个 switch 节可以任意重新排列而不会影响语句的行为。例如，上面 switch 语句中的各节的顺序可以在不影响语句行为的情况下反转排列：

switch (i) {

default:

CaseAny();

break;

case 1:

CaseZeroOrOne();

goto default;

case 0:

CaseZero();

goto case 1;

}

switch 节的语句列表通常以 break、goto case 或 goto default 语句结束，但是也可以使用任何其他结构，只要它能保证对应的语句列表的结束点是不可到达的。例如，由布尔表达式 true 控制的 while 语句是永远无法到达其结束点的。同样，throw 或 return 语句始终将控制转到其他地方而从不到达它的结束点。因此，下列示例是有效的：

switch (i) {

case 0:

while (true) F();

case 1:

throw new ArgumentException();

case 2:

return;

}

switch 语句的主导类型可以是 string 类型。例如：

void DoCommand(string command) {

switch (command.ToLower()) {

case "run":

DoRun();

break;

case "save":

DoSave();

break;

case "quit":

DoQuit();

break;

default:

InvalidCommand(command);

break;

}

}

与字符串相等运算符（第 7.9.7 节）一样，switch 语句区分大小写，因而只有在 switch 表达式字符串与 case 标签常数完全匹配时才会执行对应的 switch 节。

当 switch 语句的主导类型为 string 时，允许值 null 作为 case 标签常数。

switch 块的语句列表可以包含声明语句（第 8.5 节）。在 switch 块中声明的局部变量或常数的范围是该 switch 块。

在 switch 块内，表达式上下文中使用的名称的意义必须始终是相同的（第 7.5.2.1 节）。

如果 switch 语句是可到达的且下列条件至少有一个为真，则给定的 switch 节的语句列表是可到达的：

· switch 表达式是一个非常数值。

· switch 表达式是一个与该 switch 节中的某个 case 标签匹配的常数值。

· switch 表达式是一个不与任何 case 标签匹配的常数值，且该 switch 节包含 default 标签。

· 该 switch 节的某个 switch 标签由一个可到达的 goto case 或 goto default 语句引用。

如果下列条件中至少有一个为真，则 switch 语句的结束点是可到达的：

· switch 语句包含一个退出 switch 语句的可到达的 break 语句。

· switch 语句是可到达的，switch 表达式是非常数值，并且不存在 default 标签。

· switch 语句是可到达的，switch 表达式是不与任何 case 标签匹配的常数值，并且不存在任何 default 标签。


8.8迭代语句.1397358

8.8 迭代语句

迭代语句重复执行嵌入语句。

iteration-statement:（迭代语句：）

while-statement（while 语句）
do-statement（do 语句）
for-statement（for 语句）
foreach-statement（foreach 语句）


8.8.1while语句.1398454

8.8.1 while 语句

while 语句按不同条件执行一个嵌入语句零次或多次。

while-statement:（while 语句：）

while ( boolean-expression ) embedded-statement（while ( 布尔表达式 ) 嵌入语句）

while 语句按如下规则执行：

· 计算布尔表达式（第 7.16 节）。

· 如果布尔表达式产生 true，控制将转到嵌入语句。当（如果）控制到达嵌入语句的结束点（可能是通过执行一个 continue 语句）时，控制将转到 while 语句的开头。

· 如果布尔表达式产生 false，控制将转到 while 语句的结束点。

在 while 语句的嵌入语句内，break 语句（第 8.9.1 节）可用于将控制转到 while 语句的结束点（从而结束嵌入语句的迭代），而 continue 语句（第 8.9.2 节）可用于将控制转到嵌入语句的结束点。

如果 while 语句是可到达的且布尔表达式不具有常数值 false，则该 while 语句的嵌入语句是可到达的。

如果下列条件中至少有一个为真，则 while 语句的结束点是可到达的：

· while 语句包含一个可到达的 break 语句（它用于退出 while 语句）。

· while 语句是可到达的且布尔表达式不具有常数值 true。


8.8.2do语句.1402537

8.8.2 do 语句

do 语句按不同条件执行一个嵌入语句一次或多次。

do-statement:（do 语句：）

do embedded-statement while ( boolean-expression ) ;（do 嵌入语句 while ( 布尔表达式 ) ;）

do 语句按如下规则执行：

· 控制转到嵌入语句。

· 当（如果）控制到达嵌入语句的结束点（可能是通过执行一个 continue 语句）时，计算布尔表达式（第 7.16 节）。如果布尔表达式产生 true，控制将转到 do 语句的开头。否则，控制转到 do 语句的结束点。

在 do 语句的嵌入语句内，break 语句（第 8.9.1 节）可用于将控制转到 do 语句的结束点（从而结束嵌入语句的迭代），而 continue 语句（第 8.9.2 节）可用于将控制转到嵌入语句的结束点（从而执行 do 语句的另一次迭代）。

如果 do 语句是可到达的，则 do 语句的嵌入语句是可到达的。

如果下列条件中至少有一个为真，则 do 语句的结束点是可到达的：

· do 语句包含一个可到达的 break 语句（它用于退出 do 语句）。

· 嵌入语句的结束点是可到达的且布尔表达式不具有常数值 true。


8.8.3for语句.1406752

8.8.3 for 语句

for 语句计算一个初始化表达式序列，然后，当某个条件为真时，重复执行相关的嵌套语句并计算一个迭代表达式序列。

for-statement:（for 语句：）

for ( for-initializer opt ; for-condition opt ; for-iterator opt ) embedded-statement（for ( for 初始值设定项可选 ; for 条件可选 ; for 迭代程序可选 ) 嵌入语句）

for-initializer:（for 初始值设定项：）

local-variable-declaration（局部变量声明）
statement-expression-list（语句表达式列表）

for-condition:（for 条件：）

boolean-expression（布尔表达式）

for-iterator:（for 迭代程序：）

statement-expression-list（语句表达式列表）

statement-expression-list:（语句表达式列表：）

statement-expression（语句表达式）
statement-expression-list , statement-expression（语句表达式列表 , 语句表达式）

for 初始值设定项（如果存在的话）或由一个局部变量声明（第 8.5.1 节）；或由一个用逗号分隔的语句表达式（第 8.6 节）列表组成。用 for 初始值设置项声明的局部变量的范围从变量的局部变量声明符开始，一直延伸到嵌入语句的结尾。该范围包括 for 条件和 for 迭代程序。

for 条件（如果存在的话）必须是一个布尔表达式（第 7.16 节）。

for 迭代程序（如果存在的话）包含一个用逗号分隔的语句表达式（第 8.6 节）列表。

for 语句按如下规则执行：

· 如果存在 for 初始值设定项，则按变量初始值设定项或语句表达式的编写顺序执行它们。此步骤只执行一次。

· 如果存在 for 条件，则计算它。

· 如果不存在 for 条件或如果计算产生 true，控制将转到嵌入语句。当（如果）控制到达嵌入语句的结束点（可能是通过执行一个 continue 语句）时，则按顺序计算 for 迭代程序的表达式（如果有的话），然后从上述步骤中的计算 for 条件开始，执行另一次迭代。

· 如果存在 for 条件，且计算产生 false，控制将转到 for 语句的结束点。

在 for 语句的嵌入语句内，break 语句（第 8.9.1 节）可用于将控制转到 for 语句的结束点，从而结束嵌入语句的迭代；而 continue 语句（第 8.9.2 节）可用于将控制转到嵌入语句的结束点，从而执行 for 迭代程序并从 for 条件开始执行 for 语句的另一次迭代。

如果下列条件之一为真，则 for 语句的嵌入语句是可到达的：

· for 语句是可到达的且不存在 for 条件。

· for 语句是可到达的，并且存在一个 for 条件，它不具有常数值 false。

如果下列条件中至少有一个为真，则 for 语句的结束点是可到达的：

· for 语句包含一个可到达的 break 语句（它用于退出 for 语句）。

· for 语句是可到达的，并且存在一个 for 条件，它不具有常数值 true。


8.8.4foreach语句.1415849

8.8.4 foreach 语句

foreach 语句用于枚举一个集合的元素，并对该集合中的每个元素执行一次相关的嵌入语句。

foreach-statement:（foreach 语句：）

foreach ( type identifier in expression ) embedded-statement（foreach ( 类型 标识符 in 表达式 ) 嵌入语句）

foreach 语句的类型和标识符声明该语句的迭代变量。迭代变量相当于一个其范围覆盖整个嵌入语句的只读局部变量。在 foreach 语句执行期间，迭代变量表示当前正在为其执行迭代的集合元素。如果嵌入语句试图修改迭代变量（通过赋值或 ++ 和 -- 运算符）或将迭代变量作为 ref 或 out 参数传递，则将发生编译时错误。

foreach 语句的表达式的类型必须是集合类型（下面有它的定义），且必须有一个从该集合的元素类型到迭代变量的类型的显式转换（第 6.2 节）。如果该表达式具有 null 值，则将引发 System.NullReferenceException。

如果类型 C 实现了 System.Collections.IEnumerable 接口，或者能够满足下列（有关实现集合模式的）条件，就称它是集合类型：

· C 包含一个 public 实例方法，它带有签名 GetEnumerator()，且返回值属于结构类型、类类型或接口类型（后文中用 E 表示该返回值的类型）。

· E 包含一个 public 实例方法，此方法具有签名 MoveNext() 和返回类型 bool。

· E 包含一个名为 Current 的 public 实例属性，此属性允许读取当前值。此属性的类型称为该集合类型的元素类型。

一个实现 IEnumerable 的类型也是集合类型，即使它不满足上述条件。（这是可能的，如果该类型通过“显式接口成员实现”的方式实现某些 IEnumerable 成员，关于该方式详见第 13.4.1 节中的说明。）

System.Array 类型（第 12.1.1 节）是集合类型，而由于所有的数组类型都从 System.Array 派生，foreach 语句中允许使用任何数组类型表达式。foreach 按如下顺序遍历数组的元素：对于一维数组，按递增的索引顺序遍历元素，从索引 0 开始，到索引 Length – 1 结束。对于多维数组，按这样的方式遍历元素：首先增加最右边维度的索引，然后是它的左边紧邻的维度，依此类推直到最左边的那个维度。

下列形式的 foreach 语句：

foreach (ElementType element in collection) statement

对应于两个可能的扩展中的一个：

· 如果 collection 表达式的类型实现了集合模式（如上面定义的那样），则 foreach 语句的扩展是：

E enumerator = (collection).GetEnumerator();

try {

while (enumerator.MoveNext()) {

ElementType element = (ElementType)enumerator.Current;

statement;

}

}

finally {

IDisposable disposable = enumerator as System.IDisposable;

if (disposable != null) disposable.Dispose();

}

通常可以很容易对上述代码进行有效的优化。如果类型 E 实现了 System.IDisposable，则表达式 (enumerator as System.IDisposable) 将始终为非 null，因而，该实现可以安全地用一个简单转换替代可能更昂贵的类型测试。相反，如果类型 E 是 sealed 而且没有实现 System.IDisposable，则表达式 (enumerator as System.IDisposable) 的计算结果始终为 null。这种情况下，在上述代码中可以安全地删除整个 finally 子句。

· 否则，collection 表达式的类型就实现了 System.IEnumerable，这样，foreach 语句的扩展就成为：

IEnumerator enumerator =

((System.Collections.IEnumerable)(collection)).GetEnumerator();

try {

while (enumerator.MoveNext()) {

ElementType element = (ElementType)enumerator.Current;

statement;

}

}

finally {

IDisposable disposable = enumerator as System.IDisposable;

if (disposable != null) disposable.Dispose();

}

在上面两个扩展的任意一个中，enumerator 变量是一个临时变量，它在嵌入 statement 中既是不可访问的，也是不可见的，元素变量在嵌入 statement 中是只读的。

下列示例按照元素的顺序打印出一个二维数组中的各个元素的值：

using System;

class Test

{

static void Main() {

double[,] values = {

{1.2, 2.3, 3.4, 4.5},

{5.6, 6.7, 7.8, 8.9}

};

foreach (double elementValue in values)

Console.Write("{0} ", elementValue);

Console.WriteLine();

}

}

所生成的输出如下：

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9


8.9跳转语句.1427340

8.9 跳转语句

跳转语句用于无条件地转移控制。

jump-statement:（跳转语句：）

break-statement（break 语句）
continue-statement（continue 语句）
goto-statement（goto 语句）
return-statement（return 语句）
throw-statement（throw 语句）

跳转语句会将控制转到某个位置，这个位置就称为跳转语句的目标。

当一个跳转语句出现在某个块内，而该跳转语句的目标在该块之外时，就称该跳转语句退出该块。虽然跳转语句可以将控制转到一个块外，但它永远不能将控制转到一个块的内部。

由于存在 try 语句的干扰，跳转语句的执行有时会变得复杂起来。如果没有这样的 try 语句，则跳转语句无条件地将控制从跳转语句转到它的目标。当跳转涉及到 try 语句时，执行就变得复杂一些了。如果跳转语句欲退出的是一个或多个具有相关联的 finally 块的 try 块，则控制最初转到最里层的 try 语句的 finally 块。当（如果）控制到达该 finally 块的结束点时，控制就转到下一个封闭 try 语句的 finally 块。此过程不断重复，直到执行完所有涉及的 try 语句的 finally 块。

在下面的示例中

using System;

class Test

{

static void Main() {

while (true) {

try {

try {

Console.WriteLine("Before break");

break;

}

finally {

Console.WriteLine("Innermost finally block");

}

}

finally {

Console.WriteLine("Outermost finally block");

}

}

Console.WriteLine("After break");

}

}

在将控制转到跳转语句的目标之前，要先执行与两个 try 语句关联的 finally 块。

所生成的输出如下：

Before break

Innermost finally block

Outermost finally block

After break


8.9.1break语句.1432773

8.9.1 break 语句

break 语句退出直接封闭它的 switch、while、do、for 或 foreach 语句。

break-statement:（break 语句：）

break ;

break 语句的目标是直接封闭它的 switch、while、do、for 或 foreach 语句的结束点。如果 break 语句不是由 switch、while、do、for 或 foreach 语句所封闭，则发生编译时错误。

当多个 switch、while、do、for 或 foreach 语句彼此嵌套时，break 语句只应用于最里层的语句。若要穿越多个嵌套层直接转移控制，必须使用 goto 语句（第 8.9.3 节）。

break 语句不能退出 finally 块（第 8.10 节）。当 break 语句出现在 finally 块中时，该 break 语句的目标必须位于同一个 finally 块中，否则将发生编译时错误。

break 语句按如下规则执行：

· 如果 break 语句退出一个或多个具有关联 finally 块的 try 块，则控制最初会被转到最里层的 try 语句的 finally 块。当（如果）控制到达该 finally 块的结束点时，控制就转到下一个封闭 try 语句的 finally 块。此过程不断重复，直到执行完所有涉及的 try 语句的 finally 块。

· 控制转到 break 语句的目标。

由于 break 语句无条件地将控制转到别处，因此永远无法到达 break 语句的结束点。


8.9.2continue语句.1437548

8.9.2 continue 语句

continue 语句开始直接封闭它的 while、do、for 或 foreach 语句的一次新迭代。

continue-statement:（continue 语句：）

continue ;

continue 语句的目标是直接封闭它的 while、do、for 或 foreach 语句的嵌套语句的结束点。如果 continue 语句不是由 while、do、for 或 foreach 语句所封闭的，则发生编译时错误。

当多个 while、do、for 或 foreach 语句互相嵌套时，continue 语句只应用于最里层的那个语句。若要穿越多个嵌套层直接转移控制，必须使用 goto 语句（第 8.9.3 节）。

continue 语句不能退出 finally 块（第 8.10 节）。当 continue 语句出现在 finally 块中时，该 continue 语句的目标必须位于同一个 finally 块中，否则将发生编译时错误。

continue 语句按如下规则执行：

· 如果 continue 语句退出一个或多个具有关联 finally 块的 try 块，则控制最初转到最里层的 try 语句的 finally 块。当（如果）控制到达该 finally 块的结束点时，控制就转到下一个封闭 try 语句的 finally 块。此过程不断重复，直到执行完所有涉及的 try 语句的 finally 块。

· 控制转到 continue 语句的目标。

由于 continue 语句无条件地将控制转到别处，因此永远无法到达 continue 语句的结束点。


8.9.3goto语句.1442229

8.9.3 goto 语句

goto 语句将控制转到由标签标记的语句。

goto-statement:（goto 语句：）

goto identifier ;（goto 标识符 ;）
goto case constant-expression ;（goto case 常数表达式 ;）
goto default ;（goto default ;）

goto 标识符语句的目标是具有给定标签的标记语句。如果当前函数成员中不存在具有给定名称的标签，或者如果 goto 语句不在该标签的范围内，则发生编译时错误。此规则允许使用 goto 语句将控制转移“出”嵌套范围，但是不允许将控制转移“进”嵌套范围。在下面的示例中

using System;

class Test

{

static void Main(string[] args) {

string[,] table = {

{"Red", "Blue", "Green"},

{"Monday", "Wednesday", "Friday"}

};

foreach (string str in args) {

int row, colm;

for (row = 0; row <= 1; ++row)

for (colm = 0; colm <= 2; ++colm)

if (str == table[row,colm])

goto done;

Console.WriteLine("{0} not found", str);

continue;

done:

Console.WriteLine("Found {0} at [{1}][{2}]", str, row, colm);

}

}

}

goto 语句用于将控制转移出嵌套范围。

goto case 语句的目标是直接封闭着它的 switch 语句（第 8.7.2 节）中的某个语句列表，此列表包含一个具有给定常数值的 case 标签。如果 goto case 语句不是由 switch 语句封闭的，如果常数表达式不能隐式转换（第 6.1 节）为直接封闭着它的 switch 语句的主导类型，或者如果直接封闭着它的 switch 语句不包含具有给定常数值的 case 标签，则发生编译时错误。

goto default 语句的目标是直接封闭着它的那个 switch 语句（第 8.7.2 节）中的对应于 default 标签的语句列表。如果 goto default 语句不是由 switch 语句封闭的，或者如果直接封闭着它的 switch 语句不包含 default 标签，则发生编译时错误。

goto 语句不能退出 finally 块（第 8.10 节）。当 goto 语句出现在 finally 块中时，该 goto 语句的目标必须位于同一个 finally 块中，否则将发生编译时错误。

goto 语句按如下方式执行：

· 如果 goto 语句退出一个或多个具有关联的 finally 块的 try 块，则控制最初转到最里层的 try 语句的 finally 块。当（如果）控制到达该 finally 块的结束点时，控制就转到下一个封闭 try 语句的 finally 块。此过程不断重复，直到执行完所有涉及的 try 语句的 finally 块。

· 控制转到 goto 语句的目标。

由于 goto 语句无条件地将控制转到别处，因此永远无法到达 goto 语句的结束点。


8.9.4return语句.1451061

8.9.4 return 语句

return 语句将控制返回到出现 return 语句的函数成员的调用方。

return-statement:（return 语句：）

return expression opt ;（return 表达式可选 ;）

不带表达式的 return 语句只能用在不计算值的函数成员中，即只能用在返回类型为 void 的方法、属性或索引器的 set 访问器、事件的 add 和 remove 访问器、实例构造函数、静态构造函数或析构函数中。

带表达式的 return 语句只能用在计算值的函数成员中，即返回类型为非 void 的方法、属性或索引器的 get 访问器或用户定义的运算符。必须存在一个隐式转换（第 6.1 节），它能将该表达式的类型转换到包含它的函数成员的返回类型。

return 语句出现在 finally 块（第 8.10 节）中是编译时错误。

return 语句按下列规则执行：

· 如果 return 语句指定一个表达式，则计算该表达式，并将结果隐式转换为包含它的函数成员的返回类型。转换的结果成为返回到调用方的值。

· 如果 return 语句由一个或多个具有关联 finally 块的 try 块封闭，则控制最初转到最里层的 try 语句的 finally 块。当（如果）控制到达该 finally 块的结束点时，控制就转到下一个封闭 try 语句的 finally 块。此过程不断重复，直到执行完所有封闭 try 语句的 finally 块。

· 控制返回到包含它们的函数成员的调用方。

由于 return 语句无条件地将控制转到别处，因此永远无法到达 return 语句的结束点。


8.9.5throw语句.1455462

8.9.5 throw 语句

throw 语句引发一个异常。

throw-statement:（throw 语句：）

throw expression opt ;（throw 表达式可选 ;）

带表达式的 throw 语句引发一个异常，此异常的值就是通过计算该表达式而产生的值。计算该表达式所得的值必须属于类类型 System.Exception 或从 System.Exception 派生的类类型。如果表达式的计算产生 null，则引发 System.NullReferenceException。

不带表达式的 throw 语句只能用在 catch 块中，在这种情况下，该语句重新引发当前正由该 catch 块处理的那个异常。

由于 throw 语句无条件地将控制转到别处，因此永远无法到达 throw 语句的结束点。

引发一个异常时，控制转到封闭着它的 try 语句中能够处理该异常的的第一个 catch 子句。从引发一个异常开始直至将控制转到关于该异常的一个合适的异常处理程序止，这个过程称为异常传播。“传播一个异常”由重复地执行下列各步骤组成，直至找到一个与该异常匹配的 catch 子句。在此描述中，引发点最初是指引发该异常的位置。

· 在当前函数成员中，检查每个封闭着引发点的 try 语句。对于每个语句 S（按从最里层的 try 语句开始，逐次向外，直到最外层的 try 语句结束），计算下列步骤：

· 如果 S 的 try 块封闭着引发点，并且如果 S 具有一个或多个 catch 子句，则按其出现的顺序检查这些 catch 子句以找到合适的异常处理程序。检查过程中，第一个符合要求的 catch 子句（它所指定的异常类型与该异常的类型相同或是它的基类型）被认为是一个匹配项。常规 catch 子句（第 8.10 节）被认为是所有异常类型的匹配项。如果找到匹配的 catch 子句，则通过将控制转到该 catch 子句的块来完成异常传播。

· 否则（如果找不到匹配的 catch 子句），如果 S 的 try 块或 catch 块封闭着引发点并且如果 S 具有 finally 块，控制将转到 finally 块。如果在该 finally 块内引发另一个异常，则终止当前异常的处理。否则，当控制到达 finally 块的结束点时，将继续对当前异常的处理。

· 如果在当前函数成员调用中没有找到异常处理程序，则终止对该函数成员的调用。然后，为该函数成员的调用方重复执行上面的步骤，并使用对应于该调用函数成员的语句的引发点。

· 如果上述异常处理终止了当前线程中的所有函数成员调用，这表明此线程没有该异常的处理程序，那么线程本身将终止。此类终止会产生什么影响，应由实现来定义。


8.10try语句.1461301

8.10 try 语句

try 语句提供一种机制，用于捕捉在块的执行期间发生的各种异常。此外，try 语句还能让您指定一个代码块，并保证当控制离开 try 语句时，先执行该代码。

try-statement:（try 语句：）

try block catch-clauses（try 块 catch 子句）
try block finally-clause（try 块 finally 子句）
try block catch-clauses finally-clause（try 块 catch 子句 finally 子句）

catch-clauses:（catch 子句：）

specific-catch-clauses general-catch-clause opt（特定 catch 子句 常规 catch 子句可选）
specific-catch-clauses opt general-catch-clause（特定 catch 子句可选 常规 catch 子句）

specific-catch-clauses:（特定 catch 子句：）

specific-catch-clause（特定 catch 子句）
specific-catch-clauses specific-catch-clause（特定 catch 子句 特定 catch 子句）

specific-catch-clause:（特定 catch 子句：）

catch ( class-type identifier opt ) block（catch ( 类类型 标识符可选 ) 块）

general-catch-clause:（常规 catch 子句：）

catch block（catch 块）

finally-clause:（finally 子句：）

finally block（finally 块）

有三种可能的 try 语句形式：

· 一个 try 块后跟一个或多个 catch 块。

· 一个 try 块后跟一个 finally 块。

· 一个 try 块后跟一个或多个 catch 块，后面再跟一个 finally 块。

当 catch 子句指定类类型时，此类型必须为 System.Exception 或从 System.Exception 派生的类型。

当 catch 子句同时指定类类型和标识符时，相当于声明了一个具有给定名称和类型的异常变量。此异常变量相当于一个范围覆盖整个 catch 块的局部变量。在 catch 块的执行期间，此异常变量表示当前正在处理的异常。出于明确赋值检查的目的，此异常变量被认为在它的整个范围内是明确赋值的。

除非 catch 子句包含一个异常变量名，否则在该 catch 块中就不可能访问当前发生的异常对象。

既不指定异常类型也不指定异常变量名的 catch 子句称为常规 catch 子句。一个 try 语句只能有一个常规 catch 子句，而且它必须是最后一个 catch 子句（因此如果存在的话）。

有些编程语言可能支持一些异常，它们不能表示为从 System.Exception 派生的对象，尽管 C# 代码可能永远不会产生这类异常。可以使用常规 catch 子句来捕捉这类异常。因此，常规的 catch 子句在语义上不同于指定了 System.Exception 类型的那些子句，因为前者还可以捕获来自其他语言的异常。

为了找到当前发生了的异常的处理程序，catch 子句是按其词法顺序进行检查的。如果 catch 子句指定的类型与同一 try 块的某个较早的 catch 子句中所指定的类型相同，或者是从该类型派生的类型，则发生编译时错误。如果没有这个限制，就可能写出不可到达的 catch 子句。

在 catch 块内，不具有表达式的 throw 语句（第 8.9.5 节）可用于重新引发由该 catch 块捕捉到的异常。对异常变量的赋值不会改变上述被重新引发的异常。

在下面的示例中

using System;

class Test

{

static void F() {

try {

G();

}

catch (Exception e) {

Console.WriteLine("Exception in F: " + e.Message);

e = new Exception("F");

throw; // re-throw

}

}

static void G() {

throw new Exception("G");

}

static void Main() {

try {

F();

}

catch (Exception e) {

Console.WriteLine("Exception in Main: " + e.Message);

}

}

}

方法 F 捕捉到一个异常，向控制台写入一些诊断信息，更改异常变量，然后重新引发该异常。重新引发的异常是原来那个被捕获的异常，因此产生的输出为：

Exception in F: G

Exception in Main: G

如果修改第一个 catch 块中的代码，用引发异常 e 来代替重新引发当前的异常，产生的输出就会如下所示：

Exception in F: G

Exception in Main: F

break、continue 或 goto 语句将控制转移到 finally 块外部是编译时错误。当一个 break、continue 或 goto 语句出现在 finally 块中时，该语句的目标必须在同一 finally 块内，否则会发生编译时错误。

return 语句出现在 finally 块中是一个编译时错误。

try 语句按下列规则执行：

· 控制转到 try 块。

· 当（如果）控制到达 try 块的结束点时：

· 如果该 try 语句具有 finally 块，则执行 finally 块。

· 控制转到 try 语句的结束点。

· 如果在 try 块执行期间，有一个异常传播到该 try 语句：

· 按 catch 子句出现的顺序（如果有的话）逐个对其进行检查，以找到一个合适的异常处理程序。检查过程中，第一个符合要求的 catch 子句（它所指定的异常类型与该异常的类型相同或是它的基类型）被认为是一个匹配项。常规 catch 子句被认为是任何异常类型的匹配项。如果找到匹配的 catch 子句：

· 如果匹配的 catch 子句声明一个异常变量，则当前的异常对象被赋给该异常变量。

· 控制转到匹配的 catch 块。

· 当（如果）控制到达 catch 块的结束点时：

· 如果该 try 语句具有 finally 块，则执行 finally 块。

· 控制转到 try 语句的结束点。

· 如果在 catch 块执行期间有一个异常传播到 try 语句：

· 如果该 try 语句具有 finally 块，则执行 finally 块。

· 该异常就传播到更外面一层（封闭）的 try 语句。

· 如果该 try 语句没有 catch 子句或如果没有与异常匹配的 catch 子句：

· 如果该 try 语句具有 finally 块，则执行 finally 块。

· 该异常就传播到更外面一层（封闭）的 try 语句。

finally 块中的语句总是在控制离开 try 语句时先被执行。无论是什么原因引起控制转移（正常执行到达结束点，执行了 break、continue、goto 或 return 语句，或是执行一个导致离开 try 语句的异常传播），情况都是如此。

如果在执行 finally 块期间引发了一个异常，则此异常将被传播到下一个封闭的 try 语句。与此同时，原先那个正在传播过程中的异常（如果存在的话）就会被丢弃。关于传播异常的过程，在 throw 语句（第 8.9.5 节）的说明中有进一步讨论。

如果 try 语句是可到达的，则 try 语句的 try 块是可到达的。

如果 try 语句是可到达的，则 try 语句的 catch 块是可到达的。

如果 try 语句是可到达的，则 try 语句的 finally 块是可到达的。

如果下列两个条件都为真，则 try 语句的结束点是可到达的：

· try 块的结束点是可到达的或者至少一个 catch 块的结束点是可到达的。

· 如果存在一个 finally 块，此 finally 块的结束点是可到达的。


8.11checked和unchecked语句.1485124

8.11 checked 和 unchecked 语句

checked 和 unchecked 语句用于控制整型算术运算和转换的溢出检查上下文。

checked-statement:（checked 语句：）

checked block（checked 块）

unchecked-statement:（unchecked 语句：）

unchecked block（unchecked 块）

checked 语句导致它指定的块中的所有表达式都在一个选中的上下文中进行计算，而 unchecked 语句导致它们在一个未选中的上下文中进行计算。

checked 和 unchecked 语句完全等效于 checked 和 unchecked 运算符（第 7.5.12 节），不同的只是它们作用于块，而不是表达式。


8.12lock语句.1487370

8.12 lock 语句

lock 语句用于获取某个给定对象的互斥锁，执行一个语句，然后释放该锁。

lock-statement:（lock 语句：）

lock ( expression ) embedded-statement（lock ( 表达式 ) 嵌入语句）

lock 语句的表达式必须表示一个引用类型的值。永远不会为 lock 语句中的表达式执行隐式装箱转换（第 6.1.5 节），因此，如果该表达式表示的是一个值类型的值，则会导致一个编译时错误。

下列形式的 lock 语句

lock (x) ...

（其中 x 是一个引用类型的表达式）完全等效于

System.Threading.Monitor.Enter(x);

try {

...

}

finally {

System.Threading.Monitor.Exit(x);

}

不同的只是：实际执行中 x 只计算一次。

当一个互斥锁已被占用时，在同一线程中执行的代码仍可以获取和释放该锁。但是，在其他线程中执行的代码在该锁被释放前是无法获得它的。

一个类的 System.Type 对象可以方便地用来当作关于该类的静态方法的互斥锁。例如：

class Cache

{

public static void Add(object x) {

lock (typeof(Cache)) {

...

}

}

public static void Remove(object x) {

lock (typeof(Cache)) {

...

}

}

}


8.13using语句.1491952

8.13 using 语句

using 语句获取一个或多个资源，执行一个语句，然后处置该资源。

using-statement:（using 语句：）

using ( resource-acquisition ) embedded-statement（using ( 资源获取 ) 嵌入语句）

resource-acquisition:（资源获取：）

local-variable-declaration（局部变量声明）
expression（表达式）

一个资源是实现了 System.IDisposable 的类或结构，它只包含一个名为 Dispose 的不带参数的方法。正在使用资源的代码可以调用 Dispose 以表明不再需要该资源。如果不调用 Dispose，则最终将因为垃圾回收而对该资源进行自动处置。

如果资源获取的形式是局部变量声明，那么此局部变量声明的类型必须为 System.IDisposable 或是可以隐式转换为 System.IDisposable 的类型。如果资源获取的形式是表达式，那么此表达式必须是 System.IDisposable 类型或是可以隐式转换为 System.IDisposable 的类型。

在资源获取中声明的局部变量必须是只读的，且必须包含一个初始值设定项。如果嵌入语句试图修改这些局部变量（通过赋值或 ++ 和 -- 运算符）或将它们作为 ref 或 out 参数传递，则将发生编译时错误。

using 语句被翻译成三个部分：获取、使用和处置。资源的使用部分被隐式封闭在一个含有 finally 子句的 try 语句中。此 finally 子句用于处置资源。如果所获取资源是 null，则不会对 Dispose 进行调用，也不会引发任何异常。

下列形式的 using 语句

using (ResourceType resource = expression) statement

对应于下列两个可能的扩展中的一个。当 ResourceType 是值类型时，扩展为

{

ResourceType resource = expression;

try {

statement;

}

finally {

((IDisposable)resource).Dispose();

}

}

否则，当 ResourceType 是引用类型时，扩展为

{

ResourceType resource = expression;

try {

statement;

}

finally {

if (resource != null) ((IDisposable)resource).Dispose();

}

}

在上面任何一种扩展中，resource 变量在嵌入语句中都是只读的。

下列形式的 using 语句

using (expression) statement

同样具有上述两种可能的扩展，但在这种情况下 ResourceType 隐式地为 expression 的编译时类型，而 resource 变量在嵌入语句中既不可访问，也不可见。

如果资源获取采用局部变量声明的形式，则有可能获取给定类型的多个资源。下列形式的 using 语句

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement

完全等效于嵌套 using 语句的序列：

using (ResourceType r1 = e1)

using (ResourceType r2 = e2)

...

using (ResourceType rN = eN)

statement

下面的示例创建一个名为 log.txt 的文件并将两行文本写入该文件。然后该示例打开这个文件进行读取，并将它所包含的文本行复制到控制台。

using System;

using System.IO;

class Test

{

static void Main() {

using (TextWriter w = File.CreateText("log.txt")) {

w.WriteLine("This is line one");

w.WriteLine("This is line two");

}

using (TextReader r = File.OpenText("log.txt")) {

string s;

while ((s = r.ReadLine()) != null) {

Console.WriteLine(s);

}

}

}

}

由于 TextWriter 和 TextReader 类实现了 IDisposable 接口，因此该示例可以使用 using 语句以确保所涉及的文件在写入或读取操作后正确关闭。


9.命名空间.1501563

Powered By Gavin http://www.CSharpWeb.com

9. 命名空间

C# 程序是利用命名空间组织起来的。命名空间既用作程序的“内部”组织系统，也用作“外部”组织系统（一种向其他程序公开自己拥有的程序元素的方法）。

using 指令（第 9.3 节）是用来使命名空间用起来更方便。


9.1编译单元.1502181

9.1 编译单元

编译单元定义了源文件的总体结构。编译单元的组成方式如下：先是零个或多个 using 指令，后跟零个或多个全局属性，然后是零个或多个命名空间成员声明。

compilation-unit:（编译单元：）

using-directives opt global-attributes opt namespace-member-declarations opt（using 指令可选全局属性可选 命名空间成员声明可选）

一个 C# 程序由一个或多个编译单元组成，每个编译单元都用一个单独的源文件来保存。编译 C# 程序时，所有这些编译单元一起进行处理。因此，这些编译单元间可以互相依赖，甚至以循环方式互相依赖。

一个编译单元中的 using 指令的作用范围包括该编译单元内的所有全局属性和命名空间成员声明，但是不会影响其他编译单元。

编译单元的全局属性（第 17 节）用于指定目标程序集和模块的属性。程序集和模块充当类型的物理容器。程序集可以包含若干个在物理上分离的模块。

一个程序中各编译单元中的命名空间成员声明用于为一个称为“全局命名空间”的单个声明空间提供成员。例如：

文件 A.cs：

class A {}

文件 B.cs：

class B {}

这两个编译单元是为该全局命名空间提供成员的，在本例中它们分别声明了具有完全限定名 A 和 B 的两个类。由于这两个编译单元为同一声明空间提供成员，因此如果它们分别包含了一个同名成员的声明，将会是个错误。


9.2命名空间声明.1504788

9.2 命名空间声明

一个命名空间声明的组成方式如下：先是关键字 namespace，后跟一个命名空间名称和体，然后加一个分号（可选）。

namespace-declaration:（命名空间声明：）

namespace qualified-identifier namespace-body ;opt（命名空间 限定标识符 命名空间体 ;可选）

qualified-identifier:（限定标识符：）

identifier（标识符）
qualified-identifier . identifier（限定标识符 . 标识符）

namespace-body:（命名空间体：）

{ using-directives opt namespace-member-declarations opt }（{ using 指令可选 命名空间成员声明可选 }）

命名空间声明可以作为顶级声明出现在编译单元中，或是作为成员声明出现在另一个命名空间声明内。当命名空间声明作为顶级声明出现在编译单元中时，该命名空间成为全局命名空间的一个成员。当一个命名空间声明出现在另一个命名空间声明内时，该内部命名空间就成为包含着它的外部命名空间的一个成员。无论是何种情况，一个命名空间的名称在它所属的命名空间内必须是唯一的。

命名空间隐式地为 public，而且在命名空间的声明中不能包含任何访问修饰符。

在命名空间体内，可选用 using 指令来导入其他命名空间和类型的名称，这样，就可以直接地而不是通过限定名来引用它们。可选的命名空间成员声明用于为命名空间的声明空间提供成员。请注意，所有的 using 指令都必须出现在任何成员声明之前。

命名空间声明中的限定标识符可以是单个标识符或者是由“.”标记分隔的标识符序列。后一种形式允许一个程序直接定义一个嵌套命名空间，而不必按词法嵌套若干个命名空间声明。例如，

namespace N1.N2

{

class A {}

class B {}

}

在语义上等效于

namespace N1

{

namespace N2

{

class A {}

class B {}

}

}

命名空间是可扩充的，两个具有相同的完全限定名的命名空间声明是在为同一声明空间（第 3.3 节）提供成员。在下面的示例中

namespace N1.N2

{

class A {}

}

namespace N1.N2

{

class B {}

}

上面的两个命名空间声明为同一声明空间提供了成员，在本例中它们分别声明了具有完全限定名 N1.N2.A 和 N1.N2.B 的两个类。由于两个声明为同一声明空间提供成员，因此如果它们分别包含一个同名成员的声明，就将出现错误。


9.3using指令.1511094

9.3 using 指令

using 指令方便了对在其他命名空间中定义的命名空间和类型的使用。using 指令仅影响命名空间或类型名称（第 3.8 节）和简单名称（第 7.5.2 节）的名称解析过程，与声明不同，using 指令不会将新成员添加到它们与所在的编译单元或命名空间相对应的声明空间中。

using-directives:（using 指令：）

using-directive（using 指令）
using-directives using-directive（using 指令 using 指令）

using-directive:（using 指令：）

using-alias-directive（using 别名指令）
using-namespace-directive（using 命名空间指令）

using 别名指令（第 9.3.1 节）用于为一个命名空间或类型启用一个别名。

using 命名空间指令（第 9.3.2 节）用于导入一个命名空间的类型成员。

using 指令的范围扩展到直接包含它的编译单元或命名空间体内的所有命名空间成员声明。具体说来，using 指令的范围不包括与它对等的 using 指令。因此，对等 using 指令互不影响，而且按什么顺序编写它们也无关紧要。


9.3.1using别名指令.1513497

9.3.1 using 别名指令

using 别名指令用于为一个命名空间或类型指定一个别名（即标识符），该别名在直接包含此指令的编译单元或命名空间体内有效。

using-alias-directive:（using 别名指令：）

using identifier = namespace-or-type-name ;（using 标识符 = 命名空间或类型名称 ;）

在包含 using 别名指令的编译单元或命名空间体内的成员声明中，由 using 别名指令引入的标识符可用于引用给定的命名空间或类型。例如：

namespace N1.N2

{

class A {}

}

namespace N3

{

using A = N1.N2.A;

class B: A {}

}

上面的示例中，在 N3 命名空间中声明成员时，A 是 N1.N2.A 的别名，因此类 N3.B 从类 N1.N2.A 派生。通过为 N1.N2 创建别名 R 然后引用 R.A 可以得到同样的效果：

namespace N3

{

using R = N1.N2;

class B: R.A {}

}

using 别名指令中的标识符在直接包含该 using 别名指令的编译单元或命名空间的声明空间内必须是唯一的。例如：

namespace N3

{

class A {}

}

namespace N3

{

using A = N1.N2.A; // Error, A already exists

}

上例中，N3 已包含了成员 A，因此 using 别名指令使用 A 作标识符会导致一个编译时错误。同样，如果同一个编译单元或命名空间体中的两个或更多 using 别名指令用相同名称声明别名，也会导致一个编译时错误。

using 别名指令使别名可用在特定编译单元或命名空间体内，但是它不会将任何新成员添加到相应的声明空间。换句话说，using 别名指令是不可传递的，它指定的别名仅在它所在的编译单元或命名空间体内有效。在下面的示例中

namespace N3

{

using R = N1.N2;

}

namespace N3

{

class B: R.A {} // Error, R unknown

}

引入 R 的 using 别名指令的范围只延伸到包含它的命名空间体中的成员声明，因此 R 在第二个命名空间声明中是未知的。但是，如果将 using 别名指令放置在包含它的编译单元中，则该别名在两个命名空间声明中都可用：

using R = N1.N2;

namespace N3

{

class B: R.A {}

}

namespace N3

{

class C: R.A {}

}

和常规成员一样，using 别名指令引入的别名在嵌套范围中也可被具有相似名称的成员所隐藏。在下面的示例中

using R = N1.N2;

namespace N3

{

class R {}

class B: R.A {} // Error, R has no member A

}

B 的声明中对 R.A 的引用将导致编译时错误，原因是这里的 R 所引用的是 N3.R 而不是 N1.N2。

编写 using 别名指令的顺序并不重要，对在 using 别名指令引用的命名空间或类型名称的解析过程既不受 using 别名指令本身影响，也不受直接包含着该指令的编译单元或命名空间体中的其他 using 指令影响。换句话说，对 using 别名指令的命名空间或类型名称的解析，就如同在直接包含该指定的编译单元或命名空间体中根本没有 using 指令一样来处理。在下面的示例中

namespace N1.N2 {}

namespace N3

{

using R1 = N1; // OK

using R2 = N1.N2; // OK

using R3 = R1.N2; // Error, R1 unknown

}

最后一个 using 别名指令导致编译时错误，原因是它不受第一个 using 别名指令的影响。

using 别名指令可以为任何命名空间或类型创建别名，包括它在其中出现的命名空间本身，以及嵌套在该命名空间中的其他任何命名空间或类型。

对一个命名空间或类型进行访问时，无论用它的别名，还是用它的所声明的名称，结果是完全相同的。例如，给定

namespace N1.N2

{

class A {}

}

namespace N3

{

using R1 = N1;

using R2 = N1.N2;

class B

{

N1.N2.A a; // refers to N1.N2.A

R1.N2.A b; // refers to N1.N2.A

R2.A c; // refers to N1.N2.A

}

}

名称 N1.N2.A、R1.N2.A 和 R2.A 是等效的，它们都引用完全限定名为 N1.N2.A 的类。


9.3.2using命名空间指令.1524025

9.3.2 using 命名空间指令

using 命名空间指令将一个命名空间中所包含的类型导入到直接封闭该指定的编译单元或命名空间体中，从而可以直接使用这些被导入的类型的标识符而不必加上它们的限定名。

using-namespace-directive:（using 命名空间指令：）

using namespace-name ;（using 命名空间名称 ;）

在包含 using 命名空间指令的编译单元或命名空间体中的成员声明内，可以直接引用包含在给定命名空间中的那些类型。例如：

namespace N1.N2

{

class A {}

}

namespace N3

{

using N1.N2;

class B: A {}

}

上面的示例中，在 N3 命名空间中的成员声明内，N1.N2 的类型成员是直接可用的，所以类 N3.B 从类 N1.N2.A 派生。

using 命名空间指令导入包含在给定命名空间中的类型，但要注意，它不导入嵌套的命名空间。在下面的示例中

namespace N1.N2

{

class A {}

}

namespace N3

{

using N1;

class B: N2.A {} // Error, N2 unknown

}

using 命名空间指令导入包含在 N1 中的类型，但是不导入嵌套在 N1 中的命名空间。因此，在 B 的声明中引用 N2.A 导致编译时错误，原因是在涉及的范围内没有名为 N2 的成员。

与 using 别名指令不同，using 命名空间指令可能导入一些类型，它们的标识符在封闭该指令的编译单元或命名空间体内的已被用来定义另外一些类型。事实上，using 命名空间指令导入的名称会被封闭该指令的编译单元或命名空间体中具有类似名称的成员所隐藏。例如：

namespace N1.N2

{

class A {}

class B {}

}

namespace N3

{

using N1.N2;

class A {}

}

此处，在 N3 命名空间中的成员声明内，A 引用 N3.A 而不是 N1.N2.A。

当由同一编译单元或命名空间体中的 using 命名空间指令导入多个命名空间时，如果它们所包含的类型中有重名的，则直接引用该名称就被认为是不明确的。在下面的示例中

namespace N1

{

class A {}

}

namespace N2

{

class A {}

}

namespace N3

{

using N1;

using N2;

class B: A {} // Error, A is ambiguous

}

N1 和 N2 都包含一个成员 A，而由于 N3 将两者都导入，所以在 N3 中引用 A 会导致一个编译时错误。这种名称冲突，有两种解决办法：使用限定名来引用 A，或者利用 using 别名指令为想要引用的某个特定的 A 启用一个别名。例如：

namespace N3

{

using N1;

using N2;

using A = N1.A;

class B: A {} // A means N1.A

}

同 using 别名指令一样，using 命名空间指令不会将任何新成员添加到与它所在的编译单元或命名空间相关的声明空间，因而，它仅在该编译单元或者命名空间体内有效。

对 using 命名空间指令所引用的命名空间名称的解析过程，与对 using 别名指令所引用的命名空间或类型名称的解析过程相同。因此，同一编译单元或命名空间体中的 using 命名空间指令不会互相影响，而且可以按照任何顺序编写。


9.4命名空间成员.1531812

9.4 命名空间成员

命名空间成员声明或是一个命名空间声明（第 9.2 节），或是一个类型声明（第 9.5 节）。

namespace-member-declarations:（命名空间成员声明：）

namespace-member-declaration（命名空间成员声明）
namespace-member-declarations namespace-member-declaration（命名空间成员声明 命名空间成员声明）

namespace-member-declaration:（命名空间成员声明：）

namespace-declaration（命名空间声明）
type-declaration（类型声明）

编译单元或命名空间体可以包含命名空间成员声明，而此类声明则为与包含它们的编译单元或命名空间体相关的声明空间提供新成员。


9.5类型声明.1533490

9.5 类型声明

类型声明是类声明（第 10.1 节）、结构声明（第 11.1 节）、接口声明（第 13.1 节）、枚举声明（第 14.1 节）或委托声明（第 15.1 节）。

type-declaration:（类型声明：）

class-declaration（类声明）
struct-declaration（结构声明）
interface-declaration（接口声明）
enum-declaration（枚举声明）
delegate-declaration（委托声明）

类型声明可以作为顶级声明出现在编译单元中，或者作为成员声明出现在命名空间、类或结构内部。

当类型 T 的类型声明作为编译单元中的顶级声明出现时，新声明的类型的完全限定名正好是 T。当类型 T 的类型声明出现在命名空间、类或结构内时，新声明的类型的完全限定名是 N.T，其中 N 是包含它的命名空间、类或结构的完全限定名。

在类或结构内声明的类型称为嵌套类型（第 10.2.6 节）。

在一个类型声明中允许使用哪些访问修饰符以及具有何种默认访问属性，依赖于该声明发生处的上下文（第 3.5.1 节）：

· 在编译单元和命名空间中声明的类型可以具有 public 或 internal 访问属性。默认为 internal 访问属性。

· 在类中声明的类型可以具有 public、protected internal、protected、internal 或 private 访问属性。默认为 private 访问属性。

· 在结构中声明的类型可以具有 public、internal 或 private 访问属性。默认为 private 访问属性。


10.类.1537485

Powered By Gavin http://www.CSharpWeb.com

10. 类

类是一种数据结构，它可以包含数据成员（常数和字段）、函数成员（方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数）以及嵌套类型。类类型支持继承，继承是一种机制，它使派生类可以对基类进行扩展和专用化。


10.1类声明.1538028

10.1 类声明

类声明是一种类型声明（第 9.5 节），它用于声明一个新类。

class-declaration:（类声明：）

attributes opt class-modifiers opt class identifier class-base opt class-body ;opt（属性可选 类修饰符可选 class 标识符 类基可选 类体 ;可选）

类声明的组成方式如下：先是一组属性（第 17 节）（可选），后跟一组类修饰符（第 10.1.1 节）（可选），然后是关键字 class 和一个用来命名该类的标识符，接着是一个类基规范（第 10.1.2 节）（可选），最后还可添加一个分号（第 10.1.3 节）（可选）。


10.1.1类修饰符.1540855

10.1.1 类修饰符

类声明可以根据需要包含一个类修饰符序列：

class-modifiers:（类修饰符：）

class-modifier（类修饰符）
class-modifiers class-modifier（类修饰符 类修饰符）

class-modifier:（类修饰符：）

new
public
protected
internal
private
abstract
sealed

同一修饰符在一个类声明中多次出现是编译时错误。

new 修饰符适用于嵌套类。它表示所修饰的类会把继承下来的同名成员隐藏起来，如第 10.2.2 节中所描述。如果 new 修饰符出现在一个类声明中，而该声明又不是一个嵌套类声明，则导致一个编译时错误。

public、protected、internal 和 private 修饰符控制类的可访问性。根据类声明出现处的上下文，这些修饰符中，有些可能不允许使用（第 3.5.1 节）。

以下几节对 abstract 和 sealed 修饰符进行了讨论。


10.1.1.1抽象类.1543168

10.1.1.1 抽象类

abstract 修饰符用于表示所修饰的类是不完整的，并且它只能用作基类。抽象类与非抽象类在以下方面是不同的：

· 抽象类不能直接实例化，并且对抽象类使用 new 运算符是编译时错误。虽然一些变量和值在编译时的类型可以是抽象的，但是这样的变量和值必须或者为 null，或者含有对非抽象类的实例的引用（此非抽象类是从抽象类派生的）。

· 允许（但不要求）抽象类包含抽象成员。

· 抽象类不能被密封。

当从抽象类派生非抽象类时，这些非抽象类必须具体实现所继承的所有抽象成员，从而重写那些抽象成员。在下面的示例中

abstract class A

{

public abstract void F();

}

abstract class B: A

{

public void G() {}

}

class C: B

{

public override void F() {

// actual implementation of F

}

}

抽象类 A 引入抽象方法 F。类 B 引入另一个方法 G，但由于它不提供 F 的实现，B 也必须声明为抽象类。类 C 重写 F，并提供一个具体实现。由于 C 中没有了抽象成员，因此可以（但并非必须）将 C 声明为非抽象类。


10.1.1.2密封类.1546255

Powered By Gavin http://www.CSharpWeb.com

10.1.1.2 密封类

sealed 修饰符用于防止从所修饰的类派生出其它类。如果一个密封类被指定为其他类的基类，则会发生编译时错误。

密封类不能同时为抽象类。

sealed 修饰符主要用于防止非有意的派生，但是它还能促使某些运行时优化。具体说来，由于密封类永远不会有任何派生类，所以对密封类的实例的虚拟函数成员的调用可以转换为非虚拟调用来处理。


10.1.2类基规范.1546998

10.1.2 类基规范

类声明可以包含一个类基规范，它定义该类的直接基类和由该类实现的接口 (0)。

class-base:（类基：）

: class-type（: 类类型）
: interface-type-list（: 接口类型列表）
: class-type , interface-type-list（: 类类型 , 接口类型列表）

interface-type-list:（接口类型列表：）

interface-type（接口类型）
interface-type-list , interface-type（接口类型列表 , 接口类型）


10.1.2.1基类.1549857

10.1.2.1 基类

当类基中包含一个类类型时，它表示该类就是所声明的类的直接基类。如果一个类声明中没有类基，或所含的类基只列出接口类型，则假定直接基类就是 object。一个类会从它的直接基类继承成员，如第 10.2.1 节中所描述。

在下面的示例中

class A {}

class B: A {}

称类 A 为类 B 的直接基类，而称 B 是从 A 派生的。由于 A 没有显式地指定直接基类，它的直接基类隐含地为 object。

一个类类型的直接基类必须至少与该类类型本身具有同样的可访问性（第 3.5.4 节）。例如，试图从 private 或 internal 类派生一个 public 类，会导致一个编译时错误。

一个类类型的直接基类不能是任何下列类型：System.Array、System.Delegate、System.Enum 或 System.ValueType。

一个类的基类包括它的直接基类以及该直接基类的基类。换句话说，基类集是直接基类关系的传递闭包。在上面的例子中，B 的基类就包括 A 和 object。

除了类 object，每个类有且只有一个直接基类。object 类没有任何直接基类，并且是所有其他类的终极基类。

当类 B 从类 A 派生时，A 依赖于 B 是编译时错误。类直接依赖于它的直接基类（如果有的话），并且还直接依赖于它直接嵌套于其中的类（如果有的话）。从上述定义可以推出：一个类所依赖的类的完备集就是此“直接依赖于”关系的传递闭包。

在以下示例中：

class A: B {}

class B: C {}

class C: A {}

是错误的，因为这些类之间循环依赖。同样，示例

class A: B.C {}

class B: A

{

public class C {}

}

也会导致一个编译时错误，原因是 A 依赖于 B.C（它的直接基类），B.C 依赖于 B（它的直接封闭类），而 B 又循环地依赖于 A。

请注意，一个类不依赖于嵌套在它内部的类。在下面的示例中

class A

{

class B: A {}

}

B 依赖于 A（原因是 A 既是它的直接基类又是它的直接封闭类），但是 A 不依赖于 B（因为 B 既不是 A 的基类也不是 A 的封闭类）。因此，此示例是有效的。

不能从一个 sealed 类派生出别的类。在下面的示例中

sealed class A {}

class B: A {} // Error, cannot derive from a sealed class

类 B 发生错误，因为它试图从 sealed 类 A 派生。


10.1.2.2接口实现.1555133

Powered By Gavin http://www.CSharpWeb.com

10.1.2.2 接口实现

类基规范中可以包含一个接口类型列表，这表示所声明的类实现所列出的各个接口类型。第 13.4 节对接口实现进行了进一步讨论。


10.1.3类体.1555554

Powered By Gavin http://www.CSharpWeb.com

10.1.3 类体

一个类的类体用于定义该类的成员。

class-body:（类体：）

{ class-member-declarations opt }（{ 类成员声明可选 }）


10.2类成员.1556711

10.2 类成员

一个类的成员由两部分组成：由它的类成员声明引入的成员；从它的直接基类继承来的成员。

class-member-declarations:（类成员声明：）

class-member-declaration（类成员声明）
class-member-declarations class-member-declaration（类成员声明 类成员声明）

class-member-declaration:（类成员声明：）

constant-declaration（常数声明）
field-declaration（字段声明）
method-declaration（方法声明）
property-declaration（属性声明）
event-declaration（事件声明）
indexer-declaration（索引器声明）
operator-declaration（运算符声明）
constructor-declaration（构造函数声明）
destructor-declaration（析构函数声明）
static-constructor-declaration（静态构造函数声明）
type-declaration（类型声明）

一个类的成员分为下列几种类别：

· 常数，表示与该类相关联的常数值（第 10.3 节）。

· 字段，即该类的变量（第 10.4 节）。

· 方法，用于实现可由该类执行的计算和操作（第 10.5 节）。

· 属性，用于定义一些命名特性，通过它来读取和写入相关的特性（第 10.6 节）。

· 事件，用于定义可由该类生成的通知（第 10.7 节）。

· 索引器，使该类的实例可按与数组相同的（语法）方式进行索引（第 10.8 节）。

· 运算符，用于定义表达式运算符，通过它对该类的实例进行运算（第 10.9 节）。

· 实例构造函数，用于规定在初始化该类的实例时需要做些什么（第 10.10 节）。

· 析构函数，用于规定在永久地放弃该类的一个实例之前需要做些什么（第 10.12 节）。

· 静态构造函数，用于规定在初始化该类自身时需要做些什么（第 10.11 节）。

· 类型，用于表示一些类型，它们是该类的局部类型（第 9.5 节）。

可以包含可执行代码的成员统称为该类的函数成员。一个类的函数成员包括：方法、属性、事件、索引器、运算符、实例构造函数、析构函数和静态构造函数。

一个类声明将创建一个新的声明空间（第 3.3 节），而直接包含在该类声明内的类成员声明将在此声明空间中引入新成员。下列规则适用于类成员声明：

· 实例构造函数、静态构造函数和析构函数必须具有与直接封闭它们的类相同的名称。所有其他成员的名称必须与该类的名称不同。

· 常数、字段、属性、事件或类型的名称必须不同于在同一个类中声明的所有其他成员的名称。

· 方法的名称必须不同于在同一个类中声明的所有其他非方法的名称。此外，方法的签名（第 3.6 节）必须不同于在同一个类中声明的所有其他方法的签名。

· 实例构造函数的签名必须不同于在同一个类中声明的所有其他实例构造函数的签名。

· 索引器的签名必须不同于在同一个类中声明的所有其他索引器的签名。

· 运算符的签名必须不同于在同一个类中声明的所有其他运算符的签名。

类的继承成员（第 10.2.1 节）不是类的声明空间的组成部分。因此，一个派生类可以使用与所继承的成员相同的名称或签名来声明自已的新成员（这同时也隐藏了被继承的同名成员）。


10.2.1继承.1564985

10.2.1 继承

一个类继承它的直接基类的成员。继承意味着一个类隐式地把它的直接基类的所有成员当作自已的成员，但基类的实例构造函数、静态构造函数和析构函数除外。继承的一些重要性质为：

· 继承是可传递的。如果 C 从 B 派生，而 B 从 A 派生，那么 C 就会既继承在 B 中声明成员，又继承在 A 中声明的成员。

· 派生类扩展它的直接基类。派生类可以向它继承的成员添加新成员，但是它不能移除继承成员的定义。

· 实例构造函数、静态构造函数和析构函数是不可继承的，但所有其他成员是可继承的，无论它们所声明的可访问性（第 3.5 节）如何。但是，根据它们所声明的可访问性，有些继承成员在派生类中可能是无法访问的。

· 派生类可以通过声明具有相同名称或签名的新成员来隐藏（第 3.7.1.2 节）那个被继承的成员。但是，请注意隐藏继承成员并不移除该成员，它只是使被隐藏的成员在派生类中不可直接访问。

· 类的一个实例含有在该类中以及它的所有基类中声明的所有实例字段的集合，并且存在一个从派生类类型到它的任一基类类型的隐式转换（第 6.1.4 节）。因此，可以将对某个派生类实例的引用视为对它的任一个基类实例的引用。

· 类可以声明虚拟方法、属性和索引器，而派生类可以重写这些函数成员的实现。这使类展示出“多态性行为”特征，也就是说，同一个函数成员调用所执行的操作可能是不同的，这取决于用来调用该函数成员的实例的运行时类型。


10.2.2new修饰符.1567887

Powered By Gavin http://www.CSharpWeb.com

10.2.2 new 修饰符

类成员声明中可以使用与一个被继承的成员相同的名称或签名来声明一个成员。发生这种情况时，就称该派生类成员隐藏了基类成员。隐藏一个继承的成员不算是错误，但这确实会导致编译器发出警告。若要取消此警告，派生类成员的声明中可以包含一个 new 修饰符，表示派生成员是有意隐藏基成员的。第 3.7.1.2 节中对该主题进行了进一步讨论。

如果在一个不会隐藏继承成员的声明中使用了 new 修饰符，将为此发出警告。通过移除 new 修饰符可取消显示此警告。


10.2.3访问修饰符.1568787

Powered By Gavin http://www.CSharpWeb.com

10.2.3 访问修饰符

类成员声明中可以使用下列五种可能的声明可访问性（第 3.5.1 节）中的任何一种：public、protected internal、protected、internal 或 private。除 protected internal 组合外，指定一个以上的访问修饰符是编译时错误。当类成员声明不包含任何访问修饰符时，假定为 private。


10.2.4构成类型.1569886

Powered By Gavin http://www.CSharpWeb.com

10.2.4 构成类型

在成员声明中所使用的类型称为成员的构成类型。可能的构成类型包括常数、字段、属性、事件或索引器类型，方法或运算符的返回类型，以及方法、索引器、运算符和实例构造函数的参数类型。一个成员的构成类型必须至少具有与该成员本身相同的可访问性（第 3.5.4 节）。


10.2.5静态和实例成员.1570435

10.2.5 静态和实例成员

类的成员或者是静态成员，或者是实例成员。一般说来，可以这样来理解：静态成员属于类，而实例成员属于对象（类的实例）。

当字段、方法、属性、事件、运算符和构造函数声明中含有 static 修饰符时，它声明静态成员。此外，常数或类型声明会隐式地声明静态成员。静态成员具有下列特征：

· 在 E.M 形式的成员访问（第 7.5.4 节）中引用静态成员 M 时，E 必须表示含有 M 的那个类型本身。E 若表示一个实例，则会导致一个编译时错误。

· 一个静态字段只标识一个存储位置。对一个类无论创建了多少个实例，它的静态字段永远都只有一个副本。

· 静态函数成员（方法、属性、事件、运算符或构造函数）不能作用于具体的实例，在这类函数成员中引用 this 会导致一个编译时错误。

当字段、方法、属性、事件、索引器、构造函数或析构函数的声明中不包含 static 修饰符时，它声明实例成员。（实例成员有时称为非静态成员。）实例成员具有以下特点：

· 在 E.M 形式的成员访问（第 7.5.4 节）中引用实例成员 M 时，E 必须表示某个含有 M 的类型的一个实例。E 若表示类型本身，则会导致一个编译时错误。

· 类的每个实例分别包含一组该类的所有实例字段。

· 实例函数成员（方法、属性、索引器、实例构造函数或析构函数）作用于类的特定实例，此实例可以用 this 访问（第 7.5.7 节）。

下列示例阐释访问静态和实例成员的规则：

class Test

{

int x;

static int y;

void F() {

x = 1; // Ok, same as this.x = 1

y = 1; // Ok, same as Test.y = 1

}

static void G() {

x = 1; // Error, cannot access this.x

y = 1; // Ok, same as Test.y = 1

}

static void Main() {

Test t = new Test();

t.x = 1; // Ok

t.y = 1; // Error, cannot access static member through instance

Test.x = 1; // Error, cannot access instance member through type

Test.y = 1; // Ok

}

}

F 方法显示，在实例函数成员中，简单名称（第 7.5.2 节）既可用于访问实例成员也可用于访问静态成员。G 方法显示，在静态函数成员中，通过简单名称访问实例成员是编译时错误。Main 方法显示，在成员访问（第 7.5.4 节）中，实例成员必须通过实例访问，静态成员必须通过类型访问。


10.2.6嵌套类型.1576320

10.2.6 嵌套类型

在类或结构内声明的类型称为嵌套类型。在编译单元或命名空间内声明的类型称为非嵌套类型。

在下面的示例中

using System;

class A

{

class B

{

static void F() {

Console.WriteLine("A.B.F");

}

}

}

类 B 是嵌套类型，这是因为它在类 A 内声明，而由于类 A 在编译单元内声明，因此它是非嵌套类型。


10.2.6.1完全限定名.1577701

Powered By Gavin http://www.CSharpWeb.com

10.2.6.1 完全限定名

嵌套类型的完全限定名（第 3.8.1 节）为 S.N，其中 S 是声明了 N 类型的那个类型的完全限定名。


10.2.6.2声明可访问性.1578210

10.2.6.2 声明可访问性

非嵌套类型可以具有 public 或 internal 声明可访问性，默认的声明可访问性是 internal。嵌套类型也可以具有上述两种声明可访问性，外加一种或更多种其他的声明可访问性，具体取决于包含它的那个类型是类还是结构：

· 在类中声明的嵌套类型可以具有五种声明可访问性（public、protected internal、protected、internal 或 private）中的任一种，而且与其他类成员一样，默认的声明可访问性是 private。

· 在结构中声明的嵌套类型可以具有三种声明可访问性（public、internal 或 private）中的任一种，而且与其他结构成员一样，默认的声明可访问性为 private。

在以下示例中：

public class List

{

// Private data structure

private class Node

{

public object Data;

public Node Next;

public Node(object data, Node next) {

this.Data = data;

this.Next = next;

}

}

private Node first = null;

private Node last = null;

// Public interface

public void AddToFront(object o) {...}

public void AddToBack(object o) {...}

public object RemoveFromFront() {...}

public object RemoveFromBack() {...}

public int Count { get {...} }

}

声明了一个私有嵌套类 Node。


10.2.6.3隐藏.1582352

10.2.6.3 隐藏

嵌套类型可以隐藏（第 3.7.1 节）基成员。对嵌套类型声明允许使用 new 修饰符，以便可以明确表示隐藏。在以下示例中：

using System;

class Base

{

public static void M() {

Console.WriteLine("Base.M");

}

}

class Derived: Base

{

new public class M

{

public static void F() {

Console.WriteLine("Derived.M.F");

}

}

}

class Test

{

static void Main() {

Derived.M.F();

}

}

显示嵌套类 M，它隐藏了在 Base 中定义的方法 M。


10.2.6.4this访问.1584555

10.2.6.4 this 访问

关于 this 访问（第 7.5.7 节），嵌套类型和包含它的那个类型并不具有特殊的关系。准确地说，在嵌套类型内，this 不能用于引用包含它的那个类型的实例成员。当需要在嵌套类型内部访问包含它的那个类型的实例成员时，通过将代表所需实例的 this 作为一个参数传递给该嵌套类型的构造函数，就可以进行所需的访问了。以下示例

using System;

class C

{

int i = 123;

public void F() {

Nested n = new Nested(this);

n.G();

}

public class Nested {

C this_c;

public Nested(C c) {

this_c = c;

}

public void G() {

Console.WriteLine(this_c.i);

}

}

}

class Test {

static void Main() {

C c = new C();

c.F();

}

}

显示了此技巧。C 实例创建了一个 Nested 实例并将代表它自己的 this 传递给 Nested 的构造函数，这样，就可以对 C 的实例成员进行访问了。


10.2.6.5对包含类型的私有和受保护成员的访问.1587487

10.2.6.5 对包含类型的私有和受保护成员的访问

嵌套类型可以访问包含它的那个类型可访问的所有成员，包括该类型自己的具有 private 和 protected 声明可访问性的成员。在以下示例中：

using System;

class C

{

private static void F() {

Console.WriteLine("C.F");

}

public class Nested

{

public static void G() {

F();

}

}

}

class Test

{

static void Main() {

C.Nested.G();

}

}

显示包含有嵌套类 Nested 的类 C。在 Nested 内，方法 G 调用在 C 中定义的静态方法 F，而 F 具有 private 声明可访问性。

嵌套类型还可以访问在包含它的那个类型的基类型中定义的受保护成员。在下面的示例中

using System;

class Base

{

protected void F() {

Console.WriteLine("Base.F");

}

}

class Derived: Base

{

public class Nested

{

public void G() {

Derived d = new Derived();

d.F(); // ok

}

}

}

class Test

{

static void Main() {

Derived.Nested n = new Derived.Nested();

n.G();

}

}

嵌套类 Derived.Nested 通过对一个 Derived 的实例进行调用，访问在 Derived 的基类 Base 中定义的受保护方法 F。


10.2.7保留成员名称.1591939

10.2.7 保留成员名称

为了便于基础 C# 运行库的实现，对于每个属性、事件或索引器的源成员声明，任何一个实现都必须根据该成员声明的种类、名称和类型保留两个方法签名。如果程序声明一个成员，而该成员的签名与这些保留签名中的一个匹配，那么即使所使用的基础运行库的实现并没有使用这些保留签名，这仍将是一个编译时错误。

保留名称不会引入声明，因此它们不参与成员查找。但是，一个声明若具有相关联的保留方法签名，则该方法签名会参与继承（第 10.2.1 节），而且可以使用 new 修饰符（第 10.2.2 节）将它隐藏起来。

保留这些名称有三个目的：

· 使基础的实现可以通过将普通标识符用作一个方法名称，从而对 C# 语言的功能进行 get 或 set 访问。

· 使其他语言可以通过将普通标识符用作一个方法名称，对 C# 语言的功能进行 get 或 set 访问，从而实现交互操作。

· 使保留成员名称的细节在所有的 C# 实现中保持一致，这有助于确保被一个符合本规范的编译器所接受的源程序也可被另一个编译器接受。

析构函数（第 10.12 节）的声明也会导致一个签名被保留（第 10.2.7.4 节）。


10.2.7.1为属性保留的成员名称.1594065

10.2.7.1 为属性保留的成员名称

对于类型 T 的属性 P（第 10.6 节），保留了下列签名：

T get_P();

void set_P(T value);

即使该属性是只读或者只写的，这两个签名仍然都被保留。

在下面的示例中

using System;

class A

{

public int P {

get { return 123; }

}

}

class B: A

{

new public int get_P() {

return 456;

}

new public void set_P(int value) {

}

}

class Test

{

static void Main() {

B b = new B();

A a = b;

Console.WriteLine(a.P);

Console.WriteLine(b.P);

Console.WriteLine(b.get_P());

}

}

类 A 定义了只读属性 P，从而保留了 get_P 和 set_P 方法的签名。类 B 从 A 派生并隐藏了这两个保留的签名。此例产生输出：

123

123

456


10.2.7.2为事件保留的成员名称.1597297

Powered By Gavin http://www.CSharpWeb.com

10.2.7.2 为事件保留的成员名称

对于委托类型 T 的事件 E（第 10.7 节），保留了下列签名：

void add_E(T handler);

void remove_E(T handler);


10.2.7.3为索引器保留的成员名称.1598047

10.2.7.3 为索引器保留的成员名称

对于类型 T 的且具有参数列表 L 的索引器（第 10.8 节），保留了下列签名：

T get_Item(L);

void set_Item(L, T value);

即使索引器是只读或者只写的，这两个签名仍然都被保留。


10.2.7.4为析构函数保留的成员名称.1598975

Powered By Gavin http://www.CSharpWeb.com

10.2.7.4 为析构函数保留的成员名称

对于包含析构函数（第 10.12 节）的类，保留了下列签名：

void Finalize();


10.3常数.1599513

10.3 常数

常数是类成员，它表示一个常数值：一个可以在编译时计算的值。一个常数声明可引入一个或多个给定类型的常数。

constant-declaration:（常数声明：）

attributes opt constant-modifiers opt const type constant-declarators ;（属性可选 常数修饰符可选 const 类型 常数声明符 ;）

constant-modifiers:（常数修饰符：）

constant-modifier（常数修饰符）
constant-modifiers constant-modifier（常数修饰符 常数修饰符）

constant-modifier:（常数修饰符：）

new
public
protected
internal
private

constant-declarators:（常数声明符：）

constant-declarator（常数声明符）
constant-declarators , constant-declarator（常数声明符 , 常数声明符）

constant-declarator:（常数声明符：）

identifier = constant-expression（标识符 = 常数表达式）

一个常数声明可包含一组属性（第 17 节）、一个 new 修饰符（第 10.2.2 节）和一个由四个访问修饰符构成的有效组合（第 10.2.3 节）。属性和修饰符适用于所有由该常数声明所声明的成员。虽然常数被认为是静态成员，但在常数声明中既不要求也不允许使用 static 修饰符。同一个修饰符在一个常数声明中多次出现是错误的。

常数声明的类型用于指定由声明引入的成员的类型。类型后跟一个常数声明符列表，该列表中的每个声明符引入一个新成员。常数声明符包含一个用于该命名成员的标识符，后跟一个“=”标记，然后跟一个对该成员赋值的常数表达式（第 7.15 节）。

常数声明中指定的类型必须是 sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、一个枚举类型或引用类型。每个常数表达式所产生的值，必须是属于目标类型的，或者可以通过一个隐式转换（第 6.1 节）转换为目标类型。

常数的类型必须至少与常数本身具有同样的可访问性（第 3.5.4 节）。

常数的值从表达式获取，此表达式使用简单名称（第 7.5.2 节）或成员访问（第 7.5.4 节）。

常数本身可以出现在常数表达式中。因此，常数可用在任何需要常数表达式的构造中。这样的构造示例包括 case 标签、goto case 语句、enum 成员声明、属性和其他的常数声明。

如第 7.15 节中所描述，常数表达式是在编译时就可以完全计算出来的表达式。由于创建 string 以外的引用类型的非空值的唯一方法是应用 new 运算符，但常数表达式中不允许使用 new 运算符，因此，除 string 以外的引用类型常数的唯一可能的值是 null。

如果需要一个具有常数值的符号名称，但是该值的类型不允许在常数声明中使用，或在编译时无法由常数表达式计算出该值，则可以改用 readonly 字段（第 10.4.2 节）。

声明了多个常数的一个常数声明等效于具有相同属性、修饰符和类型的多个常数的声明，其中每个声明均只声明一个常数。例如

class A

{

public const double X = 1.0, Y = 2.0, Z = 3.0;

}

等效于

class A

{

public const double X = 1.0;

public const double Y = 2.0;

public const double Z = 3.0;

}

一个常数可以依赖于同一程序内的其他常数，只要这种依赖关系不是循环的。编译器会自动地安排适当的顺序来计算各个常数声明。在下面的示例中

class A

{

public const int X = B.Z + 1;

public const int Y = 10;

}

class B

{

public const int Z = A.Y + 1;

}

编译器首先计算 A.Y，然后计算 B.Z，最后计算 A.X，产生值 10、11 和 12。常数声明也可以依赖于其他程序中的常数，但这种依赖关系只能是单方向的。上面的示例中，如果 A 和 B 在不同的程序中声明，A.X 可以依赖于 B.Z，但是 B.Z 就无法同时再依赖于 A.Y 了。


10.4字段.1609220

10.4 字段

字段是一种表示与对象或类关联的变量的成员。字段声明用于引入一个或多个给定类型的字段。

field-declaration:（字段声明：）

attributes opt field-modifiers opt type variable-declarators ;（属性可选 字段修饰符可选 类型 变量声明符 ;）

field-modifiers:（字段修饰符：）

field-modifier（字段修饰符）
field-modifiers field-modifier（字段修饰符 字段修饰符）

field-modifier:（字段修饰符：）

new
public
protected
internal
private
static
readonly
volatile

variable-declarators:（变量声明符：）

variable-declarator（变量声明符）
variable-declarators , variable-declarator（变量声明符 , 变量声明符）

variable-declarator:（变量声明符：）

identifier（标识符）
identifier = variable-initializer（标识符 = 变量初始值设定项）

variable-initializer:（变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设定项）

字段声明可以包含一组属性（第 17 节），一个 new 修饰符（第 10.2.2 节），关于四个访问修饰符的一个有效组合（第 10.2.3 节）和一个 static 修饰符（第 10.4.1 节）。此外，字段声明还可以包含一个 readonly 修饰符（第 10.4.2 节）或一个 volatile 修饰符（第 10.4.3 节），但不能两者都包含。属性和修饰符适用于由该字段声明所声明的所有成员。同一个修饰符在一个字段声明中多次出现是错误的。

字段声明的类型用于指定由该声明引入的成员的类型。类型后跟一个变量声明符列表，其中每个变量声明符引入一个新成员。变量声明符包含一个用于命名该成员的标识符，还可以根据需要再后跟一个“=”标记，以及一个用于赋予成员初始值的变量初始值设定项（第 10.4.5 节）。

字段的类型必须至少具有与字段本身同样的可访问性（第 3.5.4 节）。

字段的值从一个表达式获得，该表达式使用简单名称（第 7.5.2 节）或成员访问（第 7.5.4 节）。使用赋值（第 7.13 节）修改非只读字段的值。非只读字段的值可以使用后缀增量和减量运算符（第 7.5.9 节）以及前缀增量和减量运算符（第 7.6.5 节）获取和修改。

声明了多个字段的一个字段声明等效于具有相同属性、修饰符和类型的多个字段的声明，其中每个声明均只声明一个字段。例如

class A

{

public static int X = 1, Y, Z = 100;

}

等效于

class A

{

public static int X = 1;

public static int Y;

public static int Z = 100;

}


10.4.1静态和实例字段.1616676

10.4.1 静态和实例字段

当一个字段声明中含有 static 修饰符时，由该声明引入的字段为静态字段。当不存在 static 修饰符时，由该声明引入的字段为实例字段。静态字段和实例字段是 C# 所支持的几种变量（第 5 节）中的两种，它们有时被分别称为静态变量和实例变量。

静态字段不属于某个特定的实例；相反，它只标识了一个存储位置。不管创建了多少个类实例，对于关联的应用程序域来说，在任何时候静态字段都只会有一个副本。

实例字段属于某个实例。具体说来，类的每个实例都包含了该类的所有实例字段的一个单独的集合。

若用 E.M 的成员访问形式（第 7.5.4 节）来引用一个字段，如果 M 是静态字段，则 E 必须表示含有 M 的一个类型，但如果 M 是实例字段，则 E 必须表示一个含有 M 的类型的某个实例。

（第 10.2.5 节）对静态成员和实例成员之间的差异进行了进一步讨论。


10.4.2只读字段.1618230

10.4.2 只读字段

当字段声明中含有 readonly 修饰符时，该声明所引入的字段为只读字段。给只读字段的直接赋值只能作为声明的组成部分出现，或在同一类中的实例构造函数或静态构造函数中出现。（在这些上下文中，只读字段可以被多次赋值。）准确地说，只在下列上下文中允许对 readonly 字段进行直接赋值：

· 在用于引入该字段的变量声明符中（通过添加一个变量初始值设定项）。

· 对于实例字段，在包含字段声明的类的实例构造函数中；对于静态字段，在包含字段声明的类的静态构造函数中。也只有在这些上下文中，将 readonly 字段作为 out 或 ref 参数传递才有效。

在其他任何上下文中，试图对 readonly 字段进行赋值或将它作为 out 或 ref 参数传递都会导致一个编译时错误。


10.4.2.1对常数使用静态只读字段.1619843

10.4.2.1 对常数使用静态只读字段

如果需要一个具有常数值的符号名称，但该值的类型不允许在 const 声明中使用，或者无法在编译时计算出该值，则 static readonly 字段就可以发挥作用了。在下面的示例中

public class Color

{

public static readonly Color Black = new Color(0, 0, 0);

public static readonly Color White = new Color(255, 255, 255);

public static readonly Color Red = new Color(255, 0, 0);

public static readonly Color Green = new Color(0, 255, 0);

public static readonly Color Blue = new Color(0, 0, 255);

private byte red, green, blue;

public Color(byte r, byte g, byte b) {

red = r;

green = g;

blue = b;

}

}

Black、White、Red、Green 和 Blue 成员不能被声明为 const 成员，这是因为在编译时无法计算它们的值。不过，将它们声明为 static readonly 能达到基本相同的效果。


10.4.2.2常数和静态只读字段的版本控制.1622217

10.4.2.2 常数和静态只读字段的版本控制

常数和只读字段具有不同的二进制版本控制语义。当表达式引用常数时，该常数的值在编译时获取，但是当表达式引用只读字段时，要等到运行时才获取该字段的值。请考虑一个包含两个单独程序的应用程序：

using System;

namespace Program1

{

public class Utils

{

public static readonly int X = 1;

}

}

namespace Program2

{

class Test

{

static void Main() {

Console.WriteLine(Program1.Utils.X);

}

}

}

Program1 和 Program2 命名空间表示两个单独编译的程序。由于 Program1.Utils.X 声明为静态只读字段，因此 Console.WriteLine 语句要输出的值在编译时是未知的，直到在运行时才能获取。这样，如果更改 X 的值并重新编译 Program1，则即使 Program2 未被重新编译，Console.WriteLine 语句也将输出新值。但是，假如 X 是常数，X 的值将在编译 Program2 时获取，并且在重新编译 Program2 之前不会受到 Program1 中的更改的影响。


10.4.3易失字段.1624880

10.4.3 易失字段

当字段声明中含有 volatile 修饰符时，该声明引入的字段为易失字段。

由于采用了优化技术（它会重新安排指令的执行顺序），在多线程的程序运行环境下，如果不采取同步（如由 lock 语句（第 8.12 节）所提供的）控制手段，则对于非易失字段的访问可能会导致意外的和不可预见的结果。这些优化可以由编译器、运行时系统或硬件执行。但是，对于易失字段，优化时的这种重新排序必须遵循以下规则：

· 读取一个易失字段称为易失读取。易失读取具有“获取语义”；也就是说，按照指令序列，所有排在易失读取之后的对内存的引用，在执行时也一定排在它的后面。

· 写入一个易失字段称为易失写入。易失写入具有“释放语义”；也就是说，按照指令序列，所有排在易失写入之前的对内存的引用，在执行时也一定排在它的前面。

这些限制能确保所有线程都会观察到由其他任何线程所执行的易失写入（按照原来安排的顺序）。一个遵循本规范的实现并非必须做到：使易失写入的执行顺序，在所有正在执行的线程看来都是一样的。易失字段的类型必须是下列类型中的一种：

· 引用类型。

· 类型 byte、sbyte、short、ushort、int、uint、char、float 或 bool。

· 枚举基类型为 byte、sbyte、short、ushort、int 或 uint 的枚举类型。

在以下示例中：

using System;

using System.Threading;

class Test

{

public static int result;

public static volatile bool finished;

static void Thread2() {

result = 143;

finished = true;

}

static void Main() {

finished = false;

// Run Thread2() in a new thread

new Thread(new ThreadStart(Thread2)).Start();

// Wait for Thread2 to signal that it has a result by setting

// finished to true.

for (;;) {

if (finished) {

Console.WriteLine("result = {0}", result);

return;

}

}

}

}

产生下列输出：

result = 143

在本示例中，方法 Main 启动一个新线程，该线程运行方法 Thread2。该方法将一个值存储在叫做 result 的非易失字段中，然后将 true 存储在易失字段 finished 中。主线程等待字段 finished 被设置为 true，然后读取字段 result。由于 finished 已被声明为 volatile，主线程从字段 result 读取的值一定是 143。如果字段 finished 未被声明为 volatile，则对 finished 和 result 的写入的顺序就可能改变，这样，当主线程读取字段 result 时，它可能尚未被赋值（因而为初始值 0）。将 finished 声明为 volatile 字段可以防止这种不一致性。


10.4.4字段初始化.1631464

10.4.4 字段初始化

字段（无论是静态字段还是实例字段）的初始值都是字段的类型的默认值（第 5.2 节）。在此默认初始化发生之前是不可能看到字段的值的，因此字段永远不会是“未初始化的”。在以下示例中：

using System;

class Test

{

static bool b;

int i;

static void Main() {

Test t = new Test();

Console.WriteLine("b = {0}, i = {1}", b, t.i);

}

}

产生输出

b = False, i = 0

这是因为 b 和 i 都被自动初始化为默认值。


10.4.5变量初始值设定项.1632992

z
Powered By Gavin http://www.CSharpWeb.com

10.4.5 变量初始值设定项

字段声明可以包含变量初始值设定项。对于静态字段，变量初始值设定项相当于在类初始化期间执行的赋值语句。对于实例字段，变量初始值设定项相当于创建类的实例时执行的赋值语句。

在以下示例中：

using System;

class Test

{

static double x = Math.Sqrt(2.0);

int i = 100;

string s = "Hello";

static void Main() {

Test a = new Test();

Console.WriteLine("x = {0}, i = {1}, s = {2}", x, a.i, a.s);

}

}

产生输出

x = 1.4142135623731, i = 100, s = Hello

这是因为对 x 的赋值发生在静态字段初始值设定项执行时，而对 i 和 s 的赋值发生在实例字段初始值设定项执行时。

第 10.4.4 节中描述的默认值初始化对所有字段都发生，包括具有变量初始值设定项的字段。因此，当初始化一个类时，首先将该类中的所有静态字段初始化为它们的默认值，然后以文本顺序执行各个静态字段初始值设定项。与此类似，创建类的一个实例时，首先将该实例中的所有实例字段初始化为它们的默认值，然后以文本顺序执行各个实例字段初始值设定项。

在具有变量初始值设定项的静态字段处于默认值状态时，也有可能访问它们。但是，为了培养良好的编程风格，强烈建议不要这么做。在以下示例中：

using System;

class Test

{

static int a = b + 1;

static int b = a + 1;

static void Main() {

Console.WriteLine("a = {0}, b = {1}", a, b);

}

}

显示此行为。尽管 a 和 b 的定义是循环的，此程序仍是有效的。它产生以下输出：

a = 1, b = 2

这是因为静态字段 a 和 b 在它们的初始值设定项执行之前被初始化为 0（int 的默认值）。当 a 的初始值设定项运行时，b 的值为零，所以 a 被初始化为 1。当 b 的初始值设定项运行时，a 的值已经为 1，因此 b 被初始化为 2。


10.4.5.1静态字段初始化.1636876

10.4.5.1 静态字段初始化

类的静态字段变量初始值设定项对应于一个赋值序列，这些赋值按照它们在相关的类声明中出现的文本顺序执行。如果类中存在静态构造函数（第 10.11 节），则静态字段初始值设定项的执行在该静态构造函数即将执行前发生。否则，静态字段初始值设定项在第一次使用该类的静态字段之前先被执行，但实际执行时间依赖于具体的实现。在以下示例中：

using System;

class Test

{

static void Main() {

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

}

public static int F(string s) {

Console.WriteLine(s);

return 1;

}

}

class A

{

public static int X = Test.F("Init A");

}

class B

{

public static int Y = Test.F("Init B");

}

或者产生如下输出：

Init A

Init B

1 1

或者产生如下输出：

Init B

Init A

1 1

这是因为 X 的初始值设定项和 Y 的初始值设定项的执行顺序无法预先确定，上述两种顺序都有可能发生；唯一能够确定的是：它们一定会在对那些字段的引用之前发生。但是，下面的示例：

using System;

class Test

{

static void Main() {

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

}

public static int F(string s) {

Console.WriteLine(s);

return 1;

}

}

class A

{

static A() {}

public static int X = Test.F("Init A");

}

class B

{

static B() {}

public static int Y = Test.F("Init B");

}

所产生的输出必然是：

Init B

Init A

1 1

这是因为关于何时执行静态构造函数的规则（在第 10.11 节中定义）规定：B 的静态构造函数（以及 B 的静态字段初始值设定项）必须在 A 的静态构造函数和字段初始值设定项之前运行。


10.4.5.2实例字段初始化.1641594

10.4.5.2 实例字段初始化

类的实例字段变量初始值设定项相当于一个赋值序列，它在当控制进入该类的任一个实例构造函数（第 10.10.1 节）时立即执行。这些变量初始值设定项按它们出现在类声明中的文本顺序执行。第 10.10 节中对类实例的创建和初始化过程进行了进一步描述。

实例字段的变量初始值设定项不能引用正在创建的实例。因此，在变量初始值设定项中引用 this 是编译时错误，同样，在变量初始值设定项中通过简单名称引用任何一个实例成员也是一个编译时错误。在下面的示例中

class A

{

int x = 1;

int y = x + 1; // Error, reference to instance member of this

}

y 的变量初始值设定项导致编译时错误，原因是它引用了正在创建的实例的成员。


10.5方法.1643034

10.5 方法

方法是一种用于实现可以由对象或类执行的计算或操作的成员。方法是使用方法声明来声明的：

method-declaration:（方法声明：）

method-header method-body（方法头 方法体）

method-header:（方法头：）

attributes opt method-modifiers opt return-type member-name ( formal-parameter-list opt )（属性可选 方法修饰符可选 返回类型 成员名 ( 形参表可选 )）

method-modifiers:（方法修饰符：）

method-modifier（方法修饰符）
method-modifiers method-modifier（方法修饰符 方法修饰符）

method-modifier:（方法修饰符：）

new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

return-type:（返回类型：）

type（类型）
void

member-name:（成员名：）

identifier（标识符）
interface-type . identifier（接口类型 . 标识符）

method-body:（方法体：）

block（块）
;

方法声明中可包含一组属性（第 17 节）和关于四个访问修饰符（第 10.2.3 节）的一个有效组合，还可含有 new（第 10.2.2 节）、static（第 10.5.2 节）、virtual（第 10.5.3 节）、override（第 10.5.4 节）、sealed（第 10.5.5 节）、abstract（第 10.5.6 节）以及 extern（第 10.5.7 节）修饰符。

如果以下所有条件为真，则所述的声明就具有一个有效的修饰符组合：

· 该声明包含一个有效的访问修饰符（第 10.2.3 节）组合。

· 该声明中所含的修饰符没有彼此相同的。

· 该声明最多包含下列修饰符中的一个：static、virtual 和 override。

· 该声明最多包含下列修饰符中的一个：new 和 override。

· 如果该声明包含了 abstract 修饰符，则该声明不包含下列任何修饰符：static、virtual¡¢sealed 或 extern。

· 如果声明包含 private 修饰符，则该声明不包含下列任何修饰符：virtual、override 或 abstract。

· 如果声明包含 sealed 修饰符，则该声明还包含 override 修饰符。

方法声明的返回类型用于指定由该方法计算和返回的值的类型。如果方法不返回一个值，则它的返回类型为 void。

成员名用于指定所声明的方法的名称。若所声明的方法不是关于某个显式接口成员的一个实现（第 13.4.1 节），则成员名就是一个标识符。而对于一个显式接口成员实现，成员名应由接口类型后跟一个“.”和一个标识符组成。

可选的形参表用于指定方法的参数（第 10.5.1 节）。

返回类型和在方法的形参表中引用的各个类型必须至少具有和方法本身相同的可访问性（第 3.5.4 节）。

对于 abstract 和 extern 方法，方法体只有一个分号。对于所有其他方法，方法体由一个块组成，该块用于指定在调用方法时要执行哪些语句。

一个方法的名称和形参表定义了该方法的签名（第 3.6 节）。准确地说，一个方法的签名由它的名称以及它的形参的数目、每个形参的修饰符和类型组成。返回类型不是方法签名的组成部分，形参的名称也不是。

方法的名称必须不同于在同一个类中声明的所有其他非方法的名称。此外，一个方法的签名必须不同于在同一个类中声明的所有其他方法的签名。


10.5.1方法参数.1652644

10.5.1 方法参数

一个方法的参数（如果有的话）是由该方法的形参表来声明的。

formal-parameter-list:（形参表：）

fixed-parameters（固定参数）
fixed-parameters , parameter-array（固定参数 , 参数数组）
parameter-array（参数数组）

fixed-parameters:（固定参数：）

fixed-parameter（固定参数）
fixed-parameters , fixed-parameter（固定参数 , 固定参数）

fixed-parameter:（固定参数：）

attributes opt parameter-modifier opt type identifier（属性可选 参数修饰符可选 类型 标识符）

parameter-modifier:（参数修饰符：）

ref
out

parameter-array:（参数数组：）

attributes opt params array-type identifier（属性可选 params 数组类型 标识符）

形参表包含一个或多个由逗号分隔的参数，其中只有最后一个参数才可以是参数数组。

固定参数包含一组可选的属性（第 17 节）、一个可选的 ref 或 out 修饰符、一个类型和一个标识符。每个固定参数均声明了参数，指定了该参数的名称及其所属的类型。

参数数组包含一组可选的属性（第 17 节）、一个 params 修饰符、一个数组类型和一个标识符。参数数组声明单个具有给定名称且属于给定数组类型的参数。参数数组中的数组类型必须是一维数组类型（第 12.1 节）。在方法调用中，参数数组可以用单个给定数组类型的参数来表示，也可以用零个或多个该数组元素类型的参数来表示。第 10.5.1.4 节中对参数数组进行了进一步描述。

一个方法声明为所声明的参数和局部变量创建了单独的声明空间。该方法的形参表和在方法的块中的局部变量声明把它们所声明的名称提供给此声明空间。方法声明空间中的所有名称都必须是唯一的。因此，参数或局部变量的名称与其他参数或局部变量的名称相同是编译时错误。

执行一个方法调用（第 7.5.5.1 节）时，创建关于该方法的形参列表和局部变量的一个副本（仅适用于本次调用），而该调用所提供的参数列表则用于把所含的值或变量引用赋给新创建的形参。在方法的块内，形参可以在简单名称表达式（第 7.5.2 节）中由它们的标识符引用。

有四种形参：

· 值参数，声明时不带任何修饰符。

· 引用参数，用 ref 修饰符声明。

· 输出参数，用 out 修饰符声明。

· 参数数组，用 params 修饰符声明。

如第 3.6 节中所描述的，ref 和 out 修饰符是方法签名的组成部分，但 params 修饰符不是。


10.5.1.1值参数.1659829

Powered By Gavin http://www.CSharpWeb.com

10.5.1.1 值参数

声明时不带修饰符的参数是值参数。一个值参数相当于一个局部变量，只是它的初始值来自该方法调用所提供的相应参数。

当形参是值参数时，方法调用中的对应参数必须是一个表达式，并且它的类型可以隐式转换（第 6.1 节）为形参的类型。

允许方法将新值赋给值参数。这样的赋值只影响由该值参数表示的局部存储位置，而不会影响在方法调用时由调用方给出的实参。


10.5.1.2引用参数.1660573

10.5.1.2 引用参数

用 ref 修饰符声明的参数是引用参数。与值参数不同，引用参数并不创建新的存储位置。相反，引用参数表示的存储位置恰是在方法调用中作为参数给出的那个变量所表示的存储位置。

当形参为引用参数时，方法调用中的对应参数必须由关键字 ref，后跟一个与形参类型相同的变量引用（第 5.4 节）组成。变量在可以作为引用参数传递之前，必须先明确赋值。

在方法内部，引用参数始终被认为是明确赋值的。

在以下示例中：

using System;

class Test

{

static void Swap(ref int x, ref int y) {

int temp = x;

x = y;

y = temp;

}

static void Main() {

int i = 1, j = 2;

Swap(ref i, ref j);

Console.WriteLine("i = {0}, j = {1}", i, j);

}

}

产生输出

i = 2, j = 1

对于 Main 中的 Swap 的调用，x 表示 i 而 y 表示 j。因此，该调用具有交换 i 和 j 的值的效果。

在采用引用参数的方法中，多个名称可能表示同一存储位置。在下面的示例中

class A

{

string s;

void F(ref string a, ref string b) {

s = "One";

a = "Two";

b = "Three";

}

void G() {

F(ref s, ref s);

}

}

方法 G 中对 F 的调用为 a 和 b 分别传递了一个对 s 的引用。因此，对于该调用，名称 s、a 和 b 全都引用同一存储位置，并且三个赋值全都修改了同一个实例字段 s。


10.5.1.3输出参数.1664705

10.5.1.3 输出参数

用 out 修饰符声明的参数是输出参数。类似于引用参数，输出参数不创建新的存储位置。相反，输出参数表示的存储位置恰是在该方法调用中作为参数给出的那个变量所表示的存储位置。

当形参为输出参数时，方法调用中的相应参数必须由关键字 out，后跟一个与形参类型相同的变量引用（第 5.4 节）组成。变量在可以作为输出参数传递之前不一定需要明确赋值，但是在将变量作为输出参数传递的调用之后，该变量被认为是明确赋值的。

在方法内部，与局部变量相同，输出参数最初被认为是未赋值的，因而必须在使用它的值之前明确赋值。

在方法返回之前，该方法的每个输出参数都必须明确赋值。

输出参数通常用在需要产生多个返回值的方法中。例如：

using System;

class Test

{

static void SplitPath(string path, out string dir, out string name) {

int i = path.Length;

while (i > 0) {

char ch = path[i – 1];

if (ch == '\\' || ch == '/' || ch == ':') break;

i--;

}

dir = path.Substring(0, i);

name = path.Substring(i);

}

static void Main() {

string dir, name;

SplitPath("c:\\Windows\\System\\hello.txt", out dir, out name);

Console.WriteLine(dir);

Console.WriteLine(name);

}

}

此例产生输出：

c:\Windows\System\

hello.txt

请注意，dir 和 name 变量在它们被传递给 SplitPath 之前可以是未赋值的，而它们在调用之后就被认为是明确赋值的了。


10.5.1.4参数数组.1668209

10.5.1.4 参数数组

用 params 修饰符声明的参数是参数数组。如果形参表包含一个参数数组，则该参数数组必须位于该列表的最后而且它必须是一维数组类型。例如，类型 string[] 和 string[][] 可用作参数数组的类型，但是类型 string[,] 不能。不可能将 params 修饰符与 ref 和 out 修饰符组合起来使用。

在一个方法调用中，允许以下列两种方式之一来为参数数组指定对应的参数：

· 赋予参数数组的参数可以是一个表达式，它的类型可以隐式转换（第 6.1 节）为该参数数组的类型。在此情况下，参数数组的作用与值参数完全一样。

· 或者，此调用可以为参数数组指定零个或多个参数，其中每个参数都是一个表达式，它的类型可隐式转换（第 6.1 节）为该参数数组的元素的类型。在此情况下，调用会创建一个该参数数组类型的实例，其所含的元素个数等于给定的参数个数，再用给定的参数值初始化此数组实例的每个元素，然后将新创建的数组实例用作实参。

除了允许在调用中使用可变数量的参数，参数数组与同一类型的值参数（第 10.5.1.1 节）完全等效。

在以下示例中：

using System;

class Test

{

static void F(params int[] args) {

Console.Write("Array contains {0} elements:", args.Length);

foreach (int i in args)

Console.Write(" {0}", i);

Console.WriteLine();

}

static void Main() {

int[] arr = {1, 2, 3};

F(arr);

F(10, 20, 30, 40);

F();

}

}

产生输出

Array contains 3 elements: 1 2 3

Array contains 4 elements: 10 20 30 40

Array contains 0 elements:

F 的第一次调用只是将数组 a 作为值参数传递。F 的第二次调用自动创建一个具有给定元素值的四元素 int[] 并将该数组实例作为值参数传递。与此类似，F 的第三次调用创建一个零元素的 int[] 并将该实例作为值参数传递。第二次和第三次调用完全等效于编写下列代码：

F(new int[] {10, 20, 30, 40});

F(new int[] {});

执行重载决策时，具有参数数组的方法可能以它的正常形式或以它的扩展形式（第 7.4.2.1 节）成为适用的。只有在方法的正常形式不适用，并且在同一类型中尚未声明与方法的扩展形式具有相同签名的方法时，上述的方法扩展形式才可供选用。

在以下示例中：

using System;

class Test

{

static void F(params object[] a) {

Console.WriteLine("F(object[])");

}

static void F() {

Console.WriteLine("F()");

}

static void F(object a0, object a1) {

Console.WriteLine("F(object,object)");

}

static void Main() {

F();

F(1);

F(1, 2);

F(1, 2, 3);

F(1, 2, 3, 4);

}

}

产生输出

F();

F(object[]);

F(object,object);

F(object[]);

F(object[]);

在该示例中，在同一个类中，已经声明了两个常规方法，它们的签名与具有参数数组的那个方法的扩展形式相同。因此，在执行重载决策时不考虑这些扩展形式，因而第一次和第三次方法调用将选择常规方法。当在某个类中声明了一个具有参数数组的方法时，同时再声明一些与该方法的扩展形式具有相同的签名的常规方法，这种情况比较常见。这样做可以避免为数组配置内存空间（若调用具有参数数组的方法的扩展形式，则无法避免）。

当参数数组的类型为 object[] 时，在方法的正常形式和单个 object 参数的扩展形式之间将产生潜在的多义性。产生此多义性的原因是 object[] 本身可隐式转换为 object。然而，此多义性并不会造成任何问题，这是因为可以在需要时通过插入一个强制转换来解决它。

在以下示例中：

using System;

class Test

{

static void F(params object[] args) {

foreach (object o in args) {

Console.Write(o.GetType().FullName);

Console.Write(" ");

}

Console.WriteLine();

}

static void Main() {

object[] a = {1, "Hello", 123.456};

object o = a;

F(a);

F((object)a);

F(o);

F((object[])o);

}

}

产生输出

System.Int32 System.String System.Double

System.Object[]

System.Object[]

System.Int32 System.String System.Double

在 F 的第一次和最后一次调用中，F 的正常形式是适用的，这是因为存在一个从自变量类型到参数类型的转换（这里，其实两者都是 object[] 类型）。因此，重载决策选择 F 的正常形式，而且将该参数作为常规的值参数传递。在第二次和第三次调用中，F 的正常形式不适用，这是因为不存在从自变量类型到参数类型的转换（类型 object 不能隐式转换为类型 object[]）。但是，F 的扩展形式是适用的，因此重载决策选择它。因此，这两个调用都创建了一个具有单个元素的、类型为 object[] 的数组，并且用给定的参数值（它本身是对一个 object[] 的引用）初始化该数组的唯一元素。


10.5.2静态和实例方法.1678735

10.5.2 静态和实例方法

若一个方法声明中含有 static 修饰符，则称该方法为静态方法。若其中没有 static 修饰符时，则称该方法为实例方法。

静态方法不对特定实例进行操作，在静态方法中引用 this 是编译时错误。

实例方法对类的某个给定的实例进行操作，而且可以用 this（第 7.5.7 节）来访问该实例。

在 E.M 形式的成员访问（第 7.5.4 节）中引用一个方法时，如果 M 是静态方法，则 E 必须表示含有 M 的一个类型，而如果 M 是实例方法，则 E 必须表示含有 M 的类型的一个实例。

（第 10.2.5 节）对静态成员和实例成员之间的差异进行了进一步讨论。


10.5.3虚拟方法.1680089

10.5.3 虚拟方法

若一个实例方法的声明中含有 virtual 修饰符，则称该方法为虚拟方法。若其中没有 virtual 修饰符，则称该方法为非虚拟方法。

非虚拟方法的实现是不会变的：无论是在声明它的类的实例上调用该方法还是在派生类的实例上调用，实现都是相同的。与此相反，一个虚拟方法的实现可以由派生类取代。取代所继承的虚拟方法的实现的过程称为重写该方法（第 10.5.4 节）。

在一个虚拟方法调用中，该调用所涉及的那个实例的运行时类型确定了要被调用的究竟是该方法的哪一个实现。在非虚拟方法调用中，相关的实例的编译时类型是决定性因素。准确地说，当在具有编译时类型 C 和运行时类型 R 的实例（其中 R 为 C 或者从 C 派生的类）上用参数列表 A 调用名为 N 的方法时，调用按下述规则处理：

· 首先，将重载决策应用于 C、N 和 A，以从在 C 中声明的和由 C 继承的方法集中选择一个特定的方法 M。第 7.5.5.1 节对此进行了描述。

· 然后，如果 M 为非虚拟方法，则调用 M。

· 否则（M 为虚拟方法），就会调用就 R 而言 M 的派生程度最大的那个实现。

对于在一个类中声明的或者由类继承的每个虚拟方法，存在一个就该类而言的派生程度最大的实现。就类 R 而言虚拟方法 M 的派生度最大的实现按下述规则确定：

· 如果 R 中含有关于 M 的 virtual 声明，则这是 M 的派生程度最大的实现。

· 否则，如果 R 中含有关于 M 的 override 声明，则这是 M 的派生程度最大的实现。

· 否则，就 R 而言 M 的派生程度最大的实现与就 R 的直接基类而言 M 的派生程度最大的实现相同。

下列实例阐释虚拟方法和非虚拟方法之间的区别：

using System;

class A

{

public void F() { Console.WriteLine("A.F"); }

public virtual void G() { Console.WriteLine("A.G"); }

}

class B: A

{

new public void F() { Console.WriteLine("B.F"); }

public override void G() { Console.WriteLine("B.G"); }

}

class Test

{

static void Main() {

B b = new B();

A a = b;

a.F();

b.F();

a.G();

b.G();

}

}

在该示例中，A 引入一个非虚拟方法 F 和一个虚拟方法 G。类 B 引入一个新的非虚拟方法 F，从而隐藏了继承的 F，并且还重写了继承的方法 G。此例产生输出：

A.F

B.F

B.G

B.G

请注意，语句 a.G() 实际调用的是 B.G 而不是 A.G。这是因为，对调用哪个实际方法实现起决定作用的是该实例的运行时类型（即 B），而不是该实例的编译时类型（即 A）。

由于一个类中声明的方法可以隐藏继承来的方法，因此同一个类中可以包含若干个具有相同签名的虚拟方法。这不会造成多义性问题，因为除派生程度最大的那个方法外，其他方法都被隐藏起来了。在下面的示例中

using System;

class A

{

public virtual void F() { Console.WriteLine("A.F"); }

}

class B: A

{

public override void F() { Console.WriteLine("B.F"); }

}

class C: B

{

new public virtual void F() { Console.WriteLine("C.F"); }

}

class D: C

{

public override void F() { Console.WriteLine("D.F"); }

}

class Test

{

static void Main() {

D d = new D();

A a = d;

B b = d;

C c = d;

a.F();

b.F();

c.F();

d.F();

}

}

C 类和 D 类均含有两个具有相同签名的虚拟方法：A 引入的虚拟方法和 C 引入的虚拟方法。但是，由 C 引入的方法隐藏了从 A 继承的方法。因此，D 中的重写声明所重写的是由 C 引入的方法，D 不可能重写由 A 引入的方法。此例产生输出：

B.F

B.F

D.F

D.F

请注意，通过访问 D 的实例（借助一个派生程度较小的类型，它的方法没有被隐藏起来），可以调用被隐藏的虚拟方法。


10.5.4重写方法.1690126

10.5.4 重写方法

若一个实例方法声明中含有 override 修饰符，则称该方法为重写方法。重写方法用相同的签名重写所继承的虚拟方法。虚拟方法声明用于引入新方法，而重写方法声明则用于使现有的继承虚拟方法专用化（通过提供该方法的新实现）。

由 override 声明所重写的那个方法称为已重写了的基方法。对于在类 C 中声明的重写方法 M，已重写了的基方法是通过检查 C 的各个基类确定的，检查的方法是：先从 C 的直接基类开始，逐个检查每个后续的直接基类，直至找到一个与 M 具有相同签名的可访问方法。为了查找已重写了的基方法，可访问方法可以这样来定义：如果一个方法是 public、是 protected、是 protected internal，或者是 internal 并且与 C 声明在同一程序中，则认为它是可访问的。

除非下列所有项对于一个重写声明皆为真，否则将会出现编译时错误：

· 可以按照上面描述的规则找到一个已重写了的基方法。

· 该已重写了的基方法是一个虚拟、抽象或重写方法。换句话说，已重写了的基方法不能是静态或非虚拟方法。

· 已重写了的基方法不是密封方法。

· 重写声明和已重写了的基方法具有相同的返回类型。

· 重写声明和已重写了的基方法具有相同的声明可访问性。换句话说，重写声明不能更改所对应的虚拟方法的可访问性。

重写声明可以使用基访问（第 7.5.8 节）访问已重写了的基方法。在下面的示例中

class A

{

int x;

public virtual void PrintFields() {

Console.WriteLine("x = {0}", x);

}

}

class B: A

{

int y;

public override void PrintFields() {

base.PrintFields();

Console.WriteLine("y = {0}", y);

}

}

在 B 中的调用 base.PrintFields() 就调用了在 A 中声明的 PrintFields 方法。基访问禁用了虚拟调用机制，它只是简单地将那个重写了的基方法视为非虚拟方法。如果把 B 中的调用改写成 ((A)this).PrintFields()，它将递归调用在 B 中声明而不是在 A 中声明的 PrintFields 方法，这是因为 PrintFields 是虚拟的，而且 ((A)this) 的运行时类型为 B。

只有在包含了 override 修饰符时，一个方法才能重写另一个方法。在所有其他情况下，声明一个与继承了的方法具有相同签名的方法只会使那个被继承的方法隐藏起来。在下面的示例中

class A

{

public virtual void F() {}

}

class B: A

{

public virtual void F() {} // Warning, hiding inherited F()

}

B 中的 F 方法不包含 override 修饰符，因此不重写 A 中的 F 方法。相反，B 中的 F 方法隐藏 A 中的方法，并且由于该声明中没有包含 new 修饰符，从而会导致一个警告。

在下面的示例中

class A

{

public virtual void F() {}

}

class B: A

{

new private void F() {} // Hides A.F within B

}

class C: B

{

public override void F() {} // Ok, overrides A.F

}

B 中的 F 方法隐藏从 A 继承的虚拟 F 方法。由于 B 中的新 F 具有私有访问权限，它的范围只包括 B 的类体而没有延伸到 C。所以，允许 C 中的 F 声明重写从 A 继承的 F。


10.5.5密封方法.1697832

10.5.5 密封方法

当实例方法声明包含 sealed 修饰符时，称该方法为密封方法。如果实例方法声明包含 sealed 修饰符，则它必须也包含 override 修饰符。使用 sealed 修饰符可以防止派生类进一步重写该方法。

在以下示例中：

using System;

class A

{

public virtual void F() {

Console.WriteLine("A.F");

}

public virtual void G() {

Console.WriteLine("A.G");

}

}

class B: A

{

sealed override public void F() {

Console.WriteLine("B.F");

}

override public void G() {

Console.WriteLine("B.G");

}

}

class C: B

{

override public void G() {

Console.WriteLine("C.G");

}

}

类 B 提供两个重写方法：具有 sealed 修饰符的 F 方法和不具有此修饰符的 G 方法。通过使用 sealed 修饰符，B 就可以防止 C 进一步重写 F。


10.5.6抽象方法.1700877

10.5.6 抽象方法

当实例方法声明包含 abstract 修饰符时，称该方法为抽象方法。虽然抽象方法同时隐含为虚拟方法，但是它不能有 virtual 修饰符。

抽象方法声明引入一个新的虚拟方法，但不提供该方法的实现。相反，非抽象类的派生类需要重写该方法以提供它们自己的实现。由于抽象方法不提供任何实际实现，因此抽象方法的方法体只包含一个分号。

只允许在抽象类（第 10.1.1.1 节）中使用抽象方法声明。

在下面的示例中

public abstract class Shape

{

public abstract void Paint(Graphics g, Rectangle r);

}

public class Ellipse: Shape

{

public override void Paint(Graphics g, Rectangle r) {

g.DrawEllipse(r);

}

}

public class Box: Shape

{

public override void Paint(Graphics g, Rectangle r) {

g.DrawRect(r);

}

}

Shape 类定义了一个可以绘制自身的几何形状对象的抽象概念。Paint 方法是抽象的，这是因为没有有意义的默认实现。Ellipse 和 Box 类是具体的 Shape 实现。由于这些类是非抽象的，因此要求它们重写 Paint 方法并提供实际实现。

若一个基访问（第 7.5.8 节）引用的是一个抽象方法，则会导致一个编译时错误。在下面的示例中

abstract class A

{

public abstract void F();

}

class B: A

{

public override void F() {

base.F(); // Error, base.F is abstract

}

}

对 base.F() 调用导致了编译时错误，原因是它引用了抽象方法。

在一个抽象方法声明中可以重写虚拟方法。这使一个抽象类可以强制从它的派生类重新实现该方法，并使该方法的原始实现不再可用。在下面的示例中

using System;

class A

{

public virtual void F() {

Console.WriteLine("A.F");

}

}

abstract class B: A

{

public abstract override void F();

}

class C: B

{

public override void F() {

Console.WriteLine("C.F");

}

}

类 A 声明一个虚拟方法，类 B 用一个抽象方法重写此方法，而类 C 重写该抽象方法以提供它自己的实现。


10.5.7外部方法.1706225

10.5.7 外部方法

当方法声明包含 extern 修饰符时，称该方法为外部方法。外部方法是在外部实现的，编程语言通常是使用 C# 以外的语言。由于外部方法声明不提供任何实际实现，因此外部方法的方法体只包含一个分号。

extern 修饰符通常与 DllImport 属性（第 10.5.1 节）一起使用，从而使外部方法可以由 DLL（动态链接库）实现。执行环境可以支持其他用来提供外部方法实现的机制。

当外部方法包含 DllImport 属性时，该方法声明必须同时包含一个 static 修饰符。此示例说明 extern 修饰符和 DllImport 属性的使用：

using System.Text;

using System.Security.Permissions;

using System.Runtime.InteropServices;

class Path

{

[DllImport("kernel32", SetLastError=true)]

static extern bool CreateDirectory(string name, SecurityAttribute sa);

[DllImport("kernel32", SetLastError=true)]

static extern bool RemoveDirectory(string name);

[DllImport("kernel32", SetLastError=true)]

static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);

[DllImport("kernel32", SetLastError=true)]

static extern bool SetCurrentDirectory(string name);

}


10.5.8方法体.1708663

10.5.8 方法体

方法声明中的方法体或者由一个块，或者由一个分号组成。

抽象和外部方法声明不提供方法实现，所以它们的方法体只包含一个分号。对于任何其他方法，方法体是一个块（第 8.2 节），它包含了在调用该方法时应执行的语句。

当方法的返回类型为 void 时，不允许该方法体中的 return 语句（第 8.9.4 节）指定表达式。如果一个 void 方法的方法体的执行正常完成（即控制自方法体的结尾离开），则该方法只是返回到它的调用方。

当方法的返回类型不是 void 时，该方法体中的每个 return 语句都必须指定一个可隐式转换为返回类型的类型的表达式。对于一个返回值的方法，其方法体的结束点必须是不可到达的。换句话说，在执行返回值的方法中，不允许控制自方法体的结尾离开。

在下面的示例中

class A

{

public int F() {} // Error, return value required

public int G() {

return 1;

}

public int H(bool b) {

if (b) {

return 1;

}

else {

return 0;

}

}

}

返回值的 F 方法导致编译时错误，原因是控制可以超出方法体的结尾。G 和 H 方法是正确的，这是因为所有可能的执行路径都以一个指定返回值的 return 语句结束。


10.5.9方法重载.1711512

Powered By Gavin http://www.CSharpWeb.com

10.5.9 方法重载

第 7.4.2 节对方法重载决策规则进行了描述。


10.6属性.1711902

10.6 属性

属性是一种用于访问对象或类的特性的成员。属性的示例包括字符串的长度、字体的大小、窗口的标题、客户的名称，等等。属性是字段的自然扩展，此两者都是具有关联类型的命名成员，而且访问字段和属性的语法是相同的。然而，与字段不同，属性不表示存储位置。相反，属性有访问器，这些访问器指定在它们的值被读取或写入时需执行的语句。因此属性提供了一种机制，它把读取和写入对象的某些特性与一些操作关联起来；甚至，它们还可以对此类特性进行计算。

属性是使用属性声明声明的：

property-declaration:（属性声明：）

attributes opt property-modifiers opt type member-name { accessor-declarations }（属性可选 属性修饰符可选 类型 成员名 { 访问器声明 }）

property-modifiers:（属性修饰符：）

property-modifier（属性修饰符）
property-modifiers property-modifier（属性修饰符 属性修饰符）

property-modifier:（属性修饰符：）

new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

member-name:（成员名：）

identifier（标识符）
interface-type . identifier（接口类型 . 标识符）

属性声明可包含一组特性（第 17 节）和四个访问修饰符（第 10.2.3 节）的有效组合，还可包含 new（第 10.2.2 节）、static（第 10.5.2 节）、virtual（第 10.5.3 节）、override（第 10.5.4 节）、sealed（第 10.5.5 节）、abstract（第 10.5.6 节）以及 extern（第 10.5.7 节）修饰符。

关于有效的修饰符组合，属性声明与方法声明（第 10.5 节）遵循相同的规则。

属性声明中的类型用于指定该声明所引入的属性的类型，而成员名则指定该属性的名称。除非该属性是一个显式的接口成员实现，否则成员名就只是一个标识符。对于显式接口成员实现（第 13.4.1 节），成员名由一个接口类型，后跟一个“.”和一个标识符来组成。

属性的类型必须至少与属性本身具有同样的可访问性（第 3.5.4 节）。

访问器声明（必须括在“{”和“}”标记中）声明属性的访问器（第 10.6.2 节）。访问器指定与属性的读取和写入相关联的可执行语句。

虽然访问属性的语法与访问字段的语法相同，但是属性并不归类为变量。因此，不能将属性作为 ref 或 out 参数传递。

属性声明包含 extern 修饰符时，称该属性为外部属性。因为外部属性声明不提供任何实际的实现，所以它的每个访问器声明都仅含有一个分号。


10.6.1静态和实例属性.1718680

10.6.1 静态和实例属性

当属性声明包含 static 修饰符时，称该属性为静态属性。当不存在 static 修饰符时，称该属性为实例属性。

静态属性不与特定实例相关联，因此在静态属性的访问器内引用 this 是编译时错误。

实例属性与类的一个给定实例相关联，并且该实例可以在属性的访问器内用 this（第 7.5.7 节）来访问。

在 E.M 形式的成员访问（第 7.5.4 节）中引用属性时，如果 M 是静态属性，则 E 必须表示包含 M 的类型，如果 M 是实例属性，则 E 必须表示包含 M 的类型的一个实例。

（第 10.2.5 节）对静态成员和实例成员之间的差异进行了进一步讨论。


10.6.2访问器.1720036

10.6.2 访问器

属性的访问器声明指定与读取和写入该属性相关联的可执行语句。

accessor-declarations:（访问器声明：）

get-accessor-declaration set-accessor-declaration opt（get 访问器声明 set 访问器声明可选）
set-accessor-declaration get-accessor-declaration opt（set 访问器声明 get 访问器声明可选）

get-accessor-declaration:（get 访问器声明：）

attributes opt get accessor-body（属性可选 get 访问器体）

set-accessor-declaration:（set 访问器声明：）

attributes opt set accessor-body（属性可选 set 访问器体）

accessor-body:（访问器体：）

block（块）
;

访问器声明由一个 get 访问器声明或一个 set 访问器声明，或者两者一起组成。每个访问器声明都包含标记 get 或 set，后跟一个访问器体。对于 abstract 和 extern 属性，每个指定访问器的访问器体只是一个分号。对于所有非抽象、非外部属性的访问器，访问器体是一个块，它指定调用该访问器时需执行的语句。

get 访问器相当于一个具有属性类型返回值的无参数方法。除了作为赋值的目标，当在表达式中引用属性时，将调用该属性的 get 访问器以计算该属性的值（第 7.1.1 节）。get 访问器的体必须遵循第 10.5.8 节中所描述的适用于返回值方法的规则。具体说来，get 访问器的体中的所有 return 语句都必须指定一个可隐式转换为属性类型的表达式。另外，get 访问器的结束点必须是不可到达的。

set 访问器相当于一个具有单个属性类型值参数和 void 返回类型的方法。set 访问器的隐式参数始终命名为 value。当一个属性作为赋值（第 7.13 节）的目标，或者作为 ++ 或 -- 运算符（第 7.5.9 节），（第 7.6.5 节）的操作数被引用时，就会调用 set 访问器，所传递的参数（其值为赋值右边的值或者 ++ 或 -- 运算符的操作数）将提供新值（第 7.13.1 节）。set 访问器的体必须遵循第 10.5.8 节中所描述的适用于 void 方法的规则。具体说来，不允许 set 访问器体中的 return 语句指定表达式。由于 set 访问器隐式具有名为 value 的参数，因此在 set 访问器中，若在局部变量或常数声明中出现该名称，则会导致一个编译时错误。

根据 get 和 set 访问器是否存在，一个属性可按下列规则分类：

· 同时包含 get 访问器和 set 访问器的属性称为读写属性。

· 只具有 get 访问器的属性称为只读属性。将只读属性作为赋值目标会导致编译时错误。

· 只具有 set 访问器的属性称为只写属性。除了作为赋值的目标外，在表达式中引用只写属性是编译时错误。

在下面的示例中

public class Button: Control

{

private string caption;

public string Caption {

get {

return caption;

}

set {

if (caption != value) {

caption = value;

Repaint();

}

}

}

public override void Paint(Graphics g, Rectangle r) {

// Painting code goes here

}

}

Button 控件声明了一个公共 Caption 属性。Caption 属性的 get 访问器返回存储在私有 caption 字段中的字符串。set 访问器检查新值是否与当前值不同，如果新值与当前值不同，它将存储新值并重新绘制控件。属性经常遵循上述的模式：get 访问器只返回一个存储在私有字段中的值，而 set 访问器则用于修改该私有字段，然后再执行一些必要的操作，以完全更新所涉及的对象的状态。

使用上边给定的 Button 类，下面是一个使用 Caption 属性的示例：

Button okButton = new Button();

okButton.Caption = "OK"; // Invokes set accessor

string s = okButton.Caption; // Invokes get accessor

此处，通过向属性赋值调用 set 访问器，而 get 访问器则通过在表达式中引用该属性来调用。

属性的 get 和 set 访问器都不是独立的成员，也不能单独地声明一个属性的访问器。因此，读写属性的两个访问器不可能具有不同的可访问性。在以下示例中：

class A

{

private string name;

public string Name { // Error, duplicate member name

get { return name; }

}

public string Name { // Error, duplicate member name

set { name = value; }

}

}

不是声明单个读写属性。相反，它声明了两个同名的属性，一个是只读的，一个是只写的。由于在同一个类中声明的两个成员不能同名，此示例将导致发生一个编译时错误。

当在一个派生类中用与某个所继承的属性相同的名称声明一个新属性时，该派生属性将会隐藏所继承的属性（同时在读取和写入方面）。在下面的示例中

class A

{

public int P {

set {...}

}

}

class B: A

{

new public int P {

get {...}

}

}

B 中的 P 属性同时在读取和写入方面隐藏 A 中的 P 属性。因此，在下列语句中

B b = new B();

b.P = 1; // Error, B.P is read-only

((A)b).P = 1; // Ok, reference to A.P

向 b.P 赋值导致编译时出现错误，原因是 B 中的只读属性 P 隐藏了 A 中的只写属性 P。但是，仍可以使用强制转换来访问那个被隐藏了的 P 属性。

与公共字段不同，属性在对象的内部状态和它的公共接口之间提供了一种隔离手段。请看此示例：

class Label

{

private int x, y;

private string caption;

public Label(int x, int y, string caption) {

this.x = x;

this.y = y;

this.caption = caption;

}

public int X {

get { return x; }

}

public int Y {

get { return y; }

}

public Point Location {

get { return new Point(x, y); }

}

public string Caption {

get { return caption; }

}

}

此处，Label 类使用两个 int 字段 x 和 y 存储它的位置。该位置同时采用两种方式公开：X 和 Y 属性；Point 类型的 Location 属性。如果在 Label 的未来版本中采用 Point 结构在内部存储此位置更为方便，则可以不影响类的公共接口就完成更改：

class Label

{

private Point location;

private string caption;

public Label(int x, int y, string caption) {

this.location = new Point(x, y);

this.caption = caption;

}

public int X {

get { return location.x; }

}

public int Y {

get { return location.y; }

}

public Point Location {

get { return location; }

}

public string Caption {

get { return caption; }

}

}

相反，如果 x 和 y 是 public readonly 字段，则对 Label 类进行上述更改是不可能的。

通过属性公开状态并不一定比直接公开字段效率低。具体说来，当属性是非虚拟的且只包含少量代码时，执行环境可能会用访问器的实际代码替换对访问器进行的调用。此过程称为内联，它使属性访问与字段访问一样高效，而且仍保留了属性的更高灵活性。

由于调用 get 访问器在概念上等效于读取字段的值，使 get 访问器具有可见的副作用被认为是不好的编程风格。在下面的示例中

class Counter

{

private int next;

public int Next {

get { return next++; }

}

}

Next 属性的值取决于该属性以前被访问的次数。因此，访问此属性就会产生可见的副作用，而此属性应当作为一个方法实现。

get 访问器的“无副作用”约定并不意味着 get 访问器应当始终被编写为只返回存储在字段中的值。事实上，get 访问器经常通过访问多个字段或调用方法以计算属性的值。但是，正确设计的 get 访问器不会执行任何导致对象的状态发生可见变化的操作。

属性还可用于将某个资源的初始化延迟到第一次引用该资源时执行。例如：

using System.IO;

public class Console

{

private static TextReader reader;

private static TextWriter writer;

private static TextWriter error;

public static TextReader In {

get {

if (reader == null) {

reader = new StreamReader(Console.OpenStandardInput());

}

return reader;

}

}

public static TextWriter Out {

get {

if (writer == null) {

writer = new StreamWriter(Console.OpenStandardOutput());

}

return writer;

}

}

public static TextWriter Error {

get {

if (error == null) {

error = new StreamWriter(Console.OpenStandardError());

}

return error;

}

}

}

Console 类包含三个属性：In、Out 和 Error，它们分别表示三种标准的设备：输入、输出和错误信息报告。通过将这些成员作为属性公开，Console 类可以将它们的初始化延迟到它们被实际使用时。例如，在下列示例中，仅在第一次引用 Out 属性时

Console.Out.WriteLine("hello, world");

才创建输出设备的基础 TextWriter。但是，如果应用程序不引用 In 和 Error 属性，则不会创建这些设备的任何对象。


10.6.3虚拟、密封、重写和抽象访问器.1743777

10.6.3 虚拟、密封、重写和抽象访问器

virtual 属性声明指定属性的访问器是虚拟的。virtual 修饰符适用于读写属性的两个访问器（读写属性的访问器不可能只有一个是虚拟的）。

abstract 属性声明指定属性的访问器是虚拟的，而且没有提供访问器的实际实现。另外，非抽象派生类还要求通过重写属性以提供它们自己的访问器实现。由于抽象属性声明的访问器不提供实际实现，因此它的访问器体只包含一个分号。

同时包含 abstract 和 override 修饰符的属性声明表示属性是抽象的并且重写一个基属性。此类属性的访问器也是抽象的。

只能在抽象类（第 10.1.1.1 节）中使用抽象属性声明。通过用一个含有 override 指令的属性声明，可以在派生类中来重写被继承的虚拟属性的访问器。这称为重写属性声明。重写属性声明并不声明新属性。相反，它只是对现有虚拟属性的访问器的实现进行专用化。

重写属性声明必须指定与所继承的属性完全相同的可访问性修饰符、类型和名称。如果被继承的属性只有单个访问器（即该属性是只读或只写的），则重写属性必须只包含该访问器。如果被继承的属性同时包含两个访问器（即该属性是读写的），则重写属性既可以仅包含其中任一个访问器，也可同时包含两个访问器。

重写属性声明可以包含 sealed 修饰符。此修饰符的使用可以防止派生类进一步重写该属性。密封属性的访问器也是密封的。

除了在声明和调用语法中的差异，虚拟、密封、重写和抽象访问器与虚拟、密封、重写和抽象方法具有完全相同的行为。具体说来，第 10.5.3 节、第 10.5.4 节、第 10.5.5 节和第 10.5.6 节中描述的规则都适用，就好像访问器是相应形式的方法一样：

· get 访问器相当于一个无参数方法，该方法具有属性类型的返回值以及与包含属性相同的修饰符。

· set 访问器相当于一个方法，该方法具有单个属性类型的值参数、void 返回类型以及与包含属性相同的修饰符。

在下面的示例中

abstract class A

{

int y;

public virtual int X {

get { return 0; }

}

public virtual int Y {

get { return y; }

set { y = value; }

}

public abstract int Z { get; set; }

}

X 是虚拟只读属性，Y 是虚拟读写属性，而 Z 是抽象读写属性。由于 Z 是抽象的，所以包含它的类 A 也必须被声明为抽象的。

下面显示了一个从 A 派生的类：

class B: A

{

int z;

public override int X {

get { return base.X + 1; }

}

public override int Y {

set { base.Y = value < 0? 0: value; }

}

public override int Z {

get { return z; }

set { z = value; }

}

}

此处，X、Y 和 Z 的声明是重写属性声明。每个属性声明都与它们所继承的属性的可访问性修饰符、类型和名称完全匹配。X 的 get 访问器和 Y 的 set 访问器使用 base 关键字来访问所继承的访问器。Z 的声明重写了两个抽象访问器，因此在 B 中不再有抽象的函数成员，B 也可以是非抽象类。


10.7事件.1749833

10.7 事件

事件是一种使对象或类能够提供通知的成员。客户端可以通过提供事件处理程序为相应的事件添加可执行代码。

事件是使用事件声明来声明的：

event-declaration:（事件声明：）

attributes opt event-modifiers opt event type variable-declarators ;（属性可选 事件修饰符可选 event 类型 变量声明符 ;）
attributes opt event-modifiers opt event type member-name { event-accessor-declarations }（属性可选 事件修饰符可选 event 类型 成员名 { 事件访问器声明 }）

event-modifiers:（事件修饰符：）

event-modifier（事件修饰符）
event-modifiers event-modifier（事件修饰符 事件修饰符）

event-modifier:（事件修饰符：）

new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

event-accessor-declarations:（事件访问器声明：）

add-accessor-declaration remove-accessor-declaration（添加访问器声明 移除访问器声明）
remove-accessor-declaration add-accessor-declaration（移除访问器声明 添加访问器声明）

add-accessor-declaration:（添加访问器声明：）

attributes opt add block（属性可选 add 块）

remove-accessor-declaration:（移除访问器声明：）

attributes opt remove block（属性可选 remove 块）

事件声明可包含一组属性（第 17 节）和四个访问修饰符（第 10.2.3 节）的有效组合、new（第 10.2.2 节）、static（第 10.5.2 节）、virtual（第 10.5.3 节）、override（第 10.5.4 节）、sealed（第 10.5.5 节）、abstract（第 10.5.6 节）以及 extern（第 10.5.7 节）修饰符。

关于有效的修饰符组合，事件声明与方法声明（第 10.5 节）遵循相同的规则。

事件声明的类型必须是委托类型（第 4.2 节），而该委托类型必须至少具有与事件本身一样的可访问性（第 3.5.4 节）。

事件声明中可以包含事件访问器声明。但是，如果声明中没有包含事件访问器声明，对于非外部、非抽象事件，编译器将自动提供它们（第 3.7.1 节）；对于外部事件，访问器由外部提供。

一个省略了事件访问器声明的事件声明用于定义一个或多个事件（每个变量声明符各表示一个事件）。一个事件声明中的属性和修饰符适用于所有由该事件声明所声明的成员。

若一个事件声明既包含 abstract 修饰符又包含以括号分隔的事件访问器声明，则会导致一个编译时错误。

当事件声明包含 extern 修饰符时，称该事件为外部事件。因为外部事件声明不提供任何实际的实现，所以在一个外部事件声明中既包含 extern 修饰符又包含事件访问器声明是错误的。

事件可用作 += 和 -= 运算符（第 7.13.3 节）左边的操作数。这些运算符分别用于将事件处理程序添加到所涉及的事件或从该事件中移除事件处理程序，而该事件的访问修饰符用于控制允许这类运算的上下文。

由于 += 和 -= 是仅有的能够在声明了某个事件的类型的外部对该事件进行的操作，因此，外部代码可以为一个事件添加和移除处理程序，但是不能以其他任何方式来获取或修改基础的事件处理程序列表。

在 x += y 或 x -= y 形式的运算中，当 x 是一个事件，而且该引用发生在声明了 x 事件的类型之外，则这种运算结果的类型为 void（这正好与该运算的实际效果相反，它用于给 x 赋值，应该具有 x 所属的类型）。此规则能够禁止外部代码以间接方式来检查一个事件的基础委托。

以下示例显示如何将事件处理程序添加到 Button 类的实例：

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control

{

public event EventHandler Click;

}

public class LoginDialog: Form

{

Button OkButton;

Button CancelButton;

public LoginDialog() {

OkButton = new Button(...);

OkButton.Click += new EventHandler(OkButtonClick);

CancelButton = new Button(...);

CancelButton.Click += new EventHandler(CancelButtonClick);

}

void OkButtonClick(object sender, EventArgs e) {

// Handle OkButton.Click event

}

void CancelButtonClick(object sender, EventArgs e) {

// Handle CancelButton.Click event

}

}

此处，LoginDialog 的实例构造函数创建两个 Button 实例并将事件处理程序附加到 Click 事件。


10.7.1类似字段的事件.1763108

10.7.1 类似字段的事件

在包含事件声明的类或结构的程序文本内，某些事件可以像字段一样使用。若要以这样的方式使用，事件不能是 abstract 或 extern，而且不能显式包含事件访问器声明。此类事件可以用在任何允许使用字段的上下文中。该字段含有一个委托（第 15 节），它引用已添加到相应事件的事件处理程序列表。如果尚未添加任何事件处理程序，则该字段包含 null。

在下面的示例中

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control

{

public event EventHandler Click;

protected void OnClick(EventArgs e) {

if (Click != null) Click(this, e);

}

public void Reset() {

Click = null;

}

}

Click 在 Button 类中用作一个字段。如上例所示，可以在委托调用表达式中检查、修改和使用字段。Button 类中的 OnClick 方法用于“引发”一个 Click 事件。“引发一个事件”与“调用一个由该事件表示的委托”完全等效，因此没有用于引发事件的特殊语言构造。请注意，在委托调用之前有一个检查，以确保该委托不是 null。

在 Button 类的声明外，Click 成员只能用在 += 和 -= 运算符的左边，如

b.Click += new EventHandler(...);

将一个委托追加到 Click 事件的调用列表，而

b.Click -= new EventHandler(...);

则从 Click 事件的调用列表中移除一个委托。

当编译一个类似字段的事件时，编译器会自动创建一个存储区来存放相关的委托，并为事件创建相应的访问器以向委托字段中添加或移除事件处理程序。为了线程安全，添加或移除操作需在为实例事件的包含对象加锁（第 8.12 节）的情况下进行，或者在为静态事件的类型对象（第 7.5.11 节）加锁的情况下进行。

因此，下列形式的实例事件声明：

class X {

public event D Ev;

}

可以编译为如下语句：

class X {

private D __Ev; // field to hold the delegate

public event D Ev {

add {

lock(this) { __Ev = __Ev + value; }

}

remove {

lock(this) { __Ev = __Ev - value; }

}

}

}

在类 X 中，对 Ev 的引用在编译时改为引用隐藏字段 __Ev。名称“__Ev”是任意的；隐藏字段可以具有任何名称或根本没有名称。

同样，如下形式的静态事件声明：

class X {

public static event D Ev;

可以编译为如下语句：

class X {

private static D __Ev; // field to hold the delegate

public static event D Ev {

add {

lock(typeof(X)) { __Ev = __Ev + value; }

}

remove {

lock(typeof(X)) { __Ev = __Ev - value; }

}

}

}


10.7.2事件访问器.1769185

10.7.2 事件访问器

事件声明通常省略事件访问器声明，如上面的 Button 示例中所示。但会有一些特殊情况，例如，为每个事件设置一个字段所造成的内存开销，有时会变得不可接受。在这种情况下，可以在类中使用事件访问器声明，并采用专用机制来存储事件处理程序列表。

事件的事件访问器声明指定与添加和移除事件处理程序相关联的可执行语句。

访问器声明由一个添加访问器声明和一个移除访问器声明组成。每个访问器声明包含标记 add 或 remove，后跟一个块。与添加访问器声明相关联的块指定添加事件处理程序时要执行的语句，而与移除访问器声明相关联的块指定移除事件处理程序时要执行的语句。

每个添加访问器声明和移除访问器声明相当于一个方法，它具有一个属于事件类型的值参数并且其返回类型为 void。事件访问器的隐式参数名为 value。当事件用在事件赋值中时，就会调用适当的事件访问器。具体说来，如果赋值运算符为 +=，则使用添加访问器，而如果赋值运算符为 -=，则使用移除访问器。在两种情况下，赋值运算符的右操作数都用作事件访问器的参数。添加访问器声明或移除访问器声明的块必须遵循第 10.5.8 节中所描述的适用于 void 方法的规则。具体说来，不允许此类块中的 return 语句指定表达式。

由于事件访问器隐式具有一个名为 value 的参数，因此在事件访问器中声明的局部变量或常数若使用该名称，就会导致一个编译时错误。

在下面的示例中

class Control: Component

{

// Unique keys for events

static readonly object mouseDownEventKey = new object();

static readonly object mouseUpEventKey = new object();

// Return event handler associated with key

protected Delegate GetEventHandler(object key) {...}

// Add event handler associated with key

protected void AddEventHandler(object key, Delegate handler) {...}

// Remove event handler associated with key

protected void RemoveEventHandler(object key, Delegate handler) {...}

// MouseDown event

public event MouseEventHandler MouseDown {

add { AddEventHandler(mouseDownEventKey, value); }

remove { RemoveEventHandler(mouseDownEventKey, value); }

}

// MouseUp event

public event MouseEventHandler MouseUp {

add { AddEventHandler(mouseUpEventKey, value); }

remove { RemoveEventHandler(mouseUpEventKey, value); }

}

// Invoke the MouseUp event

protected void OnMouseUp(MouseEventArgs args) {

MouseEventHandler handler;

handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);

if (handler != null)

handler(this, args);

}

}

Control 类为事件实现了一个内部存储机制。AddEventHandler 方法将委托值与键关联，GetEventHandler 方法返回当前与键关联的委托，而 RemoveEventHandler 方法将移除一个委托使它不再成为指定事件的一个事件处理程序。可以推断：在这样设计的基础存储机制下，当一个键所关联的委托值为 null 时，不会有存储开销，从而使未处理的事件不占任何存储空间。


10.7.3静态和实例事件.1774714

10.7.3 静态和实例事件

当事件声明包含 static 修饰符时，称该事件为静态事件。当不存在 static 修饰符时，称该事件为实例事件。

静态事件不和特定实例关联，因此在静态事件的访问器中引用 this 是编译时错误。

实例事件与类的给定实例关联，此实例在该事件的访问器中可以用 this（第 7.5.7 节）来访问。

在 E.M 形式的成员访问（第 7.5.4 节）中引用事件时，如果 M 为静态事件，则 E 必须表示包含 M 的类型，如果 M 为实例事件，则 E 必须表示包含 M 的类型的一个实例。

（第 10.2.5 节）对静态成员和实例成员之间的差异进行了进一步讨论。


10.7.4虚拟、密封、重写和抽象访问器.1776040

10.7.4 虚拟、密封、重写和抽象访问器

virtual 事件声明指定事件的访问器是虚拟的。virtual 修饰符适用于事件的两个访问器。

abstract 事件声明指定事件的访问器是虚拟的，但是不提供这些访问器的实际实现。而且，非抽象派生类需要通过重写事件来提供它们自己的访问器实现。由于抽象事件声明的访问器不提供任何实际实现，因此它的访问器体只包含一个分号。

同时包含 abstract 和 override 修饰符的事件声明指定该事件是抽象的并重写一个基事件。此类事件的访问器也是抽象的。

只允许在抽象类（第 10.1.1.1 节）中使用抽象事件声明。

一个虚拟事件的访问器可以在相关的派生类中用一个含有 override 修饰符的事件声明来进行重写。这称为重写事件声明。重写事件声明不声明新事件。实际上，它只是专用化了现有虚拟事件的访问器的实现。

重写事件声明必须采用与被重写事件完全相同的可访问性修饰符、类型和名称。

重写事件声明可以包含 sealed 修饰符。使用此修饰符可以防止相关的派生类进一步重写该事件。密封事件的访问器也是密封的。

重写事件声明包含 new 修饰符是编译时错误。

除了在声明和调用语法中的差异，虚拟、密封、重写和抽象访问器与虚拟、密封、重写和抽象方法具有完全相同的行为。准确地说，第 10.5.3 节、第 10.5.4 节、第 10.5.5 节和第 10.5.6 节中描述的规则仍然适用，就好像访问器是相应形式的方法一样。每个访问器都对应于一个方法，它只有一个属于所涉及的事件类型的值参数、返回类型为void，且具有与该事件相同的修饰符。


10.8索引器.1778079

10.8 索引器

索引器是这样一个成员：它使对象能够用与数组相同的方式进行索引。索引器是使用索引器声明来声明的：

indexer-declaration:（索引器声明：）

attributes opt indexer-modifiers opt indexer-declarator { accessor-declarations }（属性可选 索引器修饰符可选 索引器声明符 { 访问器声明 }）

indexer-modifiers:（索引器修饰符：）

indexer-modifier（索引器修饰符）
indexer-modifiers indexer-modifier（索引器修饰符 索引器修饰符）

indexer-modifier:（索引器修饰符：）

new
public
protected
internal
private
virtual
sealed
override
abstract
extern

indexer-declarator:（索引器声明符：）

type this [ formal-parameter-list ]（类型 this [ 形参表 ]）
type interface-type . this [ formal-parameter-list ]（类型 接口类型 . this [ 形参表 ]）

索引器声明可包含一组属性（第 17 节）和四个访问修饰符（第 10.2.3 节）的有效组合，还可包含 new（第 10.2.2 节）、virtual（第 10.5.3 节）、override（第 10.5.4 节）、sealed（第 10.5.5 节）、abstract（第 10.5.6 节）以及 extern（第 10.5.7 节）修饰符。

关于有效的修饰符组合，索引器声明与方法声明（第 10.5 节）遵循相同的规则（唯一的例外是：在索引器声明中不允许使用静态修饰符）。

修饰符 virtual、override 和 abstract 是互相排斥的，但有一种情况除外。abstract 和 override 修饰符可以一起使用以便抽象索引器可以重写虚拟索引器。

索引器声明的类型用于指定由该声明引入的索引器的元素类型。除非索引器是一个显式接口成员的实现，否则该类型后要跟一个关键字 this。而对于显式接口成员的实现，该类型后要先跟一个接口类型、一个“.”，再跟一个关键字 this。与其他成员不同，索引器不具有用户定义的名称。

形参表用于指定索引器的参数。索引器的形参表对应于方法的形参表（第 10.5.1 节），不同之处仅在于索引器的形参表中必须至少含有一个参数，并且不允许使用 ref 和 out 参数修饰符。

索引器的类型和在形参表中引用的每个参数的类型都必须至少具有与索引器本身相同的可访问性（第 3.5.4 节）。

访问器声明（第 10.6.2 节）（它必须被括在“{”和“}”标记内）用于声明该索引器的访问器。这些访问器用来指定与读取和写入索引器元素相关联的可执行语句。

虽然访问索引器元素的语法与访问数组元素的语法相同，但是索引器元素并不属于变量。因此，不可能将索引器元素作为 ref 或 out 参数传递。

索引器的形参表定义索引器的签名（第 3.6 节）。具体说来，索引器的签名由其形参的数量和类型组成。但索引器元素的类型和形参的名称都不是索引器签名的组成部分。

索引器的签名必须不同于在同一个类中声明的所有其他索引器的签名。

索引器和属性在概念上非常类似，但在下列方面有所区别：

· 属性由它的名称标识，而索引器由它的签名标识。

· 属性是通过简单名称（第 7.5.2 节）或是成员访问（第 7.5.4 节）来访问的，而索引器元素则是通过元素访问（第 7.5.6.2 节）来访问的。

· 属性可以是 static 成员，而索引器始终是实例成员。

· 属性的 get 访问器对应于不带参数的方法，而索引器的 get 访问器对应于与索引器具有相同的形参表的方法。

· 属性的 set 访问器对应于具有名为 value 的单个参数的方法，而索引器的 set 访问器对应于与索引器具有相同的形参表加上一个名为 value 的附加参数的方法。

· 若在索引器访问器内使用与该索引器的参数相同的名称来声明局部变量，就会导致一个编译时错误。

· 在重写属性声明中，被继承的属性是使用语法 base.P 访问的，其中 P 为属性名称。在重写索引器声明中，被继承的索引器是使用语法 base[E] 访问的，其中 E 是一个用逗号分隔的表达式列表。

除上述差异以外，所有在第 10.6.2 节和第 10.6.3 节中定义的规则都适用于索引器访问器以及属性访问器。

当索引器声明包含 extern 修饰符时，称该索引器为外部索引器。因为外部索引器声明不提供任何实际的实现，所以它的每个访问器声明都由一个分号组成。

下面的示例声明了一个 BitArray 类，该类实现了一个索引器，用于访问位数组中的单个位。

using System;

class BitArray

{

int[] bits;

int length;

public BitArray(int length) {

if (length < 0) throw new ArgumentException();

bits = new int[((length - 1) >> 5) + 1];

this.length = length;

}

public int Length {

get { return length; }

}

public bool this[int index] {

get {

if (index < 0 || index >= length) {

throw new IndexOutOfRangeException();

}

return (bits[index >> 5] & 1 << index) != 0;

}

set {

if (index < 0 || index >= length) {

throw new IndexOutOfRangeException();

}

if (value) {

bits[index >> 5] |= 1 << index;

}

else {

bits[index >> 5] &= ~(1 << index);

}

}

}

}

BitArray 类的实例所占的内存远少于相应的 bool[]（这是由于前者的每个值只占一位，而后者的每个值要占一个字节），而且，它可以执行与 bool[] 相同的操作。

下面的 CountPrimes 类使用 BitArray 和经典的“筛选”算法计算 1 和给定的最大数之间质数的数目：

class CountPrimes

{

static int Count(int max) {

BitArray flags = new BitArray(max + 1);

int count = 1;

for (int i = 2; i <= max; i++) {

if (!flags[i]) {

for (int j = i * 2; j <= max; j += i) flags[j] = true;

count++;

}

}

return count;

}

static void Main(string[] args) {

int max = int.Parse(args[0]);

int count = Count(max);

Console.WriteLine("Found {0} primes between 1 and {1}", count, max);

}

}

请注意，访问 BitArray 的元素的语法与访问 bool[] 的元素的语法完全相同。

下列示例显示一个具有带两个参数的索引器的 26 乘 10 网格类。第一个参数必须是 A–Z 范围内的大写或小写字母，而第二个参数必须是 0–9 范围内的整数。

using System;

class Grid

{

const int NumRows = 26;

const int NumCols = 10;

int[,] cells = new int[NumRows, NumCols];

public int this[char c, int col] {

get {

c = Char.ToUpper(c);

if (c < 'A' || c > 'Z') {

throw new ArgumentException();

}

if (col < 0 || col >= NumCols) {

throw new IndexOutOfRangeException();

}

return cells[c - 'A', col];

}

set {

c = Char.ToUpper(c);

if (c < 'A' || c > 'Z') {

throw new ArgumentException();

}

if (col < 0 || col >= NumCols) {

throw new IndexOutOfRangeException();

}

cells[c - 'A', col] = value;

}

}

}


10.8.1索引器重载.1800124

Powered By Gavin http://www.CSharpWeb.com

10.8.1 索引器重载

第 7.4.2 节中描述了索引器重载决策规则。


10.9运算符.1800514

10.9 运算符

运算符是一种用来定义可应用于类实例的表达式运算符的含义的成员。运算符是使用运算符声明来声明的：

operator-declaration:（运算符声明：）

attributes opt operator-modifiers operator-declarator operator-body（属性可选 运算符修饰符 运算符声明符 运算符体）

operator-modifiers:（运算符修饰符：）

operator-modifier（运算符修饰符）
operator-modifiers operator-modifier（运算符修饰符 运算符修饰符）

operator-modifier:（运算符修饰符：）

public
static
extern

operator-declarator:（运算符声明符：）

unary-operator-declarator（一元运算符声明符）
binary-operator-declarator（二元运算符声明符）
conversion-operator-declarator（转换运算符声明符）

unary-operator-declarator:（一元运算符声明符：）

type operator overloadable-unary-operator ( type identifier )（类型 operator 可重载的一元运算符 ( 类型 标识符 )）

overloadable-unary-operator: one of（可重载的一元运算符：下列之一）

+ - ! ~ ++ -- true false

binary-operator-declarator:（二元运算符声明符：）

type operator overloadable-binary-operator ( type identifier , type identifier )（类型 operator 可重载的二元运算符 ( 类型 标识符 , 类型 标识符 )）

overloadable-binary-operator: one of（可重载的二元运算符：下列之一）

+ - * / % & | ^ << >> == != > < >= <=

conversion-operator-declarator:（转换运算符声明符：）

implicit operator type ( type identifier )（implicit operator 类型 ( 类型 标识符 )）
explicit operator type ( type identifier )（explicit operator 类型 ( 类型 标识符 )）

operator-body:（运算符体：）

block（块）
;

有三个类别的可重载运算符：一元运算符（第 10.9.1 节）、二元运算符（第 10.9.2 节）和转换运算符（第 10.9.3 节）。

当运算符声明包含 extern 修饰符时，称该运算符为外部运算符。因为外部运算符不提供任何实际的实现，所以它的运算符体由一个分号组成。对于所有其他运算符，运算符体由一个块组成，它指定在调用该运算符时需要执行的语句。运算符的块必须遵循第 10.5.8 节中所描述的适用于值返回方法的规则。

下列规则适用于所有的运算符声明：

· 运算符声明必须同时包含一个 public 和一个 static 修饰符。

· 运算符的参数必须是值参数。在运算符声明中指定 ref 或 out 参数会导致编译时错误。

· 一个运算符的签名（第 10.9.1 节、第 10.9.2 节、第 10.9.3 节）必须不同于在同一类中声明的所有其他运算符的签名。

· 运算符声明中引用的所有类型必须至少具有与运算符本身一样的可访问性（第 3.5.4 节）。

· 同一修饰符在一个运算符声明中多次出现是错误的。

每个运算符类别都有附加的限制，将在下列几节中说明。

与其他成员一样，在基类中声明的运算符由派生类继承。由于运算符声明始终要求声明运算符的类或结构参与运算符的签名，因此在派生类中声明的运算符不可能隐藏在基类中声明的运算符。因此，运算符声明中永远不会要求也不允许使用 new 修饰符。

关于一元和二元运算符的其他信息可以在第 7.2 节中找到。

关于转换运算符的其他信息可以在第 6.4 节中找到。


10.9.1一元运算符.1817985

10.9.1 一元运算符

下列规则适用于一元运算符声明，其中 T 表示包含运算符声明的类或结构类型：

· 一元 +、-、! 或 ~ 运算符必须带有单个 T 类型的参数，并且可以返回任何类型。

· 一元 ++ 或 -- 运算符必须带有单个 T 类型的参数且必须返回类型 T。

· 一元 true 或 false 运算符必须带有单个 T 类型的参数并且必须返回类型 bool。

一元运算符的签名包含运算符标记（+、-、!、~、++、--、true 或 false）和单个形参的类型。返回类型不是一元运算符的签名的组成部分，形参的名称也不是。

一元运算符 true 和 false 要求成对的声明。如果类只声明了这两个运算符的其中一个而没有声明另一个，将发生编译时错误。第 7.16 节中对 true 和 false 运算符进行了进一步描述。

下列示例显示了对一个整数向量类的 operator ++ 的实现以及随后对它的使用：

public class IntVector

{

public IntVector(int length) {...}

public int Length {...} // read-only property

public int this[int index] {...} // read-write indexer

public static IntVector operator ++(IntVector iv) {

IntVector temp = new IntVector(iv.Length);

for (int i = 0; i < iv.Length; i++)

temp[i] = iv[i] + 1;

return temp;

}

}

class Test

{

static void Main() {

IntVector iv1 = new IntVector(4); // vector of 4 x 0

IntVector iv2;

iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1

iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2

}

}

请注意，此运算符方法怎样返回通过向操作数添加 1 而产生的值，就像后缀增量和减量运算符（第 7.5.9 节）以及前缀增量和减量运算符（第 7.6.5 节）一样。与在 C++ 中不同，此方法并不需要直接修改其操作数的值。实际上，修改操作数的值会违反后缀增量运算符的标准语义。


10.9.2二元运算符.1823083

10.9.2 二元运算符

二元运算符必须带两个参数，其中至少有一个必须具有声明了该运算符的类或结构类型。移位运算符（第 7.8 节）的参数被进一步限制。二元运算符可以返回任何类型。

二元运算符的签名由运算符标记（+、-、*、/、%、&、|、^、<<、>>、==、!=、>、<、>= 或 <=）和两个形参的类型组成。它本身的返回类型及形参的名称不是二元运算符签名的组成部分。

某些二元运算符要求成对地声明。对于需要要求成对地声明的运算符，若声明了其中一个，就必须对另一个作出相匹配的声明。当两个运算符声明具有相同的返回类型且各个参数具有相同的类型时，它们相匹配。下列运算符要求成对的声明：

· operator == 和 operator !=

· operator > 和 operator <

· operator >= 和 operator <=


10.9.3转换运算符.1825672

10.9.3 转换运算符

转换运算符声明引入用户定义的转换（第 6.4 节），此转换可以扩充预定义的隐式和显式转换。

包含 implicit 关键字的转换运算符声明引入用户定义的隐式转换。隐式转换可以在多种情况下发生，包括函数成员调用、强制转换表达式和赋值。第 6.1 节对此有进一步描述。

包含 explicit 关键字的转换运算符声明引入用户定义的显式转换。显式转换可以发生在强制转换表达式中，第 6.2 节中对此进行了进一步描述。

一个转换运算符从某个源类型（即该转换运算符的参数的类型）转换到一个目标类型（即该转换运算符的返回类型）。如果下列条件都为真，则允许类或结构声明一个从源类型 S 到目标类型 T 的转换：

· S 和 T 是不同的类型。

· S 和 T 中有一个属于类类型或结构类型，且包含了运算符的声明。

· S 和 T 都不是 object 或接口类型。

· T 不是 S 的基类，S 也不是 T 的基类。

从第二条规则可以推知，转换运算符必须将声明了该运算符的类或结构类型或者作为目标类型，或者作为源类型。例如，一个类或结构类型 C 可以定义从 C 到 int 和从 int 到 C 的转换，但是不能定义从 int 到 bool 的转换。

不能重新定义一个已存在的预定义转换。因此，不允许转换运算符将 object 转换为其他类型或将其他类型转换为 object，这是因为已存在一些隐式和显式转换来执行 object 和所有其他类型之间的转换。同样，转换的源类型和目标类型不能是对方的基类型，这是由于已经存在这样的转换。

用户定义的转换不能用于在接口类型和其他类型之间进行转换。具体说来，这条规定确保了在转换为接口类型时不会发生任何用户定义的转换，以及只有在被转换的对象实际上实现了指定的接口类型时，到该接口类型的转换才会成功。

转换运算符的签名由源类型和目标类型组成。（请注意，这是唯一一种其返回类型参与签名的成员形式。）转换运算符的 implicit 或 explicit 类别不是运算符签名的组成部分。因此，类或结构不能同时声明具有相同源类型和目标类型的 implicit 和 explicit 转换运算符。

一般来说，如果设计一个用户定义的隐式转换，就应当确保执行该转换时决不会引发异常，并且也决不会丢失信息。如果用户定义的转换可能导致引发异常（例如，由于源参数超出范围）或丢失信息（如放弃高序位），则该转换应该定义为显式转换。

在下面的示例中

using System;

public struct Digit

{

byte value;

public Digit(byte value) {

if (value < 0 || value > 9) throw new ArgumentException();

this.value = value;

}

public static implicit operator byte(Digit d) {

return d.value;

}

public static explicit operator Digit(byte b) {

return new Digit(b);

}

}

从 Digit 到 byte 的转换是隐式的，这是因为它永远不会引发异常或丢失信息，但是从 byte 到 Digit 的转换是显式的，这是由于 Digit 只能表示 byte 的可能值的一个子集。


10.10实例构造函数.1831422

10.10 实例构造函数

实例构造函数是实现初始化类实例所需操作的成员。实例构造函数是使用构造函数声明来声明的：

constructor-declaration:（构造函数声明：）

attributes opt constructor-modifiers opt constructor-declarator constructor-body（属性可选 构造函数修饰符可选 构造函数声明符 构造函数体）

constructor-modifiers:（构造函数修饰符：）

constructor-modifier（构造函数修饰符）
constructor-modifiers constructor-modifier（构造函数修饰符 构造函数修饰符）

constructor-modifier:（构造函数修饰符：）

public
protected
internal
private
extern

constructor-declarator:（构造函数声明符：）

identifier ( formal-parameter-list opt ) constructor-initializer opt（标识符 ( 形参表可选 ) 构造函数初始值设定项可选）

constructor-initializer:（构造函数初始值设定项：）

: base ( argument-list opt )（: base ( 参数列表可选 )）
: this ( argument-list opt )（: this ( 参数列表可选 )）

constructor-body:（构造函数体：）

block（块）
;

构造函数声明可以包含一组属性（第 17 节）、四个访问修饰符（第 10.2.3 节）的有效组合和一个 extern（第 10.5.7 节）修饰符。一个构造函数声明中同一修饰符不能多次出现。

构造函数声明符中的标识符必须是声明了该实例构造函数的那个类的名称。如果指定了任何其他名称，则发生编译时错误。

实例构造函数的形参表（可选的）必须遵循与方法（第 10.5 节）的形参表同样的规则。此形参表定义实例构造函数的签名（第 3.6 节），并且在函数调用中控制重载决策（第 7.4.2 节）过程以选择某个特定实例的构造函数。

在实例构造函数的形参表中引用的各个类型必须至少具有与构造函数本身相同的可访问性（第 3.5.4 节）。

可选的构造函数初始值设定项用于指定：在执行此实例构造函数的构造函数体中给出的语句之前需要调用的另一个实例构造函数。第 10.10.1 节对此有进一步描述。

当构造函数声明中包含 extern 修饰符时，称该构造函数为外部构造函数。因为外部构造函数声明不提供任何实际的实现，所以它的构造函数体仅由一个分号组成。对于所有其他构造函数，构造函数体都由一个块组成，它用于指定初始化该类的一个新实例时需要执行的语句。这正好相当于一个具有 void 返回类型的实例方法的块（第 10.5.8 节）。

实例构造函数是不能继承的。因此，一个类除了自已声明的实例构造函数外，不可能有其他的实例构造函数。如果一个类没有声明任何实例构造函数，则会自动地为它提供一个默认的实例构造函数（第 10.10.4 节）。

实例构造函数是由对象创建表达式（第 7.5.10.1 节）并通过构造函数初始值设定项调用的。


10.10.1构造函数初始值设定项.1839547

10.10.1 构造函数初始值设定项

除了类 object 的实例构造函数以外，所有其他的实例构造函数都隐式地包含一个对另一个实例构造函数的调用，该调用紧靠在构造函数体的前面。要隐式调用的构造函数是由构造函数初始值设定项确定的：

· base（参数列表可选）形式的实例构造函数初始值设定项导致调用直接基类中的实例构造函数。该构造函数是根据参数列表和第 7.4.2 节的重载决策规则选择的。候选实例构造函数集由直接基类中包含的所有可访问实例构造函数组成（包括所有默认构造函数，详见第 10.10.4 节中的定义）。如果此集合为空，或者无法标识单个最佳实例构造函数，就会发生编译时错误。

· this（参数列表可选）形式的实例构造函数初始值设定项导致调用该类本身所声明的实例构造函数。构造函数是根据参数列表和第 7.4.2 节的重载决策规则选择的。候选实例构造函数集包括在该类中声明的所有可访问的实例构造函数。如果此集合为空，或者无法标识单个最佳实例构造函数，就会发生编译时错误。如果实例构造函数声明中包含调用构造函数本身的构造函数初始值设定项，则发生编译时错误。

如果一个实例构造函数声明中没有构造函数初始值设定项，将会隐式地添加一个 base() 形式的构造函数初始值设定项。因此，下列形式的实例构造函数声明

C(...) {...}

完全等效于

C(...): base() {...}

实例构造函数声明中的形参表所给出的参数范围包含该声明的实例构造函数初始值设定项。因此，构造函数初始值设定项可以访问该构造函数的参数。例如：

class A

{

public A(int x, int y) {}

}

class B: A

{

public B(int x, int y): base(x + y, x - y) {}

}

实例构造函数初始值设定项不能访问正在创建的实例。因此在构造函数初始值设定项的参数表达式中引用 this 属于编译时错误，就像参数表达式通过简单名称引用任何实例成员属于编译时错误一样。


10.10.2实例变量初始值设定项.1842785

Powered By Gavin http://www.CSharpWeb.com

10.10.2 实例变量初始值设定项

当实例构造函数没有构造函数初始值设定项时，或仅具有 base(...) 形式的构造函数初始值设定项时，该构造函数就会隐式地执行在该类中声明的实例字段的初始化操作，这些操作由对应的字段声明中的变量初始值设定项指定。这对应于一个赋值序列，它们会在进入构造函数时，在对直接基类的构造函数进行隐式调用之前立即执行。这些变量初始值设定项按它们出现在类声明中的文本顺序执行。


10.10.3构造函数执行.1843509

10.10.3 构造函数执行

变量初始值设定项被转换为赋值语句，而这些语句将在对基类实例构造函数进行调用之前执行。这种排序确保了在执行任何访问该实例的语句之前，所有实例字段都已按照它们的变量初始值设定项进行了初始化。

给定示例

using System;

class A

{

public A() {

PrintFields();

}

public virtual void PrintFields() {}

}

class B: A

{

int x = 1;

int y;

public B() {

y = -1;

}

public override void PrintFields() {

Console.WriteLine("x = {0}, y = {1}", x, y);

}

}

当使用 new B() 创建 B 的实例时，产生如下输出：

x = 1, y = 0

x 的值为 1，这是由于变量初始值设定项是在调用基类实例构造函数之前执行的。但是，y 的值为 0（int 型变量的默认值），这是因为对 y 的赋值直到基类构造函数返回之后才执行。

可以这样设想来帮助理解：将实例变量初始值设定项和构造函数初始值设定项视为自动插入到构造函数体之前的语句。在以下示例中：

using System;

using System.Collections;

class A

{

int x = 1, y = -1, count;

public A() {

count = 0;

}

public A(int n) {

count = n;

}

}

class B: A

{

double sqrt2 = Math.Sqrt(2.0);

ArrayList items = new ArrayList(100);

int max;

public B(): this(100) {

items.Add("default");

}

public B(int n): base(n – 1) {

max = n;

}

}

包含若干个变量初始值设定项，还包含两种形式（base 和 this）的构造函数初始值设定项。此示例对应于下面显示的代码，其中每个注释指示一个自动插入的语句（用于自动插入的构造函数调用的语法是无效的，而只是用来阐释此机制）。

using System.Collections;

class A

{

int x, y, count;

public A() {

x = 1; // Variable initializer

y = -1; // Variable initializer

object(); // Invoke object() constructor

count = 0;

}

public A(int n) {

x = 1; // Variable initializer

y = -1; // Variable initializer

object(); // Invoke object() constructor

count = n;

}

}

class B: A

{

double sqrt2;

ArrayList items;

int max;

public B(): this(100) {

B(100); // Invoke B(int) constructor

items.Add("default");

}

public B(int n): base(n – 1) {

sqrt2 = Math.Sqrt(2.0); // Variable initializer

items = new ArrayList(100); // Variable initializer

A(n – 1); // Invoke A(int) constructor

max = n;

}

}


10.10.4默认构造函数.1852178

10.10.4 默认构造函数

如果一个类不包含任何实例构造函数声明，则会自动地为该类提供一个默认实例构造函数。默认构造函数只是调用直接基类的无参数构造函数。如果直接基类没有可访问的无参数实例构造函数，则发生编译时错误。对于一个抽象类，它的默认构造函数的声明可访问性是受保护的。而对于非抽象类，它的默认构造函数的声明可访问性是公共的。因此，默认构造函数始终为下列形式：

protected C(): base() {}

或

public C(): base() {}

其中 C 为类的名称。

在下面的示例中

class Message

{

object sender;

string text;

}

由于类不包含任何实例构造函数声明，因此就为它提供了一个默认构造函数。因而，此示例完全等效于

class Message

{

object sender;

string text;

public Message(): base() {}

}


10.10.5私有构造函数.1854123

10.10.5 私有构造函数

当类 T 只声明了私有实例构造函数时，则在 T 的程序文本外部，既不可能从 T 派生出新的类，也不可能直接创建 T 的任何实例。因此，如果欲设计一个类，它只包含静态成员而且有意使它不能被实例化，则只需给它添加一个空的私有实例构造函数，即可达到目的。例如：

public class Trig

{

private Trig() {} // Prevent instantiation

public const double PI = 3.14159265358979323846;

public static double Sin(double x) {...}

public static double Cos(double x) {...}

public static double Tan(double x) {...}

}

Trig 类用于将相关的方法和常数组合在一起，但是它不能被实例化。因此它声明了单个空的私有实例构造函数。若要取消默认构造函数的自动生成，必须至少声明一个实例构造函数。


10.10.6可选的实例构造函数参数.1855856

10.10.6 可选的实例构造函数参数

this(...) 形式的构造函数初始值设定项通常与重载一起使用，以实现可选的实例构造函数。在下面的示例中

class Text

{

public Text(): this(0, 0, null) {}

public Text(int x, int y): this(x, y, null) {}

public Text(int x, int y, string s) {

// Actual constructor implementation

}

}

前两个实例构造函数只为调用中没有传递过来的参数提供相应的默认值。这两个构造函数都使用 this(...) 构造函数初始值设定项来调用实际完成初始化新实例工作的第三个实例构造函数。这样，实际效果就是该实例构造函数具有可选的参数：

Text t1 = new Text(); // Same as Text(0, 0, null)

Text t2 = new Text(5, 10); // Same as Text(5, 10, null)

Text t3 = new Text(5, 20, "Hello");


10.11静态构造函数.1857752

10.11 静态构造函数

静态构造函数是一种用于实现初始化类所需操作的成员。静态构造函数是使用静态构造函数声明来声明的：

static-constructor-declaration:（静态构造函数声明：）

attributes opt static-constructor-modifiers identifier ( ) static-constructor-body（属性可选 静态构造函数修饰符 标识符 ( ) 静态构造函数体）

static-constructor-modifiers:（静态构造函数修饰符：）

externopt static（extern可选 static）
static externopt（static extern可选）

static-constructor-body:（静态构造函数体：）

block（块）
;

静态构造函数声明可包含一组属性（第 17 节）和一个 extern（第 10.5.7 节）修饰符。

静态构造函数声明的标识符必须是声明了该静态函数的那个类的名称。如果指定了任何其他名称，则发生编译时错误。

当静态构造函数声明包含 extern 修饰符时，称该静态构造函数为外部静态构造函数。因为外部静态构造函数声明不提供任何实际的实现，所以它的静态构造函数体由一个分号组成。对于所有其他的静态构造函数声明，静态构造函数体都是一个块，它指定当初始化该类时需要执行的语句。这正好相当于具有 void 返回类型的静态方法的方法体（第 10.5.8 节）。

静态构造函数是不可继承的，而且不能被直接调用。

类的静态构造函数在给定应用程序域中至多执行一次。应用程序域中第一次发生以下事件时将触发静态构造函数的执行：

· 创建类的实例。

· 引用类的任何静态成员。

如果类中包含用来开始执行的 Main 方法（第 3.1 节），则该类的静态构造函数将在调用 Main 方法之前执行。如果类包含任何带有初始值设定项的静态字段，则在执行该类的静态构造函数时，先要按照文本顺序执行那些初始值设定项。

在以下示例中：

using System;

class Test

{

static void Main() {

A.F();

B.F();

}

}

class A

{

static A() {

Console.WriteLine("Init A");

}

public static void F() {

Console.WriteLine("A.F");

}

}

class B

{

static B() {

Console.WriteLine("Init B");

}

public static void F() {

Console.WriteLine("B.F");

}

}

一定产生输出：

Init A

A.F

Init B

B.F

因为 A 的静态构造函数的执行是通过调用 A.F 触发的，而 B 的静态构造函数的执行是通过调用 B.F 触发的。

上述过程有可能构造出循环依赖关系，其中，带有变量初始值设定项的静态字段能够在其处于默认值状态时被观测。

在以下示例中：

using System;

class A

{

public static int X;

static A() {

X = B.Y + 1;

}

}

class B

{

public static int Y = A.X + 1;

static B() {}

static void Main() {

Console.WriteLine("X = {0}, Y = {1}", A.X, B.Y);

}

}

产生输出

X = 1, Y = 2

要执行 Main 方法，系统在运行类 B 的静态构造函数之前首先要运行 B.Y 的初始值设定项。因为引用了 A.X 的值，所以 Y 的初始值设定项导致运行 A 的静态构造函数。这样，A 的静态构造函数将继续计算 X 的值，从而获取 Y 的默认值 0，而 A.X 被初始化为 1。这样就完成了运行 A 的静态字段初始值设定项和静态构造函数的进程，控制返回到 Y 的初始值的计算，计算结果变为 2。


10.12析构函数.1867673

10.12 析构函数

析构函数是一种用于实现析构类实例所需操作的成员。析构函数是用析构函数的声明来声明的：

destructor-declaration:（析构函数声明：）

attributes opt externopt ~ identifier ( ) destructor-body（属性可选 extern可选 ~ 标识符 ( ) 析构函数体）

destructor-body:（析构函数体：）

block（块）
;

析构函数声明可以包括一组属性（第 17 节）。

析构函数声明的标识符必须就是声明了该析构函数的那个类的名称。如果指定了任何其他名称，则发生编译时错误。

当析构函数声明包含 extern 修饰符时，称该析构函数为外部析构函数。因为外部析构函数声明不提供任何实际的实现，所以它的析构函数体由一个分号组成。对于所有其他析构函数，析构函数体都是由一个块组成，它指定当销毁该类的一个实例时需要执行的语句。析构函数体完全相当于具有 void 返回类型的实例方法的方法体（第 10.5.8 节）。

析构函数是不可继承的。因此，除了自已所声明的析构函数外，一个类不具有其他析构函数。

由于析构函数要求不能带有参数，因此它不能被重载，所以一个类至多只能有一个析构函数。

析构函数是自动调用的，它不能被显式调用。当任何代码都不再可能使用一个实例时，该实例就符合被销毁的条件。此后，它所对应的实例析构函数随时均可能被调用。销毁一个实例时，按照从派生程度最大到派生程度最小的顺序，调用该实例的继承链中的各个析构函数。析构函数可以在任何线程上执行。有关控制何时及如何执行析构函数的规则的进一步讨论，请参见第 3.9 节。

下列示例的输出

using System;

class A

{

~A() {

Console.WriteLine("A's destructor");

}

}

class B: A

{

~B() {

Console.WriteLine("B's destructor");

}

}

class Test

{

static void Main() {

B b = new B();

b = null;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

为

B's destructor

A's destructor

这是由于继承链中的析构函数是按照从派生程度最大到派生程度最小的顺序调用的。

析构函数实际上是重写了 System.Object 中的虚方法 Finalize。C# 程序中不允许重写此方法或直接调用它（或它的重写）。例如，下列程序

class A

{

override protected void Finalize() {} // error

public void F() {

this.Finalize(); // error

}

}

包含两个错误。

编译器的行为就像此方法和它的重写根本不存在一样。因此，以下程序：

class A

{

void Finalize() {} // permitted

}

是有效的，所声明的方法隐藏了 System.Object 的 Finalize 方法。

有关从析构函数引发异常时的行为的讨论，请参见第 16.3 节。


11.结构.1875023

Powered By Gavin http://www.CSharpWeb.com

11. 结构

结构与类很相似，都表示可以包含数据成员和函数成员的数据结构。但是，与类不同，结构是一种值类型，并且不需要堆分配。结构类型的变量直接包含了该结构的数据，而类类型的变量所包含的只是对相应数据的一个引用（被引用的数据称为“对象”）。

结构对于具有值语义的小的数据结构特别有用。复数、坐标系中的点或字典中的“键-值”对都是结构的典型示例。这些数据结构的关键之处在于：它们只有少量数据成员，它们不要求使用继承或引用标识，而且它们适合使用值语义（赋值时直接复制值而不是复制它的引用）方便地实现。

如第 4.1.3 节中所描述，C# 提供的简单类型，如 int、double 和 bool，实际上全都是结构类型。正如这些预定义类型是结构一样，也可以使用结构和运算符重载在 C# 语言中实现新的“基元”类型。在本章结尾（第 11.4 节）给出了这种类型的两个示例。


11.1结构声明.1876271

11.1 结构声明

结构声明是一种用于声明新结构的类型声明（第 9.5 节）：

struct-declaration:（结构声明：）

attributes opt struct-modifiers opt struct identifier struct-interfaces opt struct-body ;opt（属性可选 结构修饰符可选 struct 标识符 结构接口可选 结构体 ;可选）

结构声明包含一组可选的属性（第 17 节），后跟一组可选的结构修饰符（第 11.1.1 节），再跟关键字 struct 和一个命名结构的标识符，然后跟一个可选的结构接口规范（第 11.1.2 节），最后跟一个结构体（第 11.1.3 节），根据需要还可以后跟一个分号。


11.1.1结构修饰符.1879023

11.1.1 结构修饰符

结构声明可以根据需要包含一个结构修饰符序列：

struct-modifiers:（结构修饰符：）

struct-modifier（结构修饰符）
struct-modifiers struct-modifier（结构修饰符 结构修饰符）

struct-modifier:（结构修饰符：）

new
public
protected
internal
private

同一修饰符在结构声明中出现多次是编译时错误。

结构声明的修饰符与类声明（第 10.1.1 节）的修饰符具有相同的意义。


11.1.2结构接口.1880452

11.1.2 结构接口

结构声明中可以含有一个结构接口规范，这种情况下称该结构实现给定的接口类型。

struct-interfaces:（结构接口：）

; interface-type-list（; 接口类型列表）

第 13.4 节对接口实现进行了进一步讨论。


11.1.3结构体.1881379

Powered By Gavin http://www.CSharpWeb.com

11.1.3 结构体

结构的结构体用于定义该结构所包含的成员。

struct-body:（结构体：）

{ struct-member-declarations opt }（{ 结构成员声明可选 }）


11.2结构成员.1882576

11.2 结构成员

一个结构的成员由两部分组成：由结构成员声明引入的成员；以及从类型 System.ValueType 继承的成员。

struct-member-declarations:（结构成员声明：）

struct-member-declaration（结构成员声明）
struct-member-declarations struct-member-declaration（结构成员声明 结构成员声明）

struct-member-declaration:（结构成员声明：）

constant-declaration（常数声明）
field-declaration（字段声明）
method-declaration（方法声明）
property-declaration（属性声明）
event-declaration（事件声明）
indexer-declaration（索引器声明）
operator-declaration（运算符声明）
constructor-declaration（构造函数声明）
static-constructor-declaration（静态构造函数声明）
type-declaration（类型声明）

除了在第 11.3 节中指出的区别外，在从第 10.2 节到第 10.11 节中关于类成员的说明也适用于结构成员。


11.3类和结构的区别.1885509

11.3 类和结构的区别

结构在以下几个重要方面和类是不同的：

· 结构是值类型（第 11.3.1 节）。

· 所有结构类型隐式地从类 System.ValueType 继承（第 11.3.2 节）。

· 对结构类型变量进行赋值意味着将创建所赋的值的一个“副本”（第 11.3.3 节）。

· 一个结构的默认值的计算如下：将所有值类型字段设置为它们的默认值，并将所有引用类型字段设置为 null，这样就产生了该结构的默认值（第 11.3.4 节）。

· 使用装箱和取消装箱操作在结构类型和 object 之间进行转换（第 11.3.5 节）。

· 对于结构，this 的意义不同（第 11.3.6 节）。

· 在结构中，实例字段声明中不能含有变量初始值设定项（第 11.3.7 节）。

· 在结构中不能声明无参数的实例构造函数（第 11.3.8 节）。

· 在结构中不能声明析构函数（第 11.3.9 节）。


11.3.1值语义.1888508

11.3.1 值语义

结构是值类型（第 4.1 节）且被称为具有值语义。另一方面，类是引用类型（第 4.2 节）且被称为具有引用语义。

结构类型的变量直接包含了该结构的数据，而类类型的变量所包含的只是对相应数据的一个引用（被引用的数据称为“对象”）。

对于类，两个变量可能引用同一对象，因此对一个变量进行的操作可能影响另一个变量所引用的对象。对于结构，每个变量都有它们自己的数据副本（除 ref 和 out 参数变量外），因此对一个变量的操作不可能影响其他变量。另外，由于结构不是引用类型，因此结构类型的值不可能为 null。

给定下列声明

struct Point

{

public int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

代码片段

Point a = new Point(10, 10);

Point b = a;

a.x = 100;

System.Console.WriteLine(b.x);

输出值 10。将 a 赋给 b 将创建该值的一个副本，因此 b 不会受到对 a.x 进行的赋值的影响。假如 Point 被改而声明为类，则由于 a 和 b 将引用同一对象，因此输出将为 100。


11.3.2继承.1890884

11.3.2 继承

所有结构类型都隐式地从类 System.ValueType 继承，而后者则从类 object 继承。一个结构声明可以指定实现的接口列表，但是不能指定基类。

结构类型永远不会是抽象的，并且始终是隐式密封的。因此在结构声明中不允许使用 abstract 和 sealed 修饰符。

由于对结构不支持继承，所以一个结构成员的声明可访问性不能是 protected 或 protected internal。

结构中的函数成员不能是 abstract 或 virtual，因而 override 修饰符只适用于重写从 System.ValueType 继承的方法。


11.3.3赋值.1892088

Powered By Gavin http://www.CSharpWeb.com

11.3.3 赋值

对结构类型变量的赋值创建了被赋值的一个“副本”。这不同于对类类型变量的赋值，后者所复制的是引用，而不是复制由该引用所标识的对象。

与赋值类似，将结构作为值参数传递或者作为函数成员的结果返回时，也创建了该结构的一个副本。但是，一个结构仍可作为 ref 或 out 参数（在函数成员调用中）被引用传递。

当结构的属性或索引器是赋值的目标时，与属性或索引器访问关联的实例表达式必须为变量类别。如果该实例表达式归类为值类别，则发生编译时错误。第 7.13.1 节对此进行了详细的描述。


11.3.4默认值.1893003

11.3.4 默认值

如第 5.2 节中所描述，有几种变量在创建时自动初始化为它们的默认值。对于类类型和其他引用类型的变量，此默认值为 null。但是，由于结构是不能为 null 的值类型，结构的默认值是通过将所有值类型字段设置为它们的默认值，并将所有引用类型字段设置为 null 而产生的值。

引用上面声明的 Point 结构，下面的示例

Point[] a = new Point[100];

将数组中的每个 Point 初始化为通过将 x 和 y 字段设置为零而产生的值。

结构的默认值相当于该结构的默认构造函数所返回的值（第 4.1.1 节）。与类不同，结构不允许声明无参数实例构造函数。相反，每个结构隐式地具有一个无参数实例构造函数，该构造函数始终返回相同的值，即通过将所有的值类型字段设置为它们的默认值，并将所有引用类型字段设置为 null 而得到的值。

设计一个结构时，要设法确保它的默认初始化状是有效的状态。在下面的示例中

using System;

struct KeyValuePair

{

string key;

string value;

public KeyValuePair(string key, string value) {

if (key == null || value == null) throw new ArgumentException();

this.key = key;

this.value = value;

}

}

用户定义的实例构造函数不允许出现 null 值（除非在显式调用时）。但 KeyValuePair 变量可能会被初始化为它的默认值，这样，key 和 value 字段就都为 null，所以，设计该结构时，必须正确处理好此问题。


11.3.5装箱和取消装箱.1895733

Powered By Gavin http://www.CSharpWeb.com

11.3.5 装箱和取消装箱

一个类类型的值可以转换为 object 类型或由该类实现的接口类型，这只需在编译时把对应的引用当作另一个类型处理即可。与此类似，一个 object 类型的值或者接口类型的值也可以被转换回类类型而不必更改相应的引用。当然，在这种情况下，需要进行“运行时类型检查”。

由于结构不是引用类型，上述操作对结构类型是以不同的方式实现的。当结构类型的值被转换为 object 类型或由该结构实现的接口类型时，就会执行一次装箱操作。与此类似，当 object 类型的值或接口类型的值被转换回结构类型时，会执行一次消装箱操作。与对类类型进行的相同操作相比，主要区别在于：装箱操作会把相关的结构值“复制”到“箱”中，而取消装箱则会从已被装箱的实例中“复制”出一个结构值。因此，在装箱或取消装箱操作后，对“箱”外的结构进行的更改不会影响已被装箱的结构。

有关装箱和取消装箱的详细信息，请参见第 4.3 节。


11.3.6this的意义.1897038

Powered By Gavin http://www.CSharpWeb.com

11.3.6 this 的意义

在类的实例构造函数和实例函数成员中，this 为值类别。因此，虽然 this 可以用于引用该函数成员调用所涉及的实例，但是不可能在类的函数成员中对 this 本身赋值。

在结构的实例构造函数内，this 相当于一个结构类型的 out 参数，而在结构的实例函数成员内，this 相当于一个结构类型的 ref 参数。在这两种情况下，this 本身相当于一个变量，因而有可能对该函数成员调用所涉及的整个结构进行修改（如对 this 赋值，或者将 this 作为 ref 或 out 参数传递）。


11.3.7字段初始值设定项.1898231

11.3.7 字段初始值设定项

如第 11.3.4 节中所描述，结构的默认值就是将所有值类型字段设置为它们的默认值并将所有引用类型字段设置为 null 而产生的值。由于这个原因，结构不允许它的实例字段声明中含有变量初始值设定项。此限制只适用于实例字段。在结构的静态字段声明中可以含有变量初始值设定项。

在以下示例中：

struct Point

{

public int x = 1; // Error, initializer not permitted

public int y = 1; // Error, initializer not permitted

}

出现错误，因为实例字段声明中含有变量初始值设定项。


11.3.8构造函数.1899482

11.3.8 构造函数

与类不同，结构不允许声明无参数实例构造函数。实际上，每个结构类型都隐式地含有一个无参数实例构造函数，该构造函数始终返回通过如下方式得到的值：将所有的值类型字段设置为它们的默认值，并将所有引用类型字段设置为 null（第 4.1.1 节）。结构可以声明具有参数的实例构造函数。例如

struct Point

{

int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

已知以上声明，语句

Point p1 = new Point();

Point p2 = new Point(0, 0);

都创建了一个 Point，而且它们的 x 和 y 都初始化为零。

一个结构的实例构造函数不能含有 base(...) 形式的构造函数初始值设定项。

如果该结构实例构造函数没有指定构造函数初始值设定项，则 this 变量就相当于一个结构类型的 out 参数，并且与 out 参数类似，this 必须在该结构函数返回的每个位置上明确赋值（第 5.3 节）。如果该结构实例构造函数指定了构造函数初始值设定项，则 this 变量就相当于结构类型的 ref 参数，并且与 ref 参数类似，this 被视为在进入构造函数体时已被明确赋值。请研究下面的实例构造函数实现：

struct Point

{

int x, y;

public int X {

set { x = value; }

}

public int Y {

set { y = value; }

}

public Point(int x, int y) {

X = x; // error, this is not yet definitely assigned

Y = y; // error, this is not yet definitely assigned

}

}

在被构造的结构的所有字段已明确赋值以前，不能调用任何实例成员函数（包括 X 和 Y 属性的 set 访问器）。但是请注意，如果 Point 是类而不是结构，则上述的实例构造函数就变成有效的了。


11.3.9析构函数.1903422

Powered By Gavin http://www.CSharpWeb.com

11.3.9 析构函数

在结构类型中不允许声明析构函数。


11.4结构示例.1903807

Powered By Gavin http://www.CSharpWeb.com

11.4 结构示例

下面是关于应用 struct 类型两个重要示例，它们各自创建一个类型，这些类型使用起来就像 C# 语言的内置类型，但具有修改了的语义。


11.4.1数据库整数类型.1904316

11.4.1 数据库整数类型

下面的 DBInt 结构实现了一个整数类型，它可以表示 int 类型的值的完整集合，再加上一个用于表示未知值的附加状态。具有这些特征的类型常用在数据库中。

using System;

public struct DBInt

{

// The Null member represents an unknown DBInt value.

public static readonly DBInt Null = new DBInt();

// When the defined field is true, this DBInt represents a known value

// which is stored in the value field. When the defined field is false,

// this DBInt represents an unknown value, and the value field is 0.

int value;

bool defined;

// Private instance constructor. Creates a DBInt with a known value.

DBInt(int value) {

this.value = value;

this.defined = true;

}

// The IsNull property is true if this DBInt represents an unknown value.

public bool IsNull { get { return !defined; } }

// The Value property is the known value of this DBInt, or 0 if this

// DBInt represents an unknown value.

public int Value { get { return value; } }

// Implicit conversion from int to DBInt.

public static implicit operator DBInt(int x) {

return new DBInt(x);

}

// Explicit conversion from DBInt to int. Throws an exception if the

// given DBInt represents an unknown value.

public static explicit operator int(DBInt x) {

if (!x.defined) throw new InvalidOperationException();

return x.value;

}

public static DBInt operator +(DBInt x) {

return x;

}

public static DBInt operator -(DBInt x) {

return x.defined ? -x.value : Null;

}

public static DBInt operator +(DBInt x, DBInt y) {

return x.defined && y.defined? x.value + y.value: Null;

}

public static DBInt operator -(DBInt x, DBInt y) {

return x.defined && y.defined? x.value - y.value: Null;

}

public static DBInt operator *(DBInt x, DBInt y) {

return x.defined && y.defined? x.value * y.value: Null;

}

public static DBInt operator /(DBInt x, DBInt y) {

return x.defined && y.defined? x.value / y.value: Null;

}

public static DBInt operator %(DBInt x, DBInt y) {

return x.defined && y.defined? x.value % y.value: Null;

}

public static DBBool operator ==(DBInt x, DBInt y) {

return x.defined && y.defined? x.value == y.value: DBBool.Null;

}

public static DBBool operator !=(DBInt x, DBInt y) {

return x.defined && y.defined? x.value != y.value: DBBool.Null;

}

public static DBBool operator >(DBInt x, DBInt y) {

return x.defined && y.defined? x.value > y.value: DBBool.Null;

}

public static DBBool operator <(DBInt x, DBInt y) {

return x.defined && y.defined? x.value < y.value: DBBool.Null;

}

public static DBBool operator >=(DBInt x, DBInt y) {

return x.defined && y.defined? x.value >= y.value: DBBool.Null;

}

public static DBBool operator <=(DBInt x, DBInt y) {

return x.defined && y.defined? x.value <= y.value: DBBool.Null;

}

public override bool Equals(object obj) {

if (!(obj is DBInt)) return false;

DBInt x = (DBInt)obj;

return value == x.value && defined == x.defined;

}

public override int GetHashCode() {

return value;

}

public override string ToString() {

return defined? value.ToString(): "DBInt.Null";

}

}


11.4.2数据库布尔类型.1913346

11.4.2 数据库布尔类型

下面的 DBBool 结构实现了一个三值逻辑类型。该类型的可能值有 DBBool.True、DBBool.False 和 DBBool.Null，其中 Null 成员用于表示未知值。这样的三值逻辑类型经常用在数据库中。

using System;

public struct DBBool

{

// The three possible DBBool values.

public static readonly DBBool Null = new DBBool(0);

public static readonly DBBool False = new DBBool(-1);

public static readonly DBBool True = new DBBool(1);

// Private field that stores –1, 0, 1 for False, Null, True.

sbyte value;

// Private instance constructor. The value parameter must be –1, 0, or 1.

DBBool(int value) {

this.value = (sbyte)value;

}

// Properties to examine the value of a DBBool. Return true if this

// DBBool has the given value, false otherwise.

public bool IsNull { get { return value == 0; } }

public bool IsFalse { get { return value < 0; } }

public bool IsTrue { get { return value > 0; } }

// Implicit conversion from bool to DBBool. Maps true to DBBool.True and

// false to DBBool.False.

public static implicit operator DBBool(bool x) {

return x? True: False;

}

// Explicit conversion from DBBool to bool. Throws an exception if the

// given DBBool is Null, otherwise returns true or false.

public static explicit operator bool(DBBool x) {

if (x.value == 0) throw new InvalidOperationException();

return x.value > 0;

}

// Equality operator. Returns Null if either operand is Null, otherwise

// returns True or False.

public static DBBool operator ==(DBBool x, DBBool y) {

if (x.value == 0 || y.value == 0) return Null;

return x.value == y.value? True: False;

}

// Inequality operator. Returns Null if either operand is Null, otherwise

// returns True or False.

public static DBBool operator !=(DBBool x, DBBool y) {

if (x.value == 0 || y.value == 0) return Null;

return x.value != y.value? True: False;

}

// Logical negation operator. Returns True if the operand is False, Null

// if the operand is Null, or False if the operand is True.

public static DBBool operator !(DBBool x) {

return new DBBool(-x.value);

}

// Logical AND operator. Returns False if either operand is False,

// otherwise Null if either operand is Null, otherwise True.

public static DBBool operator &(DBBool x, DBBool y) {

return new DBBool(x.value < y.value? x.value: y.value);

}

// Logical OR operator. Returns True if either operand is True, otherwise

// Null if either operand is Null, otherwise False.

public static DBBool operator |(DBBool x, DBBool y) {

return new DBBool(x.value > y.value? x.value: y.value);

}

// Definitely true operator. Returns true if the operand is True, false

// otherwise.

public static bool operator true(DBBool x) {

return x.value > 0;

}

// Definitely false operator. Returns true if the operand is False, false

// otherwise.

public static bool operator false(DBBool x) {

return x.value < 0;

}

public override bool Equals(object obj) {

if (!(obj is DBBool)) return false;

return value == ((DBBool)obj).value;

}

public override int GetHashCode() {

return value;

}

public override string ToString() {

if (value > 0) return "DBBool.True";

if (value < 0) return "DBBool.False";

return "DBBool.Null";

}

}


12.数组.1922357

12. 数组

数组是一种包含若干变量的数据结构，这些变量都可以通过计算索引进行访问。数组中包含的变量（又称数组的元素）具有相同的类型，该类型称为数组的元素类型。

数组有一个“秩”，它表示和每个数组元素关联的索引个数。数组的秩又称为数组的维度。“秩”为 1 的数组称为一维数组。“秩”大于 1 的数组称为多维数组。维度大小确定的多维数组通常称为两维数组、三维数组等。

数组的每个维度都有一个关联的长度，它是一个大于或等于零的整数。维度的长度不是数组类型的组成部分，而只与数组类型的实例相关联，它是在运行时创建实例时确定的。维度的长度确定该维度的下标的有效范围：对于长度为 N 的维度，下标范围可以为 0 到 N - 1（包括 0 和 N - 1）。数组中的元素总数是数组中各维度长度的乘积。如果数组的一个或多个维度的长度为零，则称该数组为空。

数组的元素类型可以是任意类型，包括数组类型。


12.1数组类型.1923806

12.1 数组类型

数组类型表示为一个“非数组类型”后跟一个或多个“秩说明符”：

array-type:（数组类型：）

non-array-type rank-specifiers（非数组类型 秩说明符）

non-array-type:（非数组类型：）

type（类型）

rank-specifiers:（秩说明符：）

rank-specifier（秩说明符）
rank-specifiers rank-specifier（秩说明符 秩说明符）

rank-specifier:（秩说明符：）

[ dim-separators opt ]（[ 维度分隔符可选 ]）

dim-separators:（维度分隔符：）

，
dim-separators ,（维度分隔符 ,）

上述产生式中，非数组类型是本身不是数组类型的任意类型。

数组类型的秩由数组类型中最左边的秩说明符给定：秩说明符指示该数组的秩等于秩说明符中“,”标记的个数加 1。

数组类型的元素类型就是去掉最左边的“秩说明符”后剩余表达式的类型：

· 形式为 T[R] 的数组类型是秩为 R，且元素类型为非数组元素类型 T 的数组。

· 形式为 T[R][R1]...[RN] 的数组类型是秩为 R、元素类型为 T[R1]...[RN] 的数组。

实质上，在解释数组类型时，先从左到右读取“秩说明符”，最后才读取那个最终的非数组元素类型。例如，类型 int[][,,][,] 表示一个一维数组，该一维数组的元素类型为三维数组，该三维数组的元素类型为二维数组，该二维数组的元素类型为 int。

在运行时，数组类型的值可以为 null 或对该数组类型的某个实例的引用。


12.1.1System.Array类型.1928325

Powered By Gavin http://www.CSharpWeb.com

12.1.1 System.Array 类型

System.Array 类型是所有数组类型的抽象基类型。存在从任何数组类型到 System.Array 的隐式引用转换（第 6.1.4 节），并且存在从 System.Array 到任何数组类型的显式引用转换（第 6.2.3 节）。请注意 System.Array 本身不是数组类型。相反，它是一个从中派生所有数组类型的类类型。

在运行时，System.Array 类型的值可以是 null 或是对任何数组类型的实例的引用。


12.2数组创建.1929240

12.2 数组创建

数组实例是由数组创建表达式（第 7.5.10.2 节）创建的，或者是由包含数组初始值设定项（第 12.6 节）的字段声明或局部变量声明创建的。

创建数组实例时，将确定秩和各维度的长度，它们在该实例的整个生存期内保持不变。换言之，对于一个已存在的数组实例，既不能更改它的秩，也不可能调整它的维度大小。

数组实例一定是数组类型。System.Array 类型是不能实例化的抽象类型。

由“数组创建表达式”创建的数组的元素总是被初始化为它们的默认值（第 5.2 节）。


12.3数组元素访问.1930132

Powered By Gavin http://www.CSharpWeb.com

12.3 数组元素访问

我们可以使用形式为 A[I1, I2, ..., IN] 的“元素访问”表达式（第 7.5.6.1 节）访问数组元素，其中 A 是数组类型的表达式，各 IX 是类型为 int、uint、long、ulong 的表达式，或者是可隐式转换为这些类型中的一个或多个的表达式。数组元素访问的结果是变量，即由下标选定的数组元素。

此外，还可以使用 foreach 语句（第 8.8.4 节）来枚举数组的各个元素。


12.4数组成员.1931194

Powered By Gavin http://www.CSharpWeb.com

12.4 数组成员

每个数组类型都继承由 System.Array 类型所声明的成员。


12.5数组协方差.1931578

12.5 数组协方差

对于任意两个“引用类型”A 和 B，如果存在从 A 到 B 的隐式引用转换（第 6.1.4 节）或显式引用转换（第 6.2.3 节），则也一定存在从数组类型 A[R] 到数组类型 B[R] 的相同的引用转换，其中 R 可以是任何给定的“秩说明符”，但这两个数组类型必须使用相同的 R。这种关系称为数组协方差。具体说来，数组协方差意味着数组类型 A[R] 的值实际上可能是对数组类型 B[R] 的实例的引用（如果存在从 B 到 A 的隐式引用转换的话）。

由于存在数组协方差，对引用类型数组的元素的赋值操作会包括一个运行时检查，以确保正在赋给数组元素的值确实是允许的类型（第 7.13.1 节）。例如：

class Test

{

static void Fill(object[] array, int index, int count, object value) {

for (int i = index; i < index + count; i++) array[i] = value;

}

static void Main() {

string[] strings = new string[100];

Fill(strings, 0, 100, "Undefined");

Fill(strings, 0, 10, null);

Fill(strings, 90, 10, 0);

}

}

Fill 方法中对 array[i] 的赋值隐式地包括一个运行时检查，它确保由 value 引用的对象是 null 或是与 array 的实际元素类型兼容的类型的实例。在 Main 中，Fill 的前两个调用成功了，但是在第三个调用中，当执行对 array[i] 的第一次赋值时会引发 System.ArrayTypeMismatchException。发生此异常是因为装箱的 int 类型不能存储在 string 数组中。

具体说来，数组协方差不能延伸到“值类型”的数组。例如，不存在允许将 int[] 当作 object[] 来处理的转换。


12.6数组初始值设定项.1935020

12.6 数组初始值设定项

数组初始值设定项可以用于字段声明（第 10.4 节）、局部变量声明（第 8.5.1 节）和数组创建表达式（第 7.5.10.2 节）中：

array-initializer:（数组初始值设定项：）

{ variable-initializer-list opt }（{ 变量初始值设定项列表可选 }）
{ variable-initializer-list , }（{ 变量初始值设定项列表 , }）

variable-initializer-list:（变量初始值设定项列表：）

variable-initializer（变量初始值设定项）
variable-initializer-list , variable-initializer（变量初始值设定项列表 , 变量初始值设定项）

variable-initializer:（变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设定项）

数组初始值设定项包含一系列变量初始值设定项，它们括在“{”和“}”标记中并且用“,”标记分隔。每个变量初始值设定项是一个表达式，或者（在多维数组的情况下）是一个嵌套的数组初始值设定项。

数组初始值设定项所在位置的上下文确定了正在被初始化的数组的类型。在数组创建表达式中，数组类型后面紧跟着初始值设定项。在字段或变量声明中，数组类型就是所声明的字段或变量的类型。当数组初始值设定项用在字段或变量声明中时，如：

int[] a = {0, 2, 4, 6, 8};

它只是下列等效数组创建表达式的简写形式：

int[] a = new int[] {0, 2, 4, 6, 8};

对于一维数组，数组初始值设定项必须包含一个表达式序列，这些表达式是与数组的元素类型兼容的赋值表达式。这些表达式从下标为零的元素开始，按照升序初始化数组元素。数组初始值设定项中所含的表达式的数目确定正在创建的数组实例的长度。例如，上面的数组初始值设定项创建了一个长度为 5 的 int[] 实例并用下列值初始化该实例：

a[0] = 0; a[1] = 2; a[2] = 4; a[3] = 6; a[4] = 8;

对于多维数组，数组初始值设定项必须具有与数组维数同样多的嵌套级别。最外面的嵌套级别对应于最左边的维度，而最里面的嵌套级别对应于最右边的维度。数组各维度的长度是由数组初始值设定项中相应嵌套级别内的元素数目确定的。对于每个嵌套的数组初始值设定项，元素的数目必须与同一级别的其他数组初始值设定项所包含的元素数相同。示例：

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

创建一个二维数组，其最左边的维度的长度为 5，最右边的维度的长度为 2：

int[,] b = new int[5, 2];

然后用下列值初始化该数组实例：

b[0, 0] = 0; b[0, 1] = 1;

b[1, 0] = 2; b[1, 1] = 3;

b[2, 0] = 4; b[2, 1] = 5;

b[3, 0] = 6; b[3, 1] = 7;

b[4, 0] = 8; b[4, 1] = 9;

当数组创建表达式同时包含显式维度长度和一个数组初始值设定项时，长度必须是常数表达式，并且各嵌套级别的元素数目必须与相应的维度长度匹配。以下是几个示例：

int i = 3;

int[] x = new int[3] {0, 1, 2}; // OK

int[] y = new int[i] {0, 1, 2}; // Error, i not a constant

int[] z = new int[3] {0, 1, 2, 3}; // Error, length/initializer mismatch

这里，由于维度长度表达式不是常数，因此 y 的初始值设定项导致编译时错误；另外由于初始值设定项中所设定的长度和元素数目不一致，z 的初始值设定项也导致编译时错误。


13.接口.1941292

Powered By Gavin http://www.CSharpWeb.com

13. 接口

一个接口定义一个协定。实现某接口的类或结构必须遵守该接口定义的协定。一个接口可以从多个基接口继承，而一个类或结构可以实现多个接口。

接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或结构必须提供的成员。


13.1接口声明.1941915

13.1 接口声明

“接口声明”是用于声明新的接口类型的“类型声明”（第 9.5 节）。

interface-declaration:（接口声明：）

attributes opt interface-modifiers opt interface identifier interface-base opt interface-body ;opt（属性可选 接口修饰符可选 interface 标识符 接口基可选 接口体 ;可选）

“接口声明”由下列项组成：一个可选的属性集（第 17 节），后跟一个可选的“接口修饰符”集（第 13.1.1 节），后面再跟关键字 interface 和命名接口的“标识符”，还可根据需要后跟可选的“接口基”规范（第 13.1.2 节），再后跟“接口体”（第 13.1.3 节），还可选择后跟一个分号。


13.1.1接口修饰符.1945040

13.1.1 接口修饰符

接口声明可以根据需要包含一个接口修饰符序列：

interface-modifiers:（接口修饰符：）

interface-modifier（接口修饰符）
interface-modifiers interface-modifier（接口修饰符 接口修饰符）

interface-modifier:（接口修饰符：）

new
public
protected
internal
private

同一修饰符在一个接口声明中出现多次属于编译时错误。

new 修饰符仅允许在类中定义的接口中使用。它指定接口隐藏同名的继承成员，详见第 10.2.2 节中的介绍。

public、protected、internal 和 private 修饰符控制接口的可访问性。根据接口声明所在的上下文，只允许使用这些修饰符中的一部分（第 3.5.1 节）。


13.1.2基接口.1946849

13.1.2 基接口

接口可以从零个或多个接口继承，被继承的接口称为该接口的显式基接口。当接口具有一个或多个显式基接口时，在该接口声明中，接口标识符后就要紧跟一个冒号以及一个由逗号分隔的基接口标识符列表。

interface-base:（接口基：）

; interface-type-list（; 接口类型列表）

接口的显式基接口必须至少具有与该接口本身相同的可访问性（第 3.5.4 节）。例如，在 public 接口的“接口基”中指定 private 或 internal 接口就是一个编译时错误。

接口不能从自身直接或间接继承，否则会发生编译时错误。

接口的基接口包括显式基接口，以及这些显式基接口的基接口。换言之，基接口集是显式基接口、它们的显式基接口（依此类推）的完全可传递的闭包。接口继承其基接口的所有成员。在下面的示例中

interface IControl

{

void Paint();

}

interface ITextBox: IControl

{

void SetText(string text);

}

interface IListBox: IControl

{

void SetItems(string[] items);

}

interface IComboBox: ITextBox, IListBox {}

IComboBox 的基接口是 IControl、ITextBox 和 IListBox。

换言之，上面的 IComboBox 接口继承成员 SetText 和 SetItems 以及 Paint。

如果一个类或结构实现某接口，则它还隐式实现该接口的所有基接口。


13.1.3接口体.1949831

Powered By Gavin http://www.CSharpWeb.com

13.1.3 接口体

接口的“接口体”定义接口的成员。

interface-body:（接口体：）

{ interface-member-declarations opt }（{ 接口成员声明可选 }）


13.2接口成员.1951026

13.2 接口成员

接口的成员包括从基接口继承的成员和由接口本身声明的成员。

interface-member-declarations:（接口成员声明：）

interface-member-declaration（接口成员声明）
interface-member-declarations interface-member-declaration（接口成员声明 接口成员声明）

interface-member-declaration:（接口成员声明：）

interface-method-declaration（接口方法声明）
interface-property-declaration（接口属性声明）
interface-event-declaration（接口事件声明）
interface-indexer-declaration（接口索引器声明）

一个接口声明可以声明零个或多个成员。接口的成员必须是方法、属性、事件或索引器。接口不能包含常数、字段、运算符、实例构造函数、析构函数或类型，也不能包含任何种类的静态成员。

所有接口成员都隐式地具有 public 访问属性。接口成员声明中包含任何修饰符都属于编译时错误。具体来说，不能用修饰符 abstract、public、protected、internal、private、virtual、override 或 static 来声明接口成员。

在以下示例中：

public delegate void StringListEvent(IStringList sender);

public interface IStringList

{

void Add(string s);

int Count { get; }

event StringListEvent Changed;

string this[int index] { get; set; }

}

声明了一个接口，该接口的成员涵盖了所有可能作为接口成员的种类（每种一个）：方法、属性、事件和索引器。

“接口声明”创建新的声明空间（第 3.3 节），并且“接口声明”直接包含的“接口成员声明”将新成员提供给该声明空间。以下规则适用于“接口成员声明”：

· 方法的名称必须与同一接口中声明的所有属性和事件的名称不同。此外，方法的签名（第 3.6 节）必须与同一接口中声明的所有其他方法的签名不同。

· 属性或事件的名称必须与同一接口中声明的所有其他成员的名称不同。

· 一个索引器的签名必须区别于在同一接口中声明的其他所有索引器的签名。

准确地说，接口所继承的成员不是该接口的声明空间的一部分。因此，允许接口用与它所继承的成员相同的名称或签名来声明新的成员。发生这种情况时，则称派生的接口成员隐藏了基接口成员。隐藏一个继承的成员不算是错误，但这确实会导致编译器发出警告。为了避免出现上述警告，派生接口成员的声明中必须包含一个 new 修饰符，以指示该派生成员将要隐藏对应的基成员。第 3.7.1.2 节中对该主题进行了进一步讨论。

如果在不隐藏所继承成员的声明中包含 new 修饰符，将对此状况发出警告。通过移除 new 修饰符可取消显示此警告。


13.2.1接口方法.1956075

13.2.1 接口方法

接口方法是使用“接口方法声明”来声明的：

interface-method-declaration:（接口方法声明：）

attributes opt newopt return-type identifier ( formal-parameter-list opt ) ;（属性可选 new可选 返回类型 标识符 ( 形参表可选 ) ;）

接口方法声明中的“属性”、“返回类型”、“标识符”和“形参表”与类中的方法声明的对应项（第 10.5 节）具有相同的意义。不允许接口方法声明指定方法体，因此，声明总是以分号结尾。


13.2.2接口属性.1959087

13.2.2 接口属性

接口属性是使用“接口属性声明”来声明的：

interface-property-declaration:（接口属性声明：）

attributes opt newopt type identifier { interface-accessors }（属性可选 new可选 类型 标识符 { 接口访问器 }）

interface-accessors:（接口访问器：）

attributes opt get ;（属性可选 get ;）
attributes opt set ;（属性可选 set ;）
attributes opt get ; attributes opt set ;（属性可选 get ; 属性可选 set ;）
attributes opt set ; attributes opt get ;（属性可选 set ; 属性可选 get ;）

接口属性声明中的“属性”、“类型”和“标识符”与类中的属性声明的对应项（第 10.6 节）具有相同的意义。

接口属性声明的访问器与类属性声明（第 10.6.2 节）的访问器相对应，不同之处在于接口属性声明的访问器体必须始终是一个分号。因此，访问器在这里只用于表示该属性为读写、只读还是只写。


13.2.3接口事件.1966199

13.2.3 接口事件

接口事件是使用“接口事件声明”来声明的：

interface-event-declaration:（接口事件声明：）

attributes opt newopt event type identifier ;（属性可选 new可选 event 类型 标识符 ;）

接口事件声明中的“属性”、“类型”和“标识符”与类中事件声明的对应项（第 10.7 节）具有相同的意义。


13.2.4接口索引器.1968474

13.2.4 接口索引器

接口索引器是使用“接口索引器声明”来声明的：

interface-indexer-declaration:（接口索引器声明：）

attributes opt newopt type this [ formal-parameter-list ] { interface-accessors }（属性可选 new可选 类型 this [ 形参表 ] { 接口访问器 }）

接口索引器声明中的“属性”、“类型”和“形参表”与类中索引器声明的对应项（第 10.8 节）具有相同的意义。

接口索引器声明的访问器与类索引器声明（第 10.8 节）的访问器相对应，不同之处在于接口索引器声明的访问体必须始终是一个分号。因此，访问器在这里只用于表示该索引器为读写、只读还是只写。


13.2.5接口成员访问.1972291

13.2.5 接口成员访问

接口成员是通过 I.M 形式的成员访问（第 7.5.4 节）表达式和 I[A] 形式的索引器访问（第 7.5.6.2 节）表达式来访问的，其中 I 是接口类型，M 是该接口类型的方法、属性或事件，A 是对应的索引器参数列表。

对于严格单一继承（继承链中的每个接口均恰好有零个或一个直接基接口）的接口，成员查找（第 7.3 节）、方法调用（第 7.5.5.1 节）和索引器访问（第 7.5.6.2 节）规则的效果与类和结构的完全相同：派生程度较大的成员隐藏具有相同名称或签名的派生程度较小的成员。然而，对于多重继承接口，当两个或更多个不相关（互不继承）的基接口中声明了具有相同名称或签名的成员时，就会发生多义性。本节列出了此类情况的几个示例。在所有情况下，都可以使用显式强制转换来解决这种多义性。

在下面的示例中

interface IList

{

int Count { get; set; }

}

interface ICounter

{

void Count(int i);

}

interface IListCounter: IList, ICounter {}

class C

{

void Test(IListCounter x) {

x.Count(1); // Error

x.Count = 1; // Error

((IList)x).Count = 1; // Ok, invokes IList.Count.set

((ICounter)x).Count(1); // Ok, invokes ICounter.Count

}

}

由于在 IListCounter 中对 Count 的成员查找（第 7.3 节）所获得的结果是不明确的，因此前两个语句将导致编译时错误。如示例所阐释的，将 x 强制转换为适当的基接口类型就可以解决这种多义性。此类强制转换没有运行时开销，它们只是在编译时将该实例所属类型视为派生程度较小的类型而已。

在下面的示例中

interface IInteger

{

void Add(int i);

}

interface IDouble

{

void Add(double d);

}

interface INumber: IInteger, IDouble {}

class C

{

void Test(INumber n) {

n.Add(1); // Error, both Add methods are applicable

n.Add(1.0); // Ok, only IDouble.Add is applicable

((IInteger)n).Add(1); // Ok, only IInteger.Add is a candidate

((IDouble)n).Add(1); // Ok, only IDouble.Add is a candidate

}

}

由于方法调用（第 7.5.5.1 节）要求以相同类型声明所有的重载候选方法，因此调用 n.Add(1) 是不明确的。然而，调用 n.Add(1.0) 是允许的，这是因为只有 IDouble.Add 适用。插入显式强制转换后，就只有一个候选方法了，因此没有多义性。

在下面的示例中

interface IBase

{

void F(int i);

}

interface ILeft: IBase

{

new void F(int i);

}

interface IRight: IBase

{

void G();

}

interface IDerived: ILeft, IRight {}

class A

{

void Test(IDerived d) {

d.F(1); // Invokes ILeft.F

((IBase)d).F(1); // Invokes IBase.F

((ILeft)d).F(1); // Invokes ILeft.F

((IRight)d).F(1); // Invokes IBase.F

}

}

IBase.F 成员被 ILeft.F 成员隐藏。因此，即使在通过 IRight 的访问路径中 IBase.F 似乎没有被隐藏，调用 d.F(1) 仍选择 ILeft.F。

多重继承接口中隐藏的直观规则简单地说就是：如果成员在任何一个访问路径中被隐藏，那么它在所有访问路径中都被隐藏。由于从 IDerived 经 ILeft 到 IBase 的访问路径隐藏了 IBase.F，因此该成员在从 IDerived 经 IRight 到 IBase 的访问路径中也被隐藏。


13.3完全限定接口成员名.1980550

13.3 完全限定接口成员名

接口成员有时也用它的完全限定名来引用。接口成员的完全限定名是这样组成的：声明该成员的接口的名称，后跟一个点，再后跟该成员的名称。成员的完全限定名将引用声明该成员的接口。例如，给定下列声明

interface IControl

{

void Paint();

}

interface ITextBox: IControl

{

void SetText(string text);

}

Paint 的完全限定名是 IControl.Paint，而 SetText 的完全限定名是 ITextBox.SetText。

在上面的示例中，不可以用 ITextBox.Paint 来引用 Paint 方法。

当接口是命名空间的组成部分时，该接口的成员的完全限定名需包含命名空间名称。例如

namespace System

{

public interface ICloneable

{

object Clone();

}

}

这里，Clone 方法的完全限定名是 System.ICloneable.Clone。


13.4接口实现.1982824

13.4 接口实现

接口可以由类和结构来实现。为了指示类或结构实现了某接口，在该类或结构的基类列表中应该包含该接口的标识符。例如：

interface ICloneable

{

object Clone();

}

interface IComparable

{

int CompareTo(object other);

}

class ListEntry: ICloneable, IComparable

{

public object Clone() {...}

public int CompareTo(object other) {...}

}

如果一个类或结构实现某接口，则它还隐式实现该接口的所有基接口。即使在类或结构的基类列表中没有显式列出所有基接口，也是这样。例如：

interface IControl

{

void Paint();

}

interface ITextBox: IControl

{

void SetText(string text);

}

class TextBox: ITextBox

{

public void Paint() {...}

public void SetText(string text) {...}

}

这里，TextBox 类同时实现了 IControl 和 ITextBox。


13.4.1显式接口成员实现.1985559

13.4.1 显式接口成员实现

为了实现接口，类或结构可以声明“显式接口成员实现”。显式接口成员实现就是一种方法、属性、事件或索引器声明，它使用完全限定接口成员名称作为标识符。例如

interface ICloneable

{

object Clone();

}

interface IComparable

{

int CompareTo(object other);

}

class ListEntry: ICloneable, IComparable

{

object ICloneable.Clone() {...}

int IComparable.CompareTo(object other) {...}

}

这里，关于 ICloneable.Clone 和 IComparable.CompareTo 的声明是显式接口成员实现。

某些情况下，接口成员的名称对于实现该接口的类可能是不适当的，此时，可以使用显式接口成员实现来实现该接口成员。例如，一个用于“文件抽象”的类一般会实现一个具有释放文件资源作用的 Close 成员函数，同时还可能使用显式接口成员实现来实现 IDisposable 接口的 Dispose 方法：

interface IDisposable

{

void Dispose();

}

class MyFile: IDisposable

{

void IDisposable.Dispose() {

Close();

}

public void Close() {

// Do what's necessary to close the file

System.GC.SuppressFinalize(this);

}

}

在方法调用、属性访问或索引器访问中，不能直接访问“显式接口成员实现”的成员，即使用它的完全限定名也不行。“显式接口成员实现”的成员只能通过接口实例访问，并且在通过接口实例访问时，只能用该接口成员的简单名称来引用。

显式接口成员实现中包含访问修饰符属于编译时错误，而且如果包含 abstract、virtual、override 或 static 修饰符也属于编译时错误。

显式接口成员实现具有与其他成员不同的可访问性特征。由于显式接口成员实现永远不能在方法调用或属性访问中通过它们的完全限定名来访问，因此，它们似乎是 private（私有的）。但是，因为它们可以通过接口实例来访问，所以它们似乎又是 public（公共的）。

显式接口成员实现有两个主要用途：

· 由于显式接口成员实现不能通过类或结构实例来访问，因此它们就不属于类或结构的自身的公共接口。当需在一个公用的类或结构中实现一些仅供内部使用（不允许外界访问）的接口时，这就特别有用。

· 显式接口成员实现可以消除因同时含有多个相同签名的接口成员所引起的多义性。如果没有显式接口成员实现，一个类或结构就不可能为具有相同签名和返回类型的接口成员分别提供相应的实现，也不可能为具有相同签名和不同返回类型的所有接口成员中的任何一个提供实现。

为了使显式接口成员实现有效，声明它的类或结构必须在它的基类列表中指定一个接口，而该接口必须包含一个成员，该成员的完全限定名、类型和参数类型与该显式接口成员实现所具有的完全相同。因此，在下列类中

class Shape: ICloneable

{

object ICloneable.Clone() {...}

int IComparable.CompareTo(object other) {...} // invalid

}

IComparable.CompareTo 声明将导致编译时错误，原因是 IComparable 未列在 Shape 的基类列表中，并且不是 ICloneable 的基接口。与此类似，在下列声明中

class Shape: ICloneable

{

object ICloneable.Clone() {...}

}

class Ellipse: Shape

{

object ICloneable.Clone() {...} // invalid

}

Ellipse 中的 ICloneable.Clone 声明也将导致编译时错误，因为 ICloneable 未在 Ellipse 的基类列表中显式列出。

接口成员的完全限定名必须引用声明该成员的接口。因此，下列声明中

interface IControl

{

void Paint();

}

interface ITextBox: IControl

{

void SetText(string text);

}

class TextBox: ITextBox

{

void IControl.Paint() {...}

void ITextBox.SetText(string text) {...}

}

Paint 的显式接口成员实现必须写为 IControl.Paint。


13.4.2接口映射.1993889

13.4.2 接口映射

类或结构必须为它的基类列表中所列出的接口的所有成员提供它自己的实现。在进行实现的类或结构中定位接口成员的实现的过程称为接口映射。

关于类或结构 C 的接口映射就是查找 C 的基类列表中指定的每个接口的每个成员的实现。对某个特定接口成员 I.M 的实现（其中 I 是声明了成员 M 的接口）的定位按下述规则执行：从 C 开始，按继承顺序，逐个检查它的每个后续基类（下面用 S 表示每个进行检查的类或结构），直到找到匹配项：

· 如果 S 包含一个与 I 和 M 匹配的显式接口成员实现的声明，那么此成员就是 I.M 的实现。

· 否则，如果 S 包含与 M 匹配的非静态的 public 成员声明，则此成员就是 I.M 的实现。

如果不能为在 C 的基类列表中指定的所有接口的所有成员找到实现，则将发生编译时错误。请注意，接口的成员包括那些从基接口继承的成员。

根据接口映射的含义，类成员 A 在下列情况下与接口成员 B 匹配：

· A 和 B 都是方法，并且 A 和 B 的名称、类型和形参表都相同。

· A 和 B 都是属性，A 和 B 的名称与类型相同，并且 A 与 B 具有相同的访问器（如果 A 不是显式接口成员实现，则它可以具有其他访问器）。

· A 和 B 都是事件，并且 A 和 B 的名称与类型相同。

· A 和 B 都是索引器，A 和 B 的类型与形参表相同，并且 A 与 B 具有相同的访问器（如果 A 不是显式接口成员实现，则它可以具有其他访问器）。

接口映射算法中隐含着下列值得注意的特征：

· 在类或结构成员中确定哪个实现了接口成员时，显式接口成员实现比同一个类或结构中的其他成员具有更高的优先级。

· 接口映射不涉及非公共成员和静态成员。

在下面的示例中

interface ICloneable

{

object Clone();

}

class C: ICloneable

{

object ICloneable.Clone() {...}

public object Clone() {...}

}

C 的 ICloneable.Clone 成员成为 ICloneable 中 Clone 的实现，这是因为显式接口成员实现比其他成员具有更高的优先级。

如果类或结构实现两个或更多个接口，而这些接口包含具有相同名称、类型和参数类型的成员，则这些接口成员可以全部映射到单个类或结构成员上。例如

interface IControl

{

void Paint();

}

interface IForm

{

void Paint();

}

class Page: IControl, IForm

{

public void Paint() {...}

}

此处，IControl 和 IForm 的 Paint 方法都被映射到 Page 中的 Paint 方法上。当然也可以为这两个方法提供单独的显式接口成员实现。

如果类或结构实现一个包含被隐藏成员的接口，那么一些成员必须通过显式接口成员实现来实现。例如

interface IBase

{

int P { get; }

}

interface IDerived: IBase

{

new int P();

}

此接口的实现将至少需要一个显式接口成员实现，可采取下列形式之一

class C: IDerived

{

int IBase.P { get {...} }

int IDerived.P() {...}

}

class C: IDerived

{

public int P { get {...} }

int IDerived.P() {...}

}

class C: IDerived

{

int IBase.P { get {...} }

public int P() {...}

}

当一个类实现多个具有相同基接口的接口时，为该基接口提供的实现只能有一个。在下面的示例中

interface IControl

{

void Paint();

}

interface ITextBox: IControl

{

void SetText(string text);

}

interface IListBox: IControl

{

void SetItems(string[] items);

}

class ComboBox: IControl, ITextBox, IListBox

{

void IControl.Paint() {...}

void ITextBox.SetText(string text) {...}

void IListBox.SetItems(string[] items) {...}

}

在基类列表中命名的 IControl、由 ITextBox 继承的 IControl 和由 IListBox 继承的 IControl 不可能有各自不同的实现。事实上，没有为这些接口提供单独实现的打算。相反，ITextBox 和 IListBox 的实现共享相同的 IControl 的实现，因而可以简单地认为 ComboBox 实现了三个接口：IControl、ITextBox 和 IListBox。

基类的成员参与接口映射。在下面的示例中

interface Interface1

{

void F();

}

class Class1

{

public void F() {}

public void G() {}

}

class Class2: Class1, Interface1

{

new public void G() {}

}

Class1 中的方法 F 被当作 Class2 为 Interface1 提供的实现。


13.4.3接口实现继承.2005744

13.4.3 接口实现继承

类继承由其基类提供的所有接口实现。

如果不显式地重新实现接口，派生类就无法以任何方式更改它从其基类继承的接口映射。例如，在下面的声明中

interface IControl

{

void Paint();

}

class Control: IControl

{

public void Paint() {...}

}

class TextBox: Control

{

new public void Paint() {...}

}

TextBox 中的 Paint 方法隐藏 Control 中的 Paint 方法，但这种隐藏并不更改 Control.Paint 到 IControl.Paint 的映射，所以通过类实例和接口实例对 Paint 进行的调用就将具有不同的结果

Control c = new Control();

TextBox t = new TextBox();

IControl ic = c;

IControl it = t;

c.Paint(); // invokes Control.Paint();

t.Paint(); // invokes TextBox.Paint();

ic.Paint(); // invokes Control.Paint();

it.Paint(); // invokes Control.Paint();

但是，当接口方法被映射到类中的虚拟方法上时，从该类派生的类若重写了该虚拟方法，则将同时更改该接口的实现。例如，将上面的声明改写为

interface IControl

{

void Paint();

}

class Control: IControl

{

public virtual void Paint() {...}

}

class TextBox: Control

{

public override void Paint() {...}

}

将产生下列效果

Control c = new Control();

TextBox t = new TextBox();

IControl ic = c;

IControl it = t;

c.Paint(); // invokes Control.Paint();

t.Paint(); // invokes TextBox.Paint();

ic.Paint(); // invokes Control.Paint();

it.Paint(); // invokes TextBox.Paint();

由于显式接口成员实现不能被声明为虚拟的，因此不可能重写显式接口成员实现。然而，显式接口成员实现的内部完全可以调用另一个方法，只要将该方法声明为虚拟方法，派生类就可以重写它了。例如

interface IControl

{

void Paint();

}

class Control: IControl

{

void IControl.Paint() { PaintControl(); }

protected virtual void PaintControl() {...}

}

class TextBox: Control

{

protected override void PaintControl() {...}

}

这里，从 Control 派生的类可以通过重写 PaintControl 方法来专用化 IControl.Paint 的实现。


13.4.4接口重新实现.2011504

13.4.4 接口重新实现

一个类若继承了某个接口的实现，则只要将该接口列入它的基类列表中，就可以重新实现该接口。

接口的重新实现与接口的初始实现遵循完全相同的接口映射规则。因此，继承的接口映射不会对为重新实现该接口而建立的接口映射产生任何影响。例如，在下面的声明中

interface IControl

{

void Paint();

}

class Control: IControl

{

void IControl.Paint() {...}

}

class MyControl: Control, IControl

{

public void Paint() {}

}

Control 将 IControl.Paint 映射到 Control.IControl.Paint 上，而这并不会影响 MyControl 中 IControl.Paint 的重新实现，在此重新实现中，会将 IControl.Paint 映射到 MyControl.Paint 上。

继承的公共成员声明和继承的显式接口成员声明可以参与重新实现接口的接口映射过程。例如

interface IMethods

{

void F();

void G();

void H();

void I();

}

class Base: IMethods

{

void IMethods.F() {}

void IMethods.G() {}

public void H() {}

public void I() {}

}

class Derived: Base, IMethods

{

public void F() {}

void IMethods.H() {}

}

此处，Derived 中 IMethods 的实现将各个接口方法分别映射到 Derived.F、Base.IMethods.G、Derived.IMethods.H 和 Base.I 上。

当类实现接口时，它还隐式实现该接口的所有基接口。与此类似，接口的重新实现也同时隐式地对该接口的所有基接口进行重新实现。例如

interface IBase

{

void F();

}

interface IDerived: IBase

{

void G();

}

class C: IDerived

{

void IBase.F() {...}

void IDerived.G() {...}

}

class D: C, IDerived

{

public void F() {...}

public void G() {...}

}

这里，IDerived 的重新实现了重新实现了 IBase，并将 IBase.F 映射到 D.F 上。


13.4.5抽象类和接口.2016789

13.4.5 抽象类和接口

与非抽象类类似，抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是，允许抽象类将接口方法映射到抽象方法上。例如

interface IMethods

{

void F();

void G();

}

abstract class C: IMethods

{

public abstract void F();

public abstract void G();

}

这里，IMethods 的实现将 F 和 G 映射到抽象方法上，这些抽象方法必须在从 C 派生的非抽象类中重写。

注意：显式接口成员实现本身不能是抽象的，但是当然允许显式接口成员实现调用抽象方法。例如

interface IMethods

{

void F();

void G();

}

abstract class C: IMethods

{

void IMethods.F() { FF(); }

void IMethods.G() { GG(); }

protected abstract void FF();

protected abstract void GG();

}

这里，从 C 派生的非抽象类被要求重写 FF 和 GG，从而提供 IMethods 的实际实现。


14.枚举.2019489

14. 枚举

枚举类型是一种独特的值类型（第 4.1 节），它用于声明一组命名的常数。

在以下示例中：

enum Color

{

Red,

Green,

Blue

}

声明一个名为 Color 的枚举类型，它具有三个成员：Red、Green 和 Blue。


14.1枚举声明.2020744

14.1 枚举声明

枚举声明用于声明新的枚举类型。枚举声明以关键字 enum 开始，然后定义该枚举的名称、可访问性、基础类型和成员。

enum-declaration:（枚举声明：）

attributes opt enum-modifiers opt enum identifier enum-base opt enum-body ;opt（属性可选 枚举修饰符可选 enum 标识符 枚举基可选 枚举体 ;可选）

enum-base:（枚举基：）

; integral-type（; 整型）

enum-body:（枚举体：）

{ enum-member-declarations opt }（{ 枚举成员声明可选 }）
{ enum-member-declarations , }（{ 枚举成员声明 , }）

每个枚举类型都有一个相应的整型，称为该枚举类型的基础类型。此基础类型必须能够表示该枚举中定义的所有枚举数值。枚举声明可以显式地声明 byte、sbyte、short、ushort、int、uint、long 或 ulong 类型作为对应的基础类型。请注意 char 不能用作基础类型。没有显式地声明基础类型的枚举声明意味着所对应的基础类型是 int。

在以下示例中：

enum Color: long

{

Red,

Green,

Blue

}

声明了一个基础类型为 long 的枚举。开发人员可以像本示例一样选择使用 long 基础类型，以便能够使用在 long 范围内而不是在 int 范围内的值，或者保留此选项供将来使用。


14.2枚举修饰符.2026635

14.2 枚举修饰符

枚举声明可以根据需要包含一个枚举修饰符序列：

enum-modifiers:（枚举修饰符：）

enum-modifier（枚举修饰符）
enum-modifiers enum-modifier（枚举修饰符 枚举修饰符）

enum-modifier:（枚举修饰符：）

new
public
protected
internal
private

同一修饰符在一个枚举声明中多次出现属于编译时错误。

枚举声明的修饰符与类声明（第 10.1.1 节）的修饰符具有同样的意义。然而，请注意：在枚举声明中不允许使用 abstract 和 sealed 修饰符。枚举不能是抽象的，也不允许派生。


14.3枚举成员.2028537

14.3 枚举成员

枚举类型声明的体用于定义零个或多个枚举成员，这些成员是该枚举类型的命名常数。任意两个枚举成员不能具有相同的名称。

enum-member-declarations:（枚举成员声明：）

enum-member-declaration（枚举成员声明）
enum-member-declarations , enum-member-declaration（枚举成员声明 , 枚举成员声明）

enum-member-declaration:（枚举成员声明：）

attributes opt identifier（属性可选 标识符）
attributes opt identifier = constant-expression（属性可选 标识符 = 常数表达式）

每个枚举成员均具有相关联的常数值。此值的类型就是包含了它的那个枚举的基础类型。每个枚举成员的常数值必须在该枚举的基础类型的范围之内。在以下示例中：

enum Color: uint

{

Red = -1,

Green = -2,

Blue = -3

}

产生编译时错误，原因是常数值 -1、-2 和 –3 不在基础整型 uint 的范围内。

多个枚举成员可以共享同一个关联值。在以下示例中：

enum Color

{

Red,

Green,

Blue,

Max = Blue

}

显示一个枚举，其中的两个枚举成员（Blue 和 Max）具有相同的关联值。

一个枚举成员的关联值或隐式地、或显式地被赋值。如果枚举成员的声明中具有“常数表达式”初始值设定项，则该常数表达式的值（它隐式转换为枚举的基础类型）就是该枚举成员的关联值。如果枚举成员的声明不具有初始值设定项，则它的关联值按下面规则隐式地设置：

· 如果枚举成员是在枚举类型中声明的第一个枚举成员，则它的关联值为零。

· 否则，枚举成员的关联值是通过将前一个枚举成员（按照文本顺序）的关联值加 1 得到的。这样增加后的值必须在该基础类型可表示的值的范围内；否则，会出现编译时错误。

在以下示例中：

using System;

enum Color

{

Red,

Green = 10,

Blue

}

class Test

{

static void Main() {

Console.WriteLine(StringFromColor(Color.Red));

Console.WriteLine(StringFromColor(Color.Green));

Console.WriteLine(StringFromColor(Color.Blue));

}

static string StringFromColor(Color c) {

switch (c) {

case Color.Red:

return String.Format("Red = {0}", (int) c);

case Color.Green:

return String.Format("Green = {0}", (int) c);

case Color.Blue:

return String.Format("Blue = {0}", (int) c);

default:

return "Invalid color";

}

}

}

输出枚举成员名称和它们的关联值。输出为：

Red = 0

Green = 10

Blue = 11

原因如下：

· 枚举成员 Red 被自动赋予零值（因为它不具有初始值设定项并且是第一个枚举成员）。

· 枚举成员 Green 被显式赋予值 10。

· 枚举成员 Blue 被自动赋予比文本上位于它前面的成员大 1 的值。

枚举成员的关联值不能直接或间接地使用它自己的关联枚举成员的值。除了这个循环性限制外，枚举成员初始值设定项可以自由地引用其他的枚举成员初始值设定项，而不必考虑它们所在的文本位置的排列顺序。在枚举成员初始值设定项内，其他枚举成员的值始终被视为属于所对应的基础类型，因此在引用其他枚举成员时，没有必要使用强制转换。

在以下示例中：

enum Circular

{

A = B,

B

}

产生编译时错误，因为 A 和 B 的声明是循环的。A 显式依赖于 B，而 B 隐式依赖于 A。

枚举成员的命名方式和作用范围与类中的字段完全类似。枚举成员的范围是包含了它的枚举类型的体。在该范围内，枚举成员可以用它们的简单名称引用。在所有其他代码中，枚举成员的名称必须用它的枚举类型的名称限定。枚举成员不具有任何声明可访问性，如果一个枚举类型是可访问的，则它所含的所有枚举成员都是可访问的。


14.4System.Enum类型.2038960

Powered By Gavin http://www.CSharpWeb.com

14.4 System.Enum 类型

System.Enum 类型是所有枚举类型的抽象基类（它是一种与枚举类型的基础类型不同的独特类型），并且从 System.Enum 继承的成员在任何枚举类型中都可用。存在从任何枚举类型到 System.Enum 的装箱转换（第 4.3.1 节），并且存在从 System.Enum 到任何枚举类型的取消装箱转换（第 4.3.2 节）。

请注意 System.Enum 本身不是枚举类型。相反，它是一个类类型，所有枚举类型都是从它派生的。类型 System.Enum 从类型 System.ValueType（第 4.1.1 节）派生，而后者又从类型 object 派生。在运行时，类型 System.Enum 的值可以是 null 或是对任何枚举类型的装了箱的值的引用。


14.5枚举值和运算.2040142

Powered By Gavin http://www.CSharpWeb.com

14.5 枚举值和运算

每个枚举类型都定义了一个独特类型；需要使用显式枚举转换（第 6.2.2 节）在枚举类型和整型之间或在两个枚举类型之间进行转换。一个枚举类型的值域不受它的枚举成员限制。具体说来，一个枚举的基础类型的任何一个值都可以被强制转换为该枚举类型，成为该枚举类型的一个独特的有效值。

枚举成员所属的类型就是声明了它们的那个枚举（出现在其他枚举成员初始值设定项中时除外：请参见第 14.3 节）。在枚举类型 E 中声明且关联值为 v 的枚举成员的值为 (E)v。

下列运算符可以用在枚举类型的值上：==、!=、<、>、<=、>=（第 7.9.5 节）、+（第 7.7.4 节）、‑（第 7.7.5 节）、^、&、|（第 7.10.2 节）、~（第 7.6.4 节）、++、--（第 7.5.9 节）和（第 7.6.5 节）、sizeof（第 A.5.4 节）。


15.委托.2041833

Powered By Gavin http://www.CSharpWeb.com

15. 委托

委托是用来处理其他语言（如 C++、Pascal 和 Modula）需用函数指针来处理的情况的。不过与 C++ 函数指针不同，委托是完全面对对象的；另外，C++ 指针仅指向成员函数，而委托同时封装了对象实例和方法。

委托声明定义一个从 System.Delegate 类派生的类。委托实例封装一个调用列表，该列表列出一个或多个方法，其中每个方法均作为一个可调用实体来引用。对于实例方法，可调用实体由该方法和一个相关联的实例组成。对于静态方法，可调用实体仅由一个方法组成。用一个适当的参数集来调用一个委托实例，就是用此给定的参数集来调用该委托实例的每个可调用实体。

委托实例的一个有趣且有用的属性是：它不知道也不关心它所封装的方法所属的类；它所关心的仅限于这些方法必须与委托的类型兼容（第 15.1 节）。这使委托非常适合于“匿名”调用。


15.1委托声明.2043022

15.1 委托声明

委托声明是一种类型声明（第 9.5 节），它声明一个新的委托类型。

delegate-declaration:（委托声明：）

attributes opt delegate-modifiers opt delegate return-type identifier ( formal-parameter-list opt ) ;（属性可选 委托修饰符可选 delegate 返回类型 标识符 ( 形参表可选 ) ;）

delegate-modifiers:（委托修饰符：）

delegate-modifier（委托修饰符）
delegate-modifiers delegate-modifier（委托修饰符 委托修饰符）

delegate-modifier:（委托修饰符：）

new
public
protected
internal
private

同一修饰符在一个委托声明中多次出现属于编译时错误。

new 修饰符仅允许在其他类型中声明的委托上使用，在这种情况下该修饰符表示所声明的委托会隐藏具有相同名称的继承成员，详见第 10.2.2 节。

public、protected、internal 和 private 修饰符控制委托类型的可访问性。根据委托声明所在的上下文，可能不允许使用其中某些修饰符（第 3.5.1 节）。

上述的语法产生式中，标识符用于指定委托的类型名称。

可选的形参表用于指定委托的参数，而返回类型则指定委托的返回类型。如果下面两个条件都为真，则方法和委托类型是兼容的：

· 它们具有相同的参数数目，并且类型相同，顺序相同，参数修饰符也相同。

· 它们的返回类型相同。

C# 中的委托类型是名称等效的，而不是结构等效的。具体地说，对于两个委托类型，即使它们具有相同的参数列表和返回类型，仍被认为是不同的两个委托类型。不过，这样两个彼此不同的但结构上又相同的委托类型，它们的实例在比较时可以认为是相等关系（第 7.9.8 节）。

例如：

delegate int D1(int i, double d);

class A

{

public static int M1(int a, double b) {...}

}

class B

{

delegate int D2(int c, double d);

public static int M1(int f, double g) {...}

public static void M2(int k, double l) {...}

public static int M3(int g) {...}

public static void M4(int g) {...}

}

委托类型 D1 和 D2 都与方法 A.M1 和 B.M1 兼容，这是因为它们具有相同的返回类型和参数列表；但是，这些委托类型是两个不同的类型，所以它们是不可互换的。委托类型 D1 和 D2 与方法 B.M2、B.M3 和 B.M4 不兼容，这是因为它们具有不同的返回类型或参数列表。

声明一个委托类型的唯一方法是通过委托声明。委托类型是从 System.Delegate 派生的类类型。委托类型隐含为 sealed，所以不允许从一个委托类型派生任何类型。也不允许从 System.Delegate 派生非委托类类型。请注意：System.Delegate 本身不是委托类型；它是从中派生所有委托类型的类类型。

C# 设置了专门的语法用于委托类型的实例化和调用。除实例化外，所有可以应用于类或类实例的操作也可以相应地应用于委托类或委托实例。具体说来，可以通过通常的成员访问语法访问 System.Delegate 类型的成员。

委托实例所封装的方法集合称为调用列表。从某个方法创建一个委托实例时（第 15.2 节），该委托实例将封装此方法，此时，它的调用列表只包含一个进入点。但是，当组合两个非空委托实例时，它们的调用列表被连接在一起（按照左操作数在先、右操作数在后的顺序）以组成一个新的调用列表，它包含了两个或更多个“进入点”。

委托是使用二元 +（第 7.7.4 节）和 += 运算符（第 7.13.2 节）来组合的。可以使用一元 -（第 7.7.5 节）和 -= 运算符（第 7.13.2 节）将一个委托从委托组合中移除。委托间还可以进行比较以确定它们是否相等（第 7.9.8 节）。

下面的示例显示多个委托的实例化及其相应的调用列表：

delegate void D(int x);

class C

{

public static void M1(int i) {...}

public static void M2(int i) {...}

}

class Test

{

static void Main() {

D cd1 = new D(C.M1); // M1

D cd2 = new D(C.M2); // M2

D cd3 = cd1 + cd2; // M1 + M2

D cd4 = cd3 + cd1; // M1 + M2 + M1

D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2

}

}

实例化 cd1 和 cd2 时，它们分别封装一个方法。实例化 cd3 时，它的调用列表有两个方法 M1 和 M2，而且正是这样的顺序。cd4 的调用列表包含 M1、M2 和 M1，顺序与此相同。最后，cd5 的调用列表包含：M1、M2、M1、M1 和 M2，顺序与此相同。有关组合（以及移除）委托的更多示例，请参见第 15.3 节。


15.2委托实例化.2053744

15.2 委托实例化

委托的实例是由“委托创建表达式”（第 7.5.10.3 节）创建的。因此，新创建的委托实例将引用以下各项之一：

· “委托创建表达式”中引用的静态方法，或者

· “委托创建表达式”中引用的目标对象（此对象不能为 null）和实例方法，或者

· 另一个委托

例如：

delegate void D(int x);

class C

{

public static void M1(int i) {...}

public void M2(int i) {...}

}

class Test

{

static void Main() {

D cd1 = new D(C.M1); // static method

Test t = new C();

D cd2 = new D(t.M2); // instance method

D cd3 = new D(cd2); // another delegate

}

}

委托实例一旦被实例化，它将始终引用同一目标对象和方法。记住，当组合两个委托或者从一个委托移除另一个时，将产生一个新的委托，该委托具有它自己的调用列表；被组合或移除的委托的调用列表将保持不变。


15.3委托调用.2056377

15.3 委托调用

C# 为调用委托提供了专门的语法。当调用非空的、调用列表仅包含一个进入点的委托实例时，它调用调用列表中的方法，委托调用所使用的参数和返回的值均与该方法的对应项相同。有关委托调用的详细信息，请参见第 7.5.5.2 节。如果在对这样的委托进行调用期间发生异常，而且没有在被调用的方法内捕捉到该异常，则会在调用该委托的方法内继续搜索与该异常对应的 catch 子句，就像调用该委托的方法直接调用了该委托所引用的方法一样。

如果一个委托实例的调用列表包含多个进入点，那么调用这样的委托实例就是按顺序同步地调用调用列表中所列的各个方法。以这种方式调用的每个方法都使用相同的参数集，即提供给委托实例的参数集。如果这样的委托调用包含引用参数（第 10.5.1.2 节），那么每个方法调用都将使用对同一变量的引用；这样，若调用列表中有某个方法对该变量进行了更改，则调用列表中排在该方法之后的所有方法都会见到此变更。如果委托调用包含输出参数或一个返回值，则它们的最终值就是调用列表中最后一个方法调用所产生的结果。

如果在处理此类委托的调用期间发生异常，而且没有在正被调用的方法内捕捉到该异常，则会在调用该委托的方法内继续搜索与该异常对应的 catch 子句，此时，调用列表中排在后面的任何方法将不会被调用。

试图调用其值为 null 的委托实例将导致 System.NullReferenceException 类型的异常。

下列示例显示如何实例化、组合、移除和调用委托：

using System;

delegate void D(int x);

class C

{

public static void M1(int i) {

Console.WriteLine("C.M1: " + i);

}

public static void M2(int i) {

Console.WriteLine("C.M2: " + i);

}

public void M3(int i) {

Console.WriteLine("C.M3: " + i);

}

}

class Test

{

static void Main() {

D cd1 = new D(C.M1);

cd1(-1); // call M1

D cd2 = new D(C.M2);

cd2(-2); // call M2

D cd3 = cd1 + cd2;

cd3(10); // call M1 then M2

cd3 += cd1;

cd3(20); // call M1, M2, then M1

C c = new C();

D cd4 = new D(c.M3);

cd3 += cd4;

cd3(30); // call M1, M2, M1, then M3

cd3 -= cd1; // remove last M1

cd3(40); // call M1, M2, then M3

cd3 -= cd4;

cd3(50); // call M1 then M2

cd3 -= cd2;

cd3(60); // call M1

cd3 -= cd2; // impossible removal is benign

cd3(60); // call M1

cd3 -= cd1; // invocation list is empty so cd3 is null

// cd3(70); // System.NullReferenceException thrown

cd3 -= cd1; // impossible removal is benign

}

}

如语句 cd3 += cd1; 中所显示，委托可以多次出现在一个调用列表中。这种情况下，它每出现一次，就会被调用一次。在这样的调用列表中，当移除委托时，实际上移除的是调用列表中最后出现的那个委托实例。

就在执行最后一条语句 cd3 -= cd1; 之前，委托 cd3 引用了一个空的调用列表。试图从空的列表中移除委托（或者从非空列表中移除表中没有的委托）不算是错误。

产生的输出为：

C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60


16.异常.2063837

16. 异常

C# 中的异常用于处理系统级和应用程序级的错误状态，它是一种结构化的、统一的和类型安全的处理机制。C# 中的异常机制非常类似于 C++ 的异常机制，但是有一些重要的区别：

· 在 C# 中，所有的异常必须由从 System.Exception 派生的类类型的实例来表示。在 C++ 中，可以使用任何类型的任何值表示异常。

· 在 C# 中，利用 finally 块（第 8.10 节）可编写在正常执行和异常情况下都将执行的终止代码。在 C++ 中，很难在不重复代码的情况下编写这样的代码。

· 在 C# 中，系统级的异常如溢出、被零除和 null 等都对应地定义了与其匹配的异常类，并且与应用程序级的错误状态处于同等地位。


16.1导致异常的原因.2065466

Powered By Gavin http://www.CSharpWeb.com

16.1 导致异常的原因

可以以两种不同的方式引发异常。

· throw 语句（第 8.9.5 节）用于立即无条件地引发异常。控制永远不会到达紧跟在 throw 后面的语句。

· 在处理 C# 语句和表达式的过程中，有时会出现一些例外情况，使某些操作无法正常完成，此时就会引发一个异常。例如，整数除法运算（第 7.7.2 节）中，如果分母为零，就会引发一个 System.DivideByZeroException。有关可能以此方式引发的各种异常的列表，请参见第 16.4 节。


16.2System.Exception类.2066651

16.2 System.Exception 类

System.Exception 类是所有异常的基类型。此类具有一些所有异常共享的值得注意的属性：

· Message 是 string 类型的一个只读属性，它包含关于所发生异常的原因的描述（适合人类阅读）。

· InnerException 是 Exception 类型的一个只读属性。如果它的值不是 null，则它所引用的是导致了当前异常的那个异常，即表示当前异常是在处理那个 InnerException（所引用是异常）的 catch 块中被引发的。否则，它的值为 null，则表示该异常不是由另一个异常引发的。以这种方式链接在一起的异常对象的数目可以是任意的。

这些属性的值可以在调用 System.Exception 的实例构造函数时指定。


16.3异常的处理方式.2068177

16.3 异常的处理方式

异常是由 try 语句（第 8.10 节）处理的。

发生异常时，系统将搜索可以处理该异常的最近的 catch 子句（根据该异常的运行时类型来选择）。首先，搜索当前的方法以查找一个词法上封闭着它的 try 语句，并按顺序考察与该 try 语句相关联的每个 catch 子句。如果上述操作失败，则在调用了当前方法的方法中，搜索在词法上封闭的着当前方法调用代码位置的 try 语句。此搜索将一直进行下去，直到找到可以处理当前异常的 catch 子句（该子句指定一个异常类，它与当前引发该异常的运行时类型属于同一个类或是该运行时类型所属类的一个基类。注意，没有指定异常类的 catch 子句可以处理任何异常。

找到匹配的 catch 子句后，系统将把控制转移到该 catch 子句的第一条语句。在 catch 子句的执行开始前，系统将首先按顺序执行嵌套在捕捉到该异常的 try 语句里面的所有 try 语句所对应的全部 finally 子句。

如果没有找到匹配的 catch 子句，则发生下列两种情况之一：

· 如果对匹配的 catch 子句的搜索到达一个静态构造函数（第 10.11 节）或静态字段初始值设定项，则在导致调用该静态构造函数的代码位置引发 System.TypeInitializationException。该 System.TypeInitializationException 的内部异常将包含最初引发的异常。

· 如果对匹配的 catch 子句的搜索到达最初启动当前线程的代码处，则该线程的执行就会终止。此类终止会产生什么影响，应由实现来定义。

特别值得注意的是在析构函数执行过程中发生的异常。如果在析构函数执行过程中发生异常且该异常未被捕获，则将终止该析构函数的执行，并调用它的基类的析构函数（如果有的话）。如果没有基类（如 object 类型中的情况），或者如果没有基类析构函数，则该异常将被忽略。


16.4公共异常类.2070878

16.4 公共异常类

下列异常由某些 C# 操作引发。

System.ArithmeticException | 在算术运算期间发生的异常（如 System.DivideByZeroException 和 System.OverflowException）的基类。
System.ArrayTypeMismatchException | 当存储一个数组时，如果由于被存储的元素的实际类型与数组的实际类型不兼容而导致存储失败，就会引发此异常。
System.DivideByZeroException | 在试图用零除整数值时引发。
System.IndexOutOfRangeException | 在试图使用小于零或超出数组界限的下标索引数组时引发。
System.InvalidCastException | 当从基类型或接口到派生类型的显式转换在运行时失败时，就会引发此异常。
System.NullReferenceException | 在需要使用引用对象的场合，如果使用 null 引用，就会引发此异常。
System.OutOfMemoryException | 在分配内存（通过 new）的尝试失败时引发。
System.OverflowException | 在 checked 上下文中的算术运算溢出时引发。
System.StackOverflowException | 当执行堆栈由于保存了太多挂起的方法调用而耗尽时，就会引发此异常；这通常表明存在非常深或无限的递归。
System.TypeInitializationException | 在静态构造函数引发异常并且没有可以捕捉到它的 catch 子句时引发。


17.属性.2076782

Powered By Gavin http://www.CSharpWeb.com

17. 属性

C# 语言的一个重要特征是使程序员能够为程序中定义的各种实体附加一些说明性信息。例如，类中方法的可访问性是通过用“方法修饰符”public、protected、internal 和 private 来说明它而指定的。

C# 使程序员可以创造说明性信息的新的种类，称为属性。然后，程序员可以将这种属性附加到各种程序实体，而且在运行时环境中还可以检索这些属性信息。例如，一个框架可以定义一个名为 HelpAttribute 的属性，该属性可以放在某些程序元素（如类和方法）上，以提供从这些程序元素到其文档说明的映射。

属性是通过属性类（第 17.1 节）的声明定义的，属性类可以具有定位和命名参数（第 17.1.2 节）。属性是使用属性规范（第 17.2 节）附加到 C# 程序中的实体上的，而且可以在运行时作为属性实例（第 17.3 节）检索。


17.1属性类.2078091

Powered By Gavin http://www.CSharpWeb.com

17.1 属性类

从抽象类 System.Attribute 派生的类（不论是直接的还是间接的）都称为属性类。一个关于属性类的声明定义一种新属性，它可以被放置在其他声明上。按照约定，属性类的名称均带有 Attribute 后缀。使用属性时可以包含或省略此后缀。


17.1.1属性用法.2078697

17.1.1 属性用法

AttributeUsage 属性（第 17.4.1 节）用于描述使用属性类的方式。

AttributeUsage 具有一个定位参数（第 17.1.2 节），该参数使属性类能够指定自己可以用在那种声明上。在以下示例中：

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]

public class SimpleAttribute: Attribute

{

...

}

定义了一个名为 SimpleAttribute 的属性类，此属性类只能放在类声明和接口声明上。在以下示例中：

[Simple] class Class1 {...}

[Simple] interface Interface1 {...}

显示了 Simple 属性的几种用法。虽然此属性是用名称 SimpleAttribute 定义的，但在使用时可以省略 Attribute 后缀，从而得到简称 Simple。因此，上例在语义上等效于：

[SimpleAttribute] class Class1 {...}

[SimpleAttribute] interface Interface1 {...}

AttributeUsage 还具有一个名为 AllowMultiple 的命名参数（第 17.1.2 节），此参数用于说明对于某个给定实体，是否可以多次使用该属性。如果属性类的 AllowMultiple 为 true，则此属性类是多次性属性类，可以在一个实体上多次被应用。如果属性类的 AllowMultiple 为 false 或未指定的，则此属性类是一次性属性类，在一个实体上最多只能使用一次。

在以下示例中：

using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]

public class AuthorAttribute: Attribute

{

private string name;

public AuthorAttribute(string name) {

this.name = name;

}

public string Name {

get { return name; }

}

}

定义了一个名为 AuthorAttribute 的多次性属性类。在以下示例中：

[Author("Brian Kernighan"), Author("Dennis Ritchie")]

class Class1

{

...

}

显示了一个两次使用 Author 属性的类声明。

AttributeUsage 具有另一个名为 Inherited 的命名参数，此参数指示在基类上指定该属性时，该属性是否也会被从此基类派生的类所继承。如果属性类的 Inherited 为 true，则该属性会被继承。如果属性类的 Inherited 为 false 或者未指定，那么该属性不会被继承。

没有附加 AttributeUsage 属性的属性类 X，例如

using System;

class X: Attribute {...}

等效于下面的内容：

using System;

[AttributeUsage(

AttributeTargets.All,

AllowMultiple = false,

Inherited = true)

]

class X: Attribute {...}


17.1.2定位和命名参数.2083785

17.1.2 定位和命名参数

属性类可以具有定位参数和命名参数。属性类的每个公共实例构造函数为该属性类定义一个有效的定位参数序列。属性类的每个非静态公共读写字段和属性为该属性类定义一个命名参数。

在以下示例中：

using System;

[AttributeUsage(AttributeTargets.Class)]

public class HelpAttribute: Attribute

{

public HelpAttribute(string url) { // Positional parameter

...

}

public string Topic { // Named parameter

get {...}

set {...}

}

public string Url {

get {...}

}

}

定义了一个名为 HelpAttribute 的属性类，它具有一个定位参数 (url) 和一个命名参数 (Topic)。虽然 Url 属性是非静态的和公共的，但由于它不是读写的，因此它并不定义命名参数。

此属性类可以如下方式使用：

[Help("http://www.example.com/.../Class1.htm")]

class Class1

{

...

}

[Help("http://www.example.com/.../Misc.htm", Topic = "Class2")]

class Class2

{

...

}


17.1.3属性参数类型.2086918

17.1.3 属性参数类型

属性类的定位参数和命名参数的类型仅限于属性参数类型，它们是：

· 下列类型之一：bool、byte、char、double、float、int、long、short 和 string。

· 类型 object。

· 类型 System.Type。

· 枚举类型，前提是该枚举类型具有 public 可访问性，而且所有嵌套着它的类型（如果有的话）也必须具有 public 可访问性（第 17.2 节）。

· 以上类型的一维数组。


17.2属性专用化.2088971

17.2 属性专用化

属性专用化就是将以前定义的属性应用到某个声明上。属性本身是一段附加说明性信息，可以把它指定给某个声明。可以在全局范围指定属性（即，在包含程序集或模块上指定属性），也可以为下列各项指定属性：类型声明（第 9.5 节）、类成员说明（第 10.2 节）、接口成员声明（第 13.2 节）、结构成员声明（第 11.2 节）、枚举成员声明（第 14.3 节）、属性的访问器声明（第 10.6.2 节）、事件访问器声明（第 10.7.1 节）和形参表（第 10.5.1 节）。

属性是在属性节中指定的。属性节由一对方括号组成，此方括号括着一个用逗号分隔的、含有一个或多个属性的列表。在这类列表中以何种顺序指定属性，以及附加到同一程序实体的属性节以何种顺序排列等细节并不重要。例如，属性专用化 [A][B]、[B][A]、[A, B] 和 [B, A] 是等效的。

global-attributes:（全局属性：）

global-attribute-sections（全局属性节）

global-attribute-sections:（全局属性节：）

global-attribute-section（全局属性节）

global-attribute-sections global-attribute-section（全局属性节 全局属性节）

global-attribute-section:（全局属性节：）

[ global-attribute-target-specifier attribute-list ]（[ 全局属性目标说明符 属性列表 ]）

[ global-attribute-target-specifier attribute-list , ]（[ 全局属性目标说明符 属性列表 , ]）

global-attribute-target-specifier:（全局属性目标说明符：）

global-attribute-target:（全局属性目标：）

global-attribute-target:（全局属性目标：）

assembly（程序集）

module（模块）

attributes:（属性：）

attribute-sections（属性节）

attribute-sections:（属性节：）

attribute-section（属性节）

attribute-sections attribute-section（属性节 属性节）

attribute-section:（属性节：）

[ attribute-target-specifieropt attribute-list ]（[ 属性目标说明符可选 属性列表 ]）

[ attribute-target-specifieropt attribute-list , ]（[ 属性目标说明符可选 属性列表 , ]）

attribute-target-specifier:（属性目标说明符：）

attribute-target :（属性目标 :）

attribute-target:（属性目标：）

field（字段）

event（事件）

method（方法）

param（参数）

property（属性）

return（返回）

type（类型）

attribute-list:（属性列表：）

attribute（属性）

attribute-list , attribute（属性列表 , 属性）

attribute:（属性：）

attribute-name attribute-argumentsopt（属性名 属性参数可选）

attribute-name:（属性名：）

type-name（类型名）

attribute-arguments:（属性参数：）

( positional-argument-list opt )（( 定位参数列表可选 )）

( positional-argument-list , named-argument-list )（( 定位参数列表 , 命名参数列表 )）

( named-argument-list )（( 命名参数列表 )）

positional-argument-list:（定位参数列表：）

positional-argument（定位参数）

positional-argument-list , positional-argument（定位参数列表 , 定位参数）

positional-argument:（定位参数：）

attribute-argument-expression（属性参数表达式）

named-argument-list:（命名参数列表：）

named-argument（命名参数）

named-argument-list , named-argument（命名参数列表 , 命名参数）

named-argument:（命名参数：）

identifier = attribute-argument-expression（标识符 = 属性参数表达式）

attribute-argument-expression:（属性参数表达式：）

expression（表达式）

如上所述，属性由一个“属性名”和一个可选的定位和命名参数列表组成。定位参数（如果有的话）列在命名参数前面。定位参数包含一个属性参数表达式；命名参数包含一个名称，名称后跟一个等号和一个属性参数表达式，这两种参数都受简单赋值规则约束。命名参数的排列顺序无关紧要。

属性名用于标识属性类。如果“属性名”的形式等同于一个“类型名”，则此名称必须引用一个属性类。否则将发生编译时错误。在以下示例中：

class Class1 {}

[Class1] class Class2 {} // Error

产生编译时错误，因为它试图将 Class1 用作属性类，而 Class1 并不是一个属性类。

某些上下文允许将一个属性指定给多个目标。程序中可以利用“属性目标说明符”来显式地指定目标。属性放置在全局级别中时，则需要全局属性目标说明符。对于所有其他位置上的属性，则采用系统提供的合理的默认值，但是在某些目标不明确的情况下可以使用属性目标说明符来确认或重写默认值，也可以在目标明确的情况下使用属性目标说明符来确认默认值。因此，除了在全局级别中时通常可以省略属性目标说明符。对于可能造成不明确性的上下文，按下述规则处理：

· 在全局范围指定的属性可以应用于目标程序集或目标模块。系统没有为此上下文提供默认形式，所以在此上下文中始终需要一个“属性目标说明符”。如果存在 assembly“属性目标说明符”，则表明此属性适用于指定的目标程序集；如果存在 module“属性目标说明符”，则表明此属性适用于指定的目标模块。

· 在委托声明上指定的属性，或者适用于所声明的委托，或者适用于它的返回值。如果不存在“属性目标说明符”，则此属性适用于该委托。如果存在 type“属性目标说明符”，则表明此属性适用于该委托；如果存在 return“属性目标说明符”，则表明此属性适用于返回值。

· 在方法声明上指定的属性，或者适用于所声明的方法，或者适用于它的返回值。如果不存在“属性目标说明符”，则此属性适用于方法。如果存在 method“属性目标说明符”，则表明此属性适用于方法；如果存在 return“属性目标说明符”，则表明此属性适用于返回值。

· 在运算符声明上指定的属性，或者适用于所声明的运算符，或者适用于它的返回值。如果不存在“属性目标说明符”，则此属性适用于该运算符。如果存在 method“属性目标说明符”，则表明此属性适用于该运算符；如果存在 return“属性目标说明符”，则表明此属性适用于返回值。

· 对于在省略了事件访问器的事件声明上指定的属性，它的目标对象有三种可能的选择：所声明的事件；与该事件关联的字段（如果该事件是非抽象事件的话）；与该事件关联的 add 和 remove 方法。如果不存在“属性目标说明符”，则此属性适用于该事件。如果存在 event“属性目标说明符”，则表明此属性适用于该事件；如果存在 field“属性目标说明符”，则表明此属性适用于该字段；而如果存在 method“属性目标说明符”，则表明此属性适用于这些方法。

· 在属性或索引器声明中的 get 访问器声明上指定的属性，或者适用于该访问器关联的方法，或者适用于它的返回值。如果不存在“属性目标说明符”，则此属性适用于方法。如果存在 method“属性目标说明符”，则表明此属性适用于方法；如果存在 return“属性目标说明符”，则表明此属性适用于返回值。

· 在属性或索引器声明中的 set 访问器上指定的属性，或者可适用于该访问器关联的方法，或者适用于它的独立的隐式参数。如果不存在“属性目标说明符”，则此属性适用于方法。如果存在 method“属性目标说明符”，则此属性适用于方法；如果存在 param“属性目标说明符”，则此属性适用于参数。

· 在事件声明的添加或移除访问器声明上指定的属性，或者适用于该访问器关联的方法，或者适用于它的独立参数。如果不存在“属性目标说明符”，则此属性适用于方法。如果存在 method“属性目标说明符”，则此属性适用于方法；如果存在 param“属性目标说明符”，则此属性适用于参数。

在其他上下文中，允许包含一个“属性目标说明符”，但这样做是没有必要的。例如，类声明中的属性既可以包括也可以省略说明符 type：

[type: Author("Brian Kernighan")]

class Class1 {}

[Author("Dennis Ritchie")]

class Class2 {}

如果指定了无效的属性目标说明符，则会发生错误。例如，不能将说明符 param 用在类声明中：

[param: Author("Brian Kernighan")] // Error

class Class1 {}

按照约定，属性类的名称均带有 Attribute 后缀。“类型名”形式的“属性名”既可以包含也可以省略此后缀。如果发现属性类中同时出现带和不带此后缀的名称，则引用时就可能出现多义性，从而导致运行时错误。如果在拼写属性名时，明确说明其最右边的标识符为逐字标识符（第 2.4.2 节），则它仅匹配没有后缀的属性，从而能够解析这类多义性。在以下示例中：

using System;

[AttributeUsage(AttributeTargets.All)]

public class X: Attribute

{}

[AttributeUsage(AttributeTargets.All)]

public class XAttribute: Attribute

{}

[X]                  // Error: ambiguity

class Class1 {}

[XAttribute]         // Refers to XAttribute

class Class2 {}

[@X]                  // Refers to X

class Class3 {}

[@XAttribute]         // Refers to XAttribute

class Class4 {}

显示两个分别名为 X 和 XAttribute 的属性类。属性 [X] 含义不明确，因为它既可引用 X 也可引用 XAttribute。使用逐字标识符能够在这种极少见的情况下表明确切的意图。属性 [XAttribute] 是明确的（尽管在存在名为 XAttributeAttribute 的属性类时它将是不明确的！）。如果移除了类 X 的声明，那么上述两个属性都将引用名为 XAttribute 的属性类，如下所示：

using System;

[AttributeUsage(AttributeTargets.All)]

public class XAttribute: Attribute

{}

[X] // Refers to XAttribute

class Class1 {}

[XAttribute] // Refers to XAttribute

class Class2 {}

[@X] // Error: no attribute named "X"

class Class3 {}

在同一个实体中多次使用一次性属性类属于编译时错误。在以下示例中：

using System;

[AttributeUsage(AttributeTargets.Class)]

public class HelpStringAttribute: Attribute

{

string value;

public HelpStringAttribute(string value) {

this.value = value;

}

public string Value {

get {...}

}

}

[HelpString("Description of Class1")]

[HelpString("Another description of Class1")]

public class Class1 {}

产生编译时错误，因为它试图在 Class1 的声明中多次使用一次性属性类 HelpString。

如果表达式 E 符合下列所有条件的话，则表达式 E 为“属性参数表达式”：

· E 的类型是属性参数类型（第 17.1.3 节）。

· 在编译时，E 的值可以解析为下列之一：

· 常数值。

· System.Type 对象。

· 属性参数表达式的一维数组。

例如：

using System;

[AttributeUsage(AttributeTargets.Class)]

public class TestAttribute: Attribute

{

public int P1 {

get {...}

set {...}

}

public Type P2 {

get {...}

set {...}

}

public object P3 {

get {...}

set {...}

}

}

[Test(P1 = 1234, P3 = new int[] {1, 3, 5}, P2 = typeof(float))]

class MyClass {}


17.3属性实例.2119302

Powered By Gavin http://www.CSharpWeb.com

17.3 属性实例

属性实例是一个实例，用于在运行时表示属性。属性是用属性类、定位参数和命名参数定义的。属性实例是一个属性类的实例，它是用定位参数和命名参数初始化后得到的。

属性实例的检索涉及编译时和运行时处理，详见后面几节中的介绍。


17.3.1属性的编译.2119866

17.3.1 属性的编译

对于一个具有属性类 T、定位参数列表 P 和命名参数列表 N 的属性的编译过程由下列步骤组成：

· 遵循形式为 new T(P) 的“对象创建表达式”的编译规则所规定的步骤进行编译时处理。这些步骤或者导致编译时错误，或者确定 T 上的可以在运行时调用的实例构造函数 C。

· 如果 C 不具有公共可访问性，则发生编译时错误。

· 对于 N 中的每个命名参数 Arg：

· 将 Name 设为命名参数 Arg 的标识符。

· Name 必须标识 T 中的一个非静态读写 public 字段或属性。如果 T 没有这样的字段或属性，则发生编译时错误。

· 保存下列信息：属性类 T、T 上的实例构造函数 C、定位参数列表 P 和命名参数列表 N，以供在运行时实例化该属性时调用。


17.3.2属性实例的运行时检索.2122568

17.3.2 属性实例的运行时检索

对一个属性进行编译后，会产生一个属性类 T、一个 T 上的实例构造函数 C、一个定位参数列表 P 和一个命名参数列表 N。给定了上述信息后，就可以在运行时使用下列步骤进行检索来生成一个属性实例：

· 遵循执行 new T(P) 形式的“对象创建表达式”（使用在编译时确定的实例构造函数 C）的运行时处理步骤。这些步骤或者导致异常，或者产生 T 的一个实例 O。

· 对于 N 中的每个命名参数 Arg，按以下顺序进行处理：

· 将 Name 设为命名参数 Arg 的标识符。如果 Name 标识的不是 O 上的一个非静态读写 public 字段或属性，则引发异常。

· 将 Value 设为 Arg 的“属性参数表达式”的计算结果。

· 如果 Name 标识 O 上的一个字段，则将此字段赋值为 Value。

· 否则，Name 就标识 O 上的一个属性。将此属性设置为 Value。

· 结果为 O，它是已经用定位参数列表 P 和命名参数列表 N 初始化了的属性类 T 的一个实例。


17.4保留属性.2125868

17.4 保留属性

少数属性以某种方式影响语言。这些属性包括：

· System.AttributeUsageAttribute（第 17.4.1 节），它用于描述可以以何种方式使用属性类。

· System.Diagnostics.ConditionalAttribute（第 17.4.2 节），它用于定义条件方法。

· System.ObsoleteAttribute（第 17.4.3 节），它用于将某个成员标记为已过时。


17.4.1AttributeUsage属性.2127164

17.4.1 AttributeUsage 属性

AttributeUsage 属性用于描述使用属性类的方式。

用 AttributeUsage 属性修饰的类必须直接或间接从 System.Attribute 派生。否则将发生编译时错误。

namespace System

{

[AttributeUsage(AttributeTargets.Class)]

public class AttributeUsageAttribute: Attribute

{

public AttributeUsageAttribute(AttributeTargets validOn) {...}

public virtual bool AllowMultiple { get {...} set {...} }

public virtual bool Inherited { get {...} set {...} }

public virtual AttributeTargets ValidOn { get {...} }

}

public enum AttributeTargets

{

Assembly = 0x0001,

Module = 0x0002,

Class = 0x0004,

Struct = 0x0008,

Enum = 0x0010,

Constructor = 0x0020,

Method = 0x0040,

Property = 0x0080,

Field = 0x0100,

Event = 0x0200,

Interface = 0x0400,

Parameter = 0x0800,

Delegate = 0x1000,

ReturnValue = 0x2000,

All = Assembly | Module | Class | Struct | Enum | Constructor |

Method | Property | Field | Event | Interface | Parameter |

Delegate | ReturnValue

}

}


17.4.2Conditional属性.2131304

17.4.2 Conditional 属性

利用 Conditional 属性，程序员可以定义条件方法。Conditional 属性通过测试条件编译符号来确定适用的条件。当运行到一个条件方法调用时，是否执行该调用，要根据出现该调用时是否已定义了此符号来确定。如果定义了此符号，则执行该调用；否则省略该调用（包括对调用的参数的计算）。

namespace System.Diagnostics

{

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]

public class ConditionalAttribute: Attribute

{

public ConditionalAttribute(string conditionString) {...}

public string ConditionString { get {...} }

}

}

条件方法要受到以下限制：

· 条件方法必须是类声明或结构声明中的方法。如果在接口声明中的方法上指定 Conditional 属性，将出现编译时错误。

· 条件方法必须具有 void 返回类型。

· 不能用 override 修饰符标记条件方法。但是，可以用 virtual 修饰符标记条件方法。此类方法的重写方法隐含为有条件的方法，而且不能用 Conditional 属性显式标记。

· 条件方法不能是接口方法的实现。否则将发生编译时错误。

此外，如果条件方法用在“委托创建表达式”中，也会发生编译时错误。在以下示例中：

#define DEBUG

using System;

using System.Diagnostics;

class Class1

{

[Conditional("DEBUG")]

public static void M() {

Console.WriteLine("Executed Class1.M");

}

}

class Class2

{

public static void Test() {

Class1.M();

}

}

将 Class1.M 声明为条件方法。Class2 的 Test 方法调用此方法。由于定义了条件编译符号 DEBUG，如果调用 Class2.Test，那么它将调用 M。如果尚未定义符号 DEBUG，那么 Class2.Test 将不会调用 Class1.M。

一定要注意包含或排除对条件方法的调用是由该调用所在处的条件编译符号控制的。在下面的示例中

文件 class1.cs：

using System.Diagnostics;

class Class1

{

[Conditional("DEBUG")]

public static void F() {

Console.WriteLine("Executed Class1.F");

}

}

文件 class2.cs：

#define DEBUG

class Class2

{

public static void G() {

Class1.F(); // F is called

}

}

文件 class3.cs：

#undef DEBUG

class Class3

{

public static void H() {

Class1.F(); // F is not called

}

}

类 Class2 和 Class3 分别包含对条件方法 Class1.F 的调用，根据是否定义了 DEBUG，此调用是有条件的。由于在 Class2 的上下文中定义了此符号而在 Class3 的上下文中没有定义，因此在 Class2 中包含了对 F 的调用，而在 Class3 中省略了对 F 的调用。

在继承链中使用条件方法可能引起混乱。通过 base.M 形式的 base 对条件方法进行的调用受正常条件方法调用规则的限制。在下面的示例中

文件 class1.cs：

using System;

using System.Diagnostics;

class Class1

{

[Conditional("DEBUG")]

public virtual void M() {

Console.WriteLine("Class1.M executed");

}

}

文件 class2.cs：

using System;

class Class2: Class1

{

public override void M() {

Console.WriteLine("Class2.M executed");

base.M(); // base.M is not called!

}

}

文件 class3.cs：

#define DEBUG

using System;

class Class3

{

public static void Test() {

Class2 c = new Class2();

c.M(); // M is called

}

}

Class2 包括一个对在其基类中定义的 M 的调用。此调用被省略，因为基方法是条件性的，依赖于符号 DEBUG 是否存在，而该符号在此处没有定义。因此，该方法仅向控制台写入“Class2.M executed”。审慎使用 pp 声明可以消除这类问题。


17.4.3Obsolete属性.2141478

17.4.3 Obsolete 属性

Obsolete 属性用于标记不应该再使用的类型和类型成员。

namespace System

{

[AttributeUsage(

AttributeTargets.Class |

AttributeTargets.Struct |

AttributeTargets.Enum |

AttributeTargets.Interface |

AttributeTargets.Delegate |

AttributeTargets.Method |

AttributeTargets.Constructor |

AttributeTargets.Property |

AttributeTargets.Field |

AttributeTargets.Event,

Inherited = false)

]

public class ObsoleteAttribute: Attribute

{

public ObsoleteAttribute() {...}

public ObsoleteAttribute(string message) {...}

public ObsoleteAttribute(string message, bool error) {...}

public string Message { get {...} }

public bool IsError { get {...} }

}

}

如果程序使用由 Obsolete 属性修饰的类型或成员，则编译器将发出警告或错误信息。具体说来，如果没有提供错误参数，或者如果提供了错误参数但该错误参数的值为 false，则编译器将发出警告。如果指定了错误参数并且该错误参数的值为 true，则会引发一个编译时错误。

在下面的示例中

[Obsolete("This class is obsolete; use class B instead")]

class A

{

public void F() {}

}

class B

{

public void F() {}

}

class Test

{

static void Main() {

A a = new A(); // Warning

a.F();

}

}

类 A 是用 Obsolete 属性修饰的。Main 的代码中，每次使用 A 时均会导致一个包含指定信息“This class is obsolete; use class B instead”的警告。


17.5交互操作的属性.2146113

Powered By Gavin http://www.CSharpWeb.com

17.5 交互操作的属性

注意 本节仅适用于 C# 的 Microsoft .NET 实现。


17.5.1与COM和Win32组件的交互操作.2146620

Powered By Gavin http://www.CSharpWeb.com

17.5.1 与 COM 和 Win32 组件的交互操作

.NET 运行库提供了大量属性，这些属性使 C# 程序能够与使用 COM 和 Win32 DLL 编写的组件交互操作。例如，可以在 static extern 方法上使用 DllImport 属性来表示该方法的实现应该到 Win32 DLL 中去查找。这些属性可在 System.Runtime.InteropServices 命名空间中找到，关于这些属性的详细文档在 .NET 运行库文档中。


17.5.2与其他.NET语言的交互操作.2147590

Powered By Gavin http://www.CSharpWeb.com

17.5.2 与其他 .NET 语言的交互操作

以下章节描述了 IndexerName 属性。


17.5.2.1IndexerName属性.2148041

17.5.2.1 IndexerName 属性

索引器是利用索引属性在 .NET 中实现的，并且具有一个属于 .NET 元数据的名称。如果索引器没有被指定 IndexerName 属性，则默认情况下将使用名称 Item。IndexerName 属性使开发人员可以重写此默认名称并指定不同的名称。

namespace System.Runtime.CompilerServices.CSharp

{

[AttributeUsage(AttributeTargets.Property)]

public class IndexerNameAttribute: Attribute

{

public IndexerNameAttribute(string indexerName) {...}

public string Value { get {...} }

}

}


A.不安全代码.2149608

A. 不安全代码

如前面几章所定义，核心 C# 语言没有将指针列入它所支持的数据类型，从而与 C 和 C++ 有着显著的区别。作为替代，C# 提供了各种引用类型，并能够创建可由垃圾回收器管理的对象。这种设计结合其他功能，使 C# 成为比 C 或 C++ 安全得多的语言。在核心 C# 语言中，干脆不可能有未初始化的变量、“虚”指针或者超过数组的边界对其进行索引的表达式。这样，以往总是不断地烦扰 C 和 C++ 程序的一系列错误就不会再出现了。

尽管实际上对 C 或 C++ 中的每种指针类型构造，C# 都设置了与之对应的引用类型，但仍然会有一些场合需要访问指针类型。例如，当需要与基础操作系统进行交互、访问内存映射设备，或实现一些以时间为关键的算法时，若没有访问指针的手段，就不可能或者至少很难完成。为了满足这样的需求，C# 提供了编写不安全代码的能力。

在不安全代码中，可以声明和操作指针，可以在指针和整型之间执行转换，还可以获取变量的地址，等等。在某种意义上，编写不安全代码很像在 C# 程序中编写 C 代码。

无论从开发人员还是从用户角度来看，不安全代码事实上都是一种“安全”功能。不安全代码必须用修饰符 unsafe 明确地标记，这样开发人员就不会误用不安全功能，而执行引擎将确保不会在不受信任的环境中执行不安全代码。


A.1不安全上下文.2151251

A.1 不安全上下文

C# 的不安全功能仅用于不安全上下文中。不安全上下文是通过在类型或成员的声明中包含一个 unsafe 修饰符或者通过使用“不安全语句”引入的：

· 类、结构、接口或委托的声明可以包含一个 unsafe 修饰符，在这种情况下，该类型声明的整个文本范围（包括类、结构或接口的体）被认为是不安全上下文。

· 在字段、方法、属性、事件、索引器、运算符、实例构造函数、析构函数或静态构造函数的声明中，也可以包含一个 unsafe 修饰符，在这种情况下，该成员声明的整个文本范围被认为是不安全上下文。

· “不安全语句”使得可以在“块”内使用不安全上下文。该语句关联的“块”的整个文本范围被认为是不安全上下文。

下面显示了关联的语法扩展。为简洁起见，用省略号 (...) 表示前几章中出现过的产生式。

class-modifier:（类修饰符：）

...
unsafe

struct-modifier:（结构修饰符：）

...
unsafe

interface-modifier:（接口修饰符：）

...
unsafe

delegate-modifier:（委托修饰符：）

...
unsafe

field-modifier:（字段修饰符：）

...
unsafe

method-modifier:（方法修饰符：）

...
unsafe

property-modifier:（属性修饰符：）

...
unsafe

event-modifier:（事件修饰符：）

...
unsafe

indexer-modifier:（索引器修饰符：）

...
unsafe

operator-modifier:（运算符修饰符：）

...
unsafe

constructor-modifier:（构造函数修饰符：）

...
unsafe

destructor-declaration:（析构函数声明：）

attributes opt externopt unsafeopt ~ identifier ( ) destructor-body（属性可选 extern可选 unsafe可选 ~ 标识符 ( ) 析构函数体）
attributes opt unsafeopt externopt ~ identifier ( ) destructor-body（属性可选 unsafe可选 extern可选 ~ 标识符 ( ) 析构函数体）

static-constructor-declaration:（静态构造函数声明：）

attributes opt externopt unsafeopt static identifier ( ) static-constructor-body（属性可选 extern可选 unsafe可选 static 标识符 ( ) 静态构造函数体）
attributes opt unsafeopt externopt static identifier ( ) static-constructor-body（属性可选 unsafe可选 extern可选 static 标识符 ( ) 静态构造函数体）

embedded-statement:（嵌入语句：）

...
unsafe-statement（不安全语句）

unsafe-statement:（不安全语句：）

unsafe block（unsafe 块）

在下面的示例中

public unsafe struct Node

{

public int Value;

public Node* Left;

public Node* Right;

}

在结构声明中指定的 unsafe 修饰符导致该结构声明的整个文本范围成为不安全上下文。因此，可以将 Left 和 Right 字段声明为指针类型的。上面的示例还可以编写为

public struct Node

{

public int Value;

public unsafe Node* Left;

public unsafe Node* Right;

}

此处，字段声明中的 unsafe 修饰符导致这些声明被认为是不安全上下文。

除了建立不安全上下文从而允许使用指针类型外，unsafe 修饰符对类型或成员没有影响。在下面的示例中

public class A

{

public unsafe virtual void F() {

char* p;

...

}

}

public class B: A

{

public override void F() {

base.F();

...

}

}

A 中 F 方法上的 unsafe 修饰符直接导致 F 的文本范围成为不安全上下文并可以在其中使用语言的不安全功能。在 B 中对 F 的重写中，不需要重新指定 unsafe 修饰符，除非 B 中的 F 方法本身需要访问不安全功能。

当指针类型是方法签名的一部分时，情况略有不同

public unsafe class A

{

public virtual void F(char* p) {...}

}

public class B: A

{

public unsafe override void F(char* p) {...}

}

此处，由于 F 的签名包括指针类型，因此它只能出现在不安全上下文中。然而，为设置此不安全上下文，既可以将整个类设置为不安全的（如 A 中的情况），也可以仅在方法声明中包含一个 unsafe 修饰符（如 B 中的情况）。


A.2指针类型.2169008

A.2 指针类型

在不安全上下文中，“类型”（第 4 节）可以是“指针类型”，或者是“值类型”或“引用类型”。

type:（类型：）

value-type（值类型）
reference-type（引用类型）
pointer-type（指针类型）

“指针类型”可表示为“非托管类型”后跟一个 * 标记，或者关键字 void 后跟一个 * 标记：

pointer-type:（指针类型：）

unmanaged-type *（非托管类型 *）
void *（void *）

unmanaged-type:（非托管类型）

type（类型）

指针类型中，在 * 前面指定的类型称为该指针类型的目标类型。它表示该指针类型的值所指向的变量的类型。

与引用（引用类型的值）不同，指针不受垃圾回收器跟踪（垃圾回收器不了解指针和它们指向的数据）。出于此原因，不允许指针指向引用或者包含引用的结构，并且指针的目标类型必须是非托管类型。

“非托管类型”是任何不是“引用类型”并且在任何嵌套级别都不包含“引用类型”字段的类型。换句话说，“非托管类型”是下列类型之一：

· sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 或 bool。

· 任何“枚举类型”。

· 任何“指针类型”。

· 任何由用户定义的只包含“非托管类型”字段的“结构类型”。

将指针和引用进行混合使用时的基本规则是；引用（对象）的目标可以包含指针，但指针的目标不能包含引用。

下表给出了一些指针类型的示例：

示例 | 说明
byte* | 指向 byte 的指针
char* | 指向 char 的指针
int** | 指向 int 的指针的指针
int*[] | 一维数组，它的元素是指向 int 的指针
void* | 指向未知类型的指针

对于某个给定实现，所有的指针类型都必须具有相同的大小和表示形式。

与 C 和 C++ 不同，在 C# 中，当在同一声明中声明多个指针时，* 只与基础类型写在一起，而不充当每个指针名称的前缀标点符号。例如

int* pi, pj; // NOT as int *pi, *pj;

类型为 T* 的一个指针的值表示类型为 T 的一个变量的地址。指针间接寻址运算符 * (第 A.5.1 节) 可用于访问此变量。例如，给定

int* 类型的变量 P，则表达式 *P 表示一个 int 变量，该变量的地址就是 P 的值。

与对象引用类似，指针可以是 null。如果将间接寻址运算符应用于 null 指针，则其行为将由实现自己定义。值为 null 的指针表示为将该指针的所有位都置零。

void* 类型表示指向未知类型的指针。由于目标类型是未知的，间接寻址运算符不能应用于 void* 类型的指针，也不能对这样的指针执行任何算术运算。但是 void* 类型的指针可以强制转换为任何其他指针类型（反之亦然）。

指针类型是一个单独类别的类型。与引用类型和值类型不同，指针类型不从 object 继承，而且不存在指针类型和 object 之间的转换。具体说来，指针不支持装箱和取消装箱（第 4.3 节）操作。但是，允许在不同指针类型之间以及指针类型与整型之间进行转换。第 A.4 节对此进行了描述。

指针类型可用作易失字段的类型（第 10.4.3 节）。

虽然指针可以作为 ref 或 out 参数传递，但这样做可能会导致未定义的行为，例如，指针可能被设置为指向一个局部变量，而当调用方法返回时，该局部变量可能已不存在了；或者指针曾指向一个固定对象，但当调用方法返回时，该对象不再是固定的了。例如：

using System;

class Test

{

static int value = 20;

unsafe static void F(out int* pi1, ref int* pi2) {

int i = 10;

pi1 = &i;

fixed (int* pj = &value) {

// ...

pi2 = pj;

}

}

static void Main() {

int i = 10;

unsafe {

int* px1;

int* px2 = &i;

F(out px1, ref px2);

Console.WriteLine("*px1 = {0}, *px2 = {1}",

*px1, *px2); // undefined behavior

}

}

}

方法可以返回某一类型的值，而该类型可以是指针。例如，给定一个指向连续的 int 值序列的指针、该序列的元素个数，和另外一个 int 值 (value)，下面的方法将在该整数序列中查找与该 value 匹配的值，若找到匹配项，则返回该匹配项的地址；否则，它将返回 null：

unsafe static int* Find(int* pi, int size, int value) {

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

if (*pi == value)

return pi;

++pi;

}

return null;

}

在不安全上下文中，可以使用下列几种构造操作指针：

· * 运算符可用于执行指针间接寻址（第 A.5.1 节）。

· -> 运算符可用于通过指针访问结构的成员（第 A.5.2 节）。

· [] 运算符可用于索引指针（第 A.5.3 节）。

· & 运算符可用于获取变量的地址（第 A.5.4 节）。

· ++ 和 -- 运算符可用于增加和减小指针（第 A.5.5 节）。

· + 和 - 运算符可用于执行指针算术（第 A.5.6 节）。

· ==、!=、<、>、<= 和 => 运算符可用于比较指针（第 A.5.7 节）。

· stackalloc 运算符可用于从调用堆栈（第 A.7 节）中分配内存。

· fixed 语句可用于暂时固定一个变量，以便可以获取它的地址（第 A.6 节）。


A.3固定和可移动变量.2185130

A.3 固定和可移动变量

address-of 运算符（第 A.5.4 节）和 fixed 语句（第 A.6 节）将变量划分为两个类别：固定变量和可移动变量。

固定变量驻留在不受垃圾回收器的操作影响的存储位置中。（固定变量的示例包括局部变量、值参数和由取消指针引用而创建的变量。）另一方面，可移动变量则驻留在会被垃圾回收器重定位或处置的存储位置中。（可移动变量的示例包括对象中的字段和数组的元素。）

& 运算符（第 A.5.4 节）允许不受限制地获取固定变量的地址。但是，由于可移动变量会受到垃圾回收器的重定位或处置，因此可移动变量的地址只能使用 fixed 语句（第 A.6 节）获取，而且该地址只在此 fixed 语句的生存期内有效。

准确地说，固定变量是下列之一：

· 用引用局部变量或值参数的“简单名称”（第 7.5.2 节）表示的变量。

· 用 V.I 形式的“成员访问”（第 7.5.4 节）表示的变量，其中 V 是“结构类型”的固定变量。

· 用 *P 形式的“指针间接寻址表达式”（第 A.5.1 节）、P->I 形式的“指针成员访问”（第 A.5.2 节）或 P[E] 形式的“指针元素访问”（第 A.5.3 节）表示的变量。

所有其他变量都属于可移动变量。

请注意静态字段属于可移动变量。还请注意即使赋予 ref 或 out 参数的自变量是固定变量，它们仍属于可移动变量。最后请注意，由取消指针引用而产生的变量总是属于固定变量。


A.4指针转换.2187833

A.4 指针转换

在不安全上下文中，可供使用的隐式转换的集合（第 6.1 节）也扩展为包括以下隐式指针转换：

· 从任何“指针类型”到 void* 类型。

· 从 null 类型到任何“指针类型”。

另外，在不安全上下文中，可供使用的显式转换的集合（第 6.2 节）也扩展为包括以下显式指针转换：

· 从任何“指针类型”到任何其他“指针类型”。

· 从 sbyte、byte、short、ushort、int、uint、long 或 ulong 到任何“指针类型”。

· 从任何“指针类型”到 sbyte、byte、short、ushort、int、uint、long 或 ulong。

最后，在不安全上下文中，标准隐式转换的集合（第 6.3.1 节）包括以下指针转换：

· 从任何“指针类型”到 void* 类型。

两个指针类型之间的转换永远不会更改实际的指针值。换句话说，从一个指针类型到另一个指针类型的转换不会影响由指针给出的基础地址。

当一个指针类型被转换为另一个指针类型时，如果没有将得到的指针正确地对指向的类型对齐，则当结果被取消引用时，该行为将是未定义的。一般情况下，“正确对齐”的概念是可传递的：如果指向类型 A 的指针被正确地与指向类型 B 的指针对齐，而此指向类型 B 的指针又被正确地与指向类型 C 的指针对齐，那么指向类型 A 的指针被正确地与指向类型 C 的指针对齐。

请考虑下列情况，其中具有一个类型的变量被通过指向一个不同类型的指针访问：

char c = 'A';

char* pc = &c;

void* pv = pc;

int* pi = (int*)pv;

int i = *pi; // undefined

*pi = 123456; // undefined

当一个指针类型被转换为指向字节的指针时，转换后的指针将指向原来所指变量的地址中的最低寻址字节。连续增加该变换后的指针（最大可达到该变量所占内存空间的大小），将产生指向该变量的其他字节的指针。例如，下列方法将 double 型变量中的八个字节的每一个显示为一个十六进制值：

using System;

class Test

{

unsafe static void Main() {

double d = 123.456e23;

unsafe {

byte* pb = (byte*)&d;

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

Console.Write("{0:X2} ", *pb++);

Console.WriteLine();

}

}

}

当然，产生的输出取决于字节存储顺序 (Endianness)。

指针和整数之间的映射由实现定义。但是，在具有线性地址空间的 32 位和 64 位 CPU 体系结构上，指针和整型之间的转换通常与 uint 或 ulong 类型的值与这些整型之间的对应方向上的转换具有完全相同的行为。


A.5表达式中的指针.2193711

A.5 表达式中的指针

在不安全上下文中，表达式可能产生指针类型的结果，但是在不安全上下文以外，表达式为指针类型属于编译时错误。准确地说，在不安全上下文以外，如果任何“简单名称”（第 7.5.2 节）、“成员访问”（第 7.5.4 节）、“调用表达式”（第 7.5.5 节）或“元素访问”（第 7.5.6 节）属于指针类型，则将发生编译时错误。

在不安全上下文中，“非数组创建基本表达式”（第 7.5 节）和“一元表达式”（第 7.6 节）产生式允许使用下列附加构造：

primary-no-array-creation-expression:（非数组创建基本表达式：）

...
pointer-member-access（指针成员访问）
pointer-element-access（指针元素访问）
sizeof-expression（sizeof 表达式）

unary-expression:（一元表达式：）

...
pointer-indirection-expression（指针间接寻址表达式）
addressof-expression（addressof 表达式）

以下几节对这些构造进行了描述。相关的语法暗示了不安全运算符的优先级和结合性。


A.5.1指针间接寻址.2195986

A.5.1 指针间接寻址

“指针间接寻址表达式”包含一个星号 (*)，后跟一个“一元表达式”。

pointer-indirection-expression:（指针间接寻址表达式：）

* unary-expression（* 一元表达式）

一元 * 运算符表示指针间接寻址并且用于获取指针所指向的变量。计算 *P 得到的结果（其中 P 为指针类型 T* 的表达式）是类型为 T 的一个变量。将一元 * 运算符应用于 void* 类型的表达式或者应用于不是指针类型的表达式属于编译时错误。

将一元 * 运算符应用于 null 指针的效果是由实现定义的。具体说来，不能保证此操作会引发 System.NullReferenceException。

如果已经将无效值赋给指针，则一元 * 运算符的行为是未定义的。通过一元 * 运算符取消指针引用有时会产生无效值，这些无效值包括：没能按所指向的类型正确对齐的地址（请参见第 A.4 节中的示例）和超过生存期的变量的地址。

出于明确赋值分析的目的，通过计算 *P 形式的表达式产生的变量被认为是初始化赋过值的（第 5.3.1 节）。


A.5.2指针成员访问.2198207

A.5.2 指针成员访问

指针成员访问包含一个基本表达式，后跟一个“->”标记，最后是一个标识符。

pointer-member-access:（指针成员访问：）

primary-expression -> identifier（基本表达式 -> 标识符）

在 P->I 形式的指针成员访问中，P 必须是除 void* 以外的某个指针类型的表达式，而 I 必须表示 P 所指向的类型的可访问成员。

P->I 形式的指针成员访问的计算方式与 (*P).I 完全相同。有关指针间接寻址运算符 (*) 的说明，请参见第 A.5.1 节。有关成员访问运算符 (.) 的说明，请参见第 7.5.4 节。

在下面的示例中

using System;

struct Point

{

public int x;

public int y;

public override string ToString() {

return "(" + x + "," + y + ")";

}

}

class Test

{

static void Main() {

Point point;

unsafe {

Point* p = &point;

p->x = 10;

p->y = 20;

Console.WriteLine(p->ToString());

}

}

}

-> 运算符用于通过指针访问结构中的字段和调用结构中的方法。由于 P->I 操作完全等效于 (*P).I，因此 Main 方法可以等效地编写为：

class Test

{

static void Main() {

Point point;

unsafe {

Point* p = &point;

(*p).x = 10;

(*p).y = 20;

Console.WriteLine((*p).ToString());

}

}

}


A.5.3指针元素访问.2203639

A.5.3 指针元素访问

指针元素访问包括一个非数组创建基本表达式，后跟一个用“[”和“]”括起来的表达式。

pointer-element-access:（指针元素访问：）

primary-no-array-creation-expression [ expression ]（非数组创建基本表达式 [ 表达式 ]）

在 P[E] 形式的指针元素访问中，P 必须是 void* 以外的指针类型的表达式，而 E 则必须是可以隐式转换为 int、uint、long 或 ulong 的类型的表达式。

P[E] 形式的指针元素访问的计算方式与 *(P + E) 完全相同。有关指针间接寻址运算符 (*) 的说明，请参见第 A.5.1 节。有关指针加法运算符 (+) 的说明，请参见第 A.5.6 节。

在下面的示例中

class Test

{

static void Main() {

unsafe {

char* p = stackalloc char[256];

for (int i = 0; i < 256; i++) p[i] = (char)i;

}

}

}

指针元素访问用于在 for 循环中初始化字符缓冲区。由于 P[E] 操作与 *(P + E) 完全等效，此示例可以等效地写为：

class Test

{

static void Main() {

unsafe {

char* p = stackalloc char[256];

for (int i = 0; i < 256; i++) *(p + i) = (char)i;

}

}

}

指针元素访问运算符不能检验是否发生访问越界错误，而且当访问超出边界的元素时行为是未定义的。这与 C 和 C++ 相同。


A.5.4address-of运算符.2208129

A.5.4 address-of 运算符

“addressof 表达式”包含一个“and”符 (&)，后跟一个“一元表达式”。

addressof-expression:（addressof 表达式：）

& unary-expression（& 一元表达式）

如果给定类型为 T 且属于固定变量（第 A.3 节）的表达式 E，构造 &E 将计算由 E 给出的变量的地址。计算的结果是一个类型为 T* 的值。如果 E 不属于变量，如果 E 属于易失字段，或者如果 E 表示可移动的变量，则将发生编译时错误。在最后一种情况中，可以先利用固定语句（第 A.6 节）临时“固定”该变量，再获取它的地址。

& 运算符不要求它的参数先被明确赋值，但是在执行了 & 操作后，该运算符所应用于的那个变量在此操作发生的执行路径中被“认为是”已经明确赋值的。这意味着，由程序员负责确保在相关的上下文中对该变量实际进行合适的初始化。

在下面的示例中

using System;

class Test

{

static void Main() {

int i;

unsafe {

int* p = &i;

*p = 123;

}

Console.WriteLine(i);

}

}

初始化 p 的代码执行了 &i 操作，此后 i 被认为是明确赋值的。对 *p 的赋值实际上是初始化了 i，但设置此初始化是程序员的责任，而且如果移除此赋值语句，也不会发生编译时错误。

上述 & 运算符的明确赋值规则可以避免局部变量的冗余初始化。例如，许多外部 API 要求获取指向结构的指针，而由此 API 来填充该结构。对此类 API 进行的调用通常会传递局部结构变量的地址，而如果没有上述规则，则将需要对此结构变量进行冗余初始化。

如第 7.5.4 节中所述，如果在实例构造函数或静态构造函数之外，在结构或类中定义了 readonly 字段，则该字段被认为是一个值，而不是变量。因此，无法获取它的地址。与此类似，无法获取常数的地址。


A.5.5指针增加和减小.2212028

A.5.5 指针增加和减小

在不安全上下文中，++ 运算符（第 7.5.9 节）和 -- 运算符（第 7.6.5 节）可以应用于除 void* 以外的所有类型的指针变量。因此，为每个指针类型 T* 都隐式定义了下列运算符：

T* operator ++(T* x);

T* operator --(T* x);

这些运算符分别产生与 x + 1 和 x - 1（第 A.5.6 节）相同的结果。换句话说，对于 T* 类型的指针变量，++ 运算符将 sizeof(T) 加到该变量的地址中，而 -- 运算符则从该变量的地址中减去 sizeof(T)。

如果指针递增或递减运算的结果超过指针类型的域，则结果是由实现定义的，但不会产生异常。


A.5.6指针算法.2213463

A.5.6 指针算法

在不安全上下文中，+ 运算符（第 7.7.4 节）和 - 运算符（第 7.7.5 节）可以应用于除 void* 以外的所有指针类型的值。因此，为每个指针类型 T* 都隐式定义了下列运算符：

T* operator +(T* x, int y);

T* operator +(T* x, uint y);

T* operator +(T* x, long y);

T* operator +(T* x, ulong y);

T* operator +(int x, T* y);

T* operator +(uint x, T* y);

T* operator +(long x, T* y);

T* operator +(ulong x, T* y);

T* operator –(T* x, int y);

T* operator –(T* x, uint y);

T* operator –(T* x, long y);

T* operator –(T* x, ulong y);

long operator –(T* x, T* y);

给定指针类型 T* 的表达式 P 和类型 int、uint、long 或 ulong 的表达式 N，表达式 P + N 和 N + P 的计算结果是一个属于类型 T* 的指针值，该值等于将 N * sizeof(T) 加到由 P 给出的地址上。与此类似，P - N 的计算结果也是一个属于类型 T* 的指针值，它等于从 P 给出的地址中减去 N * sizeof(T)。

给定指针类型 T* 的两个表达式 P 和 Q，表达式 P – Q 将先计算 P 和 Q 给出的地址之间的差，然后用 sizeof(T) 去除这个差。计算结果的类型始终为 long。实际上，P - Q 是这样计算的：((long)(P) - (long)(Q)) / sizeof(T)。

例如：

using System;

class Test

{

static void Main() {

unsafe {

int* values = stackalloc int[20];

int* p = &values[1];

int* q = &values[15];

Console.WriteLine("p - q = {0}", p - q);

Console.WriteLine("q - p = {0}", q - p);

}

}

}

生成以下输出：

p - q = -14

q - p = 14

如果在执行上述指针算法时，计算结果超越该指针类型的域，则将以实现定义的方式截断结果，但是不会产生异常。


A.5.7指针比较.2218738

A.5.7 指针比较

在不安全上下文中，==、!=、<、>、<= 和 => 运算符（第 7.9 节）可以应用于所有指针类型的值。指针比较运算符有：

bool operator ==(void* x, void* y);

bool operator !=(void* x, void* y);

bool operator <(void* x, void* y);

bool operator >(void* x, void* y);

bool operator <=(void* x, void* y);

bool operator >=(void* x, void* y);

由于存在从任何指针类型到 void* 类型的隐式转换，因此可以使用这些运算符来比较任何指针类型的操作数。比较运算符像比较无符号整数一样比较两个操作数给出的地址。


A.5.8sizeof运算符.2220191

A.5.8 sizeof 运算符

sizeof 运算符返回由给定类型的变量占用的字节数。被指定为 sizeof 的操作数的类型必须是“非托管类型”（第 A.2 节）。

sizeof-expression:（sizeof 表达式：）

sizeof ( unmanaged-type )（sizeof ( 非托管类型 )）

sizeof 运算符的结果是 int 类型的值。对于某些预定义类型，sizeof 运算符将产生如下表所示的常数值。

表达式 | 结果
sizeof(sbyte) | 1
sizeof(byte) | 1
sizeof(short) | 2
sizeof(ushort) | 2
sizeof(int) | 4
sizeof(uint) | 4
sizeof(long) | 8
sizeof(ulong) | 8
sizeof(char) | 2
sizeof(float) | 4
sizeof(double) | 8
sizeof(bool) | 1

对于所有其他类型，sizeof 运算符的结果是由实现定义的，并且属于值而不是常数。

一个结构所属的各个成员以什么顺序被装入该结构中，没有明确规定。

出于对齐的目的，在结构的开头、结构内以及结构的结尾处可以插入一些未命名的填充位。这些填充位的内容是不确定的。

当 sizeof 应用于具有结构类型的操作数时，结果是该类型变量所占的字节总数（包括所有填充位在内）。


A.6固定语句.2228399

A.6 固定语句

在不安全上下文中，嵌入语句（第 8 节）产生式允许使用一个附加结构即 fixed 语句，该语句用于“固定”可移动变量，从而使该变量的地址在语句的持续时间内保持不变。

embedded-statement:（嵌入语句：）

...
fixed-statement（固定语句）

fixed-statement:（固定语句：）

fixed ( pointer-type fixed-pointer-declarators ) embedded-statement（fixed ( 指针类型 固定指针声明符 ) 嵌入语句）

fixed-pointer-declarators:（固定指针声明符：）

fixed-pointer-declarator（固定指针声明符）
fixed-pointer-declarators , fixed-pointer-declarator（固定指针声明符 , 固定指针声明符）

fixed-pointer-declarator:（固定指针声明符：）

identifier = fixed-pointer-initializer（标识符 = 固定指针初始值设定项）

fixed-pointer-initializer:（固定指针初始值设定项：）

& variable-reference（& 变量引用）
expression（表达式）

如上述产生式所述，每个固定指针声明符声明一个给定指针类型的局部变量，并使用由相应的固定指针初始值设定项计算的地址初始化该局部变量。在 fixed 语句中声明的局部变量的可访问范围仅限于：在该变量声明右边的所有固定指针初始值设定项中，以及在该 fixed 语句的嵌入语句中。由 fixed 语句声明的局部变量被视为只读。如果嵌入语句试图修改此局部变量（通过赋值或 ++ 和 -- 运算符）或者将它作为 ref 或 out 参数传递，则将出现编译时错误。

“固定指针初始值设定项”可以是下列之一：

· “&”标记，后跟一个变量引用（第 5.4 节），它引用非托管类型 T 的可移动变量（第 A.3 节），前提是类型 T* 可以隐式转换为 fixed 语句中给出的指针类型。在这种情况下，初始值设定项将计算给定变量的地址，而 fixed 语句在生存期内将保证该变量的地址不变。

· 元素类型为非托管类型 T 的“数组类型”的表达式，前提是类型 T* 可隐式转换为 fixed 语句中给出的指针类型。在这种情况下，初始值设定项将计算数组中第一个元素的地址，而 fixed 语句在生存期内将保证整个数组的地址保持不变。如果数组表达式为 null 或者数组具有零个元素，则 fixed 语句的行为由实现定义。

· string 类型的表达式，前提是类型 char* 可以隐式转换为 fixed 语句中给出的指针类型。在这种情况下，初始值设定项将计算字符串中第一个字符的地址，而 fixed 语句在生存期内将保证整个字符串的地址不变。如果字符串表达式为 null，则 fixed 语句的行为是实现定义。

对于每个由“固定指针初始值设定项”计算的地址，fixed 语句确保由该地址引用的变量在 fixed 语句的生存期内不会被垃圾回收器重定位或者处置。例如，如果由“固定指针初始值设定项”计算的地址引用对象的字段或数组实例的元素，fixed 语句将保证包含该字段或元素的对象实例本身也不会在该语句的生存期内被重定位或者处置。

确保由 fixed 语句创建的指针在执行这些语句之后不再存在是程序员的责任。例如，当 fixed 语句创建的指针被传递到外部 API 时，确保 API 不会在内存中保留这些指针是程序员的责任。

固定对象可能导致中产生存储碎片（因为它们无法移动）。出于该原因，只有在绝对必要时才应当固定对象，而且固定对象的时间越短越好。

在以下示例中：

class Test

{

static int x;

int y;

unsafe static void F(int* p) {

*p = 1;

}

static void Main() {

Test t = new Test();

int[] a = new int[10];

unsafe {

fixed (int* p = &x) F(p);

fixed (int* p = &t.y) F(p);

fixed (int* p = &a[0]) F(p);

fixed (int* p = a) F(p);

}

}

}

说明了 fixed 语句的几种用法。第一条语句固定并获取一个静态字段的地址，第二条语句固定并获取一个实例字段的地址，第三条语句固定并获取一个数组元素的地址。在这几种情况下，直接使用常规 & 运算符都是错误的，这是因为这些变量都属于可移动变量。

上面示例中的第三条和第四条 fixed 语句产生相同的结果。一般情况下，对于数组实例 a，在 fixed 语句中指定 &a[0] 与只指定 a 等效。

以下是另一个 fixed 语句示例，这次使用 string：

class Test

{

static string name = "xx";

unsafe static void F(char* p) {

for (int i = 0; p[i] != '\0'; ++i)

Console.WriteLine(p[i]);

}

static void Main() {

unsafe {

fixed (char* p = name) F(p);

fixed (char* p = "xx") F(p);

}

}

}

在不安全上下文中，一维数组的数组元素按递增索引顺序存储，从索引 0 开始，到索引 Length – 1 结束。对于多维数组，数组元素按这样的方式存储：首先增加最右边维度的索引，然后是左边紧邻的维度，依此类推直到最左边。在获取指向数组实例 a 的指针 p 的 fixed 语句内，从 p 到 p + a.Length - 1 范围内的每个指针值均表示数组中的一个元素的地址。与此类似，从 p[0] 到 p[a.Length - 1] 范围内的变量表示实际的数组元素。已知数组的存储方式，可以将任意维度的数组都视为线性的。

例如：

using System;

class Test

{

static void Main() {

int[,,] a = new int[2,3,4];

unsafe {

fixed (int* p = a) {

for (int i = 0; i < a.Length; ++i) // treat as linear

p[i] = i;

}

}

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

for (int j = 0; j < 3; ++j) {

for (int k = 0; k < 4; ++k)

Console.Write("[{0},{1},{2}] = {3,2} ", i, j, k, a[i,j,k]);

Console.WriteLine();

}

}

}

生成以下输出：

[0,0,0] = 0 [0,0,1] = 1 [0,0,2] = 2 [0,0,3] = 3

[0,1,0] = 4 [0,1,1] = 5 [0,1,2] = 6 [0,1,3] = 7

[0,2,0] = 8 [0,2,1] = 9 [0,2,2] = 10 [0,2,3] = 11

[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15

[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19

[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

在下面的示例中

class Test

{

unsafe static void Fill(int* p, int count, int value) {

for (; count != 0; count--) *p++ = value;

}

static void Main() {

int[] a = new int[100];

unsafe {

fixed (int* p = a) Fill(p, 100, -1);

}

}

}

使用一条 fixed 语句固定一个数组，以便可以将该数组的地址传递给一个采用指针作为参数的方法。

通过固定字符串实例产生的 char* 类型的值始终指向空终止字符串。在获取指向字符串实例 s 的指针 p 的 fixed 语句内，从 p 到 p + s.Length - 1 范围内的指针值表示字符串中字符的地址，而指针值 p + s.Length 则始终指向一个空字符（值为 '\0' 的字符）。

通过固定指针修改托管类型的对象可能导致未定义的行为。例如，由于字符串是不可变的，因此程序员应确保指向固定字符串的指针所引用的字符不被修改。

这种字符串的自动空终止功能，大大方便了调用需要“C 风格”字符串的外部 API。但请注意，核心 C# 允许字符串实例包含空字符。如果字符串中存在此类空字符，则在将字符串视为空终止的 char* 时将出现截断。


A.7堆栈分配.2246760

A.7 堆栈分配

在不安全上下文中，局部变量声明（第 8.5.1 节）可以包含一个从调用堆栈中分配内存的堆栈分配初始值设定项。

local-variable-initializer:（局部变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设置项）
stackalloc-initializer（stackalloc 初始值设置项）

stackalloc-initializer:（stackalloc 初始值设定项：）

stackalloc unmanaged-type [ expression ]（stackalloc 非托管类型 [ 表达式 ]）

上述产生式中，“非托管类型”表示将在新分配的位置中存储的项的类型，而“表达式”则指示这些项的数目。合在一起，它们指定所需的分配大小。由于堆栈分配的大小不能为负值，因此将项的数目指定为计算结果为负值的“常数表达式”属于编译时错误。

stackalloc T[E] 形式的堆栈分配初始值设定项要求：T 必须为非托管类型（第 A.2 节），E 必须为 int 类型的表达式。该构造从调用堆栈中分配 E * sizeof(T) 个字节，并返回一个指向新分配的块的、类型 T* 的指针。如果 E 为负值，则其行为是未定义的。如果 E 为零，则不进行任何分配，并且返回的指针由实现定义。如果没有足够的内存以分配给定大小的块，则引发 System.StackOverflowException。

新分配的内存的内容是未定义的。

在 catch 或 finally 块（第 8.10 节）中不允许使用堆栈分配初始值设定项。

无法显式释放利用 stackalloc 分配的内存。在函数成员的执行期间创建的所有堆栈分配内存块都将在该函数成员返回时自动丢弃。这对应于 alloca 函数，它是通常存在于 C 和 C++ 实现中的一个扩展。

在下面的示例中

using System;

class Test

{

static string IntToString(int value) {

int n = value >= 0? value: -value;

unsafe {

char* buffer = stackalloc char[16];

char* p = buffer + 16;

do {

*--p = (char)(n % 10 + '0');

n /= 10;

} while (n != 0);

if (value < 0) *--p = '-';

return new string(p, 0, (int)(buffer + 16 - p));

}

}

static void Main() {

Console.WriteLine(IntToString(12345));

Console.WriteLine(IntToString(-999));

}

}

在 IntToString 方法中使用了 stackalloc 初始值设定项，以在堆栈上分配一个 16 个字符的缓冲区。此缓冲区在该方法返回时自动丢弃。


A.8动态内存分配.2253088

A.8 动态内存分配

除 stackalloc 运算符外，C# 不提供其他预定义构造来管理那些不受垃圾回收控制的内存。这些服务通常是由支持类库提供或者直接从基础操作系统导入的。例如，下面的 Memory 类阐释了可以如何从 C# 访问基础操作系统的有关堆处理的各种功能：

using System;

using System.Runtime.InteropServices;

public unsafe class Memory

{

// Handle for the process heap. This handle is used in all calls to the

// HeapXXX APIs in the methods below.

static int ph = GetProcessHeap();

// Private instance constructor to prevent instantiation.

private Memory() {}

// Allocates a memory block of the given size. The allocated memory is

// automatically initialized to zero.

public static void* Alloc(int size) {

void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);

if (result == null) throw new OutOfMemoryException();

return result;

}

// Copies count bytes from src to dst. The source and destination

// blocks are permitted to overlap.

public static void Copy(void* src, void* dst, int count) {

byte* ps = (byte*)src;

byte* pd = (byte*)dst;

if (ps > pd) {

for (; count != 0; count--) *pd++ = *ps++;

}

else if (ps < pd) {

for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;

}

}

// Frees a memory block.

public static void Free(void* block) {

if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();

}

// Re-allocates a memory block. If the reallocation request is for a

// larger size, the additional region of memory is automatically

// initialized to zero.

public static void* ReAlloc(void* block, int size) {

void* result = HeapReAlloc(ph, HEAP_ZERO_MEMORY, block, size);

if (result == null) throw new OutOfMemoryException();

return result;

}

// Returns the size of a memory block.

public static int SizeOf(void* block) {

int result = HeapSize(ph, 0, block);

if (result == -1) throw new InvalidOperationException();

return result;

}

// Heap API flags

const int HEAP_ZERO_MEMORY = 0x00000008;

// Heap API functions

[DllImport("kernel32")]

static extern int GetProcessHeap();

[DllImport("kernel32")]

static extern void* HeapAlloc(int hHeap, int flags, int size);

[DllImport("kernel32")]

static extern bool HeapFree(int hHeap, int flags, void* block);

[DllImport("kernel32")]

static extern void* HeapReAlloc(int hHeap, int flags,

void* block, int size);

[DllImport("kernel32")]

static extern int HeapSize(int hHeap, int flags, void* block);

}

以下给出一个使用 Memory 类的示例：

class Test

{

static void Main() {

unsafe {

byte* buffer = (byte*)Memory.Alloc(256);

try {

for (int i = 0; i < 256; i++) buffer[i] = (byte)i;

byte[] array = new byte[256];

fixed (byte* p = array) Memory.Copy(buffer, p, 256);

}

finally {

Memory.Free(buffer);

}

for (int i = 0; i < 256; i++) Console.WriteLine(array[i]);

}

}

}

此示例通过 Memory.Alloc 分配了 256 字节的内存，并且使用从 0 增加到 255 的值初始化该内存块。它然后分配一个具有 256 个元素的字节数组并使用 Memory.Copy 将内存块的内容复制到此字节数组中。最后，使用 Memory.Free 释放内存块并将字节数组的内容输出到控制台上。


B.文档注释.2262589

Powered By Gavin http://www.CSharpWeb.com

B. 文档注释

C# 提供一种机制，使程序员可以使用含有 XML 文本的特殊注释语法为他们的代码编写文档。在源代码文件中，具有某种格式的注释可用于指导某个工具根据这些注释和它们后面的源代码元素生成 XML。使用这类语法的注释称为文档注释。这些注释后面必须紧跟用户定义类型（如类、委托或接口）或者成员（如字段、事件、属性或方法）。生成 XML 的工具称为文档生成器。（此生成器可以但不一定非得是 C# 编译器本身。）由文档生成器产生的输出称为文档文件。文档文件可用作到文档查看器的输入；文档查看器是旨在生成类型信息及其关联文档的某种可视化显示的工具。

此规范推荐了一组在文档注释中使用的标记，但是这些标记不是必须使用的，如果需要也可以使用其他标记，只要遵循“符合格式标准的 XML”规则即可。


B.1介绍.2263659

B.1 介绍

具有特殊格式的注释可用于指导某个工具根据这些注释和它们后面的源代码元素生成 XML。这类注释是以三个斜杠 (///) 开始的单行注释，或者是以一个斜杠和两个星号 (/**) 开始的分隔注释。这些注释后面必须紧跟它们所注释的用户定义类型（如类、委托或接口）或者成员（如字段、事件、属性或方法）。属性节（第 17.2 节）被视为声明的一部分，因此，文档注释必须位于应用到类型或成员的属性之前。

语法：

single-line-doc-comment：（单行文档注释：）

/// input-charactersopt（/// 输入字符可选）

delimited-doc-comment:（分隔文档注释：）

/** delimited-comment-charactersopt */（/** 带分隔符的注释字符可选 */）

在单行文档注释中，如果当前单行文档注释旁边的每个单行文档注释上的 /// 字符后跟有空白字符，则此空白字符不包括在 XML 输出中。

在分隔文档注释中，如果第二行上的第一个非空白字符是一个星号，并且在分隔文档注释内的每行开头都重复同一种由可选空白字符和星号符组成的样式，则该重复出现的样式所含的字符不包括在 XML 输出中。此样式中，可以在星号符之前或之后包括空白字符。

示例：

/// <remarks>Class <c>Point</c> models a point in a two-dimensional

/// plane.</remarks>

///

public class Point

{

/// <remarks>method <c>draw</c> renders the point.</remarks>

void draw() {¼}

}

文档注释内的文本必须根据 XML 规则 (http://www.w3.org/TR/REC-xml) 设置正确的格式。如果 XML 不符合标准格式，将生成警告，并且文档文件将包含一条注释，指出遇到错误。

尽管开发人员可自由创建它们自己的标记集，但附录 B.2 中定义有建议的标记集。某些建议的标记具有特殊含义：

· <param> 标记用于描述参数。如果使用这样的标记，文档生成器必须验证指定参数是否存在以及文档注释中是否描述了所有参数。如果此验证失败，文档生成器将发出警告。

· cref 属性可以附加到任意标记，以提供对代码元素的引用。文档生成器必须验证此代码元素是否存在。如果验证失败，文档生成器将发出警告。查找在 cref 属性中描述的名称时，文档生成器必须根据源代码中出现的 using 语句来确定命名空间的可见性。

· <summary> 标记旨在标出可由文档查看器显示的有关类型或成员的额外信息。

· <include> 标记表示应该包含的来自外部 XML 文件的信息。

注意，文档文件并不提供有关类型和成员的完整信息（例如，它不包含任何关于类型的信息）。若要获得有关类型或成员的完整信息，必须协同使用文档文件与对实际涉及的类型或成员的反射调用。


B.2建议的标记.2269110

B.2 建议的标记

文档生成器必须接受和处理任何根据 XML 规则有效的标记。下列标记提供了用户文档中常用的功能。（当然，也可能有其他标记。）

标记 | 章节 | 用途
<c> | B.2.1 | 将文本设置为类似代码的字体
<code> | B.2.2 | 将一行或多行源代码或程序输出设置为某种字体
<example> | B.2.3 | 表示所含的是示例
<exception> | B.2.4 | 标识方法可能引发的异常
<include> | B.2.5 | 包括来自外部文件的 XML
<list> | B.2.6 | 创建列表或表
<para> | B.2.7 | 用于将结构添加到文本中
<param> | B.2.8 | 描述方法或构造函数的参数
<paramref> | B.2.9 | 确认某个单词是参数名
<permission> | B.2.10 | 描述成员的安全性和访问权限
<remarks> | B.2.11 | 描述一种类型
<returns> | B.2.12 | 描述方法的返回值
<see> | B.2.13 | 指定链接
<seealso> | B.2.14 | 生成“请参见”项
<summary> | B.2.15 | 描述类型的成员
<value> | B.2.16 | 描述属性


B.2.1<c>.2281474

B.2.1 <c>

此标记提供一种机制以指示用特殊字体（如用于代码块的字体）设置说明中的文本段落。对于实际代码行，请使用 <code> (B.2.2)。

语法：

<c>text</c>

示例：

/// <remarks>Class <c>Point</c> models a point in a two-dimensional

/// plane.</remarks>

public class Point

{

// ...

}


B.2.2<code>.2282570

B.2.2 <code>

此标记用于将一行或多行源代码或程序输出设置为某种特殊字体。对于叙述中较小的代码段落，请使用 <c> (B.2.1)。

语法：

<code>source code or program output</code>

示例：

/// <summary>This method changes the point's location by

/// the given x- and y-offsets.

/// <example>For example:

/// <code>

/// Point p = new Point(3,5);

/// p.Translate(-1,3);

/// </code>

/// results in <c>p</c>'s having the value (2,8).

/// </example>

/// </summary>

public void Translate(int xor, int yor) {

X += xor;

Y += yor;

}


B.2.3<example>.2284227

B.2.3 <example>

此标记用于在注释中插入代码示例，以说明如何使用所关联的方法或其他库成员。通常，此标记是同 <code> (B.2.2) 标记一起使用的。

语法：

<example>description</example>

示例：

有关示例，请参见 <code> (B.2.2)。


B.2.4<exception>.2284960

B.2.4 <exception>

此标记提供一种方法以说明关联的方法可能引发的异常。

语法：

<exception cref="member">description</exception>

其中

cref="member"

成员的名称。文档生成器检查给定成员是否存在，并将 member 转换为文档文件中的规范元素名称。

description

对引发异常的情况的描述。

示例：

public class DataBaseOperations

{

/// <exception cref="MasterFileFormatCorruptException"></exception>

/// <exception cref="MasterFileLockedOpenException"></exception>

public static void ReadRecord(int flag) {

if (flag == 1)

throw new MasterFileFormatCorruptException();

else if (flag == 2)

throw new MasterFileLockedOpenException();

// ¼

}

}


B.2.5<include>.2287375

B.2.5 <include>

此标记允许包含来自源代码文件外部的 XML 文档的信息。外部文件必须是符合标准格式的 XML 文档，还可以利用 XPath 表达式来指定应选择该 XML 文档中的哪些 XML 文本。然后用从外部文档中选定的 XML 来替换 <include> 标记。

语法：

<include file="filename" path="xpath" />

其中

file="filename"

外部 XML 文件的文件名。该文件名是相对于包含 include 标记的文件进行解释的（确定其完整路径名）。

path="xpath"

XPath 表达式，用于选择外部 XML 文件中的某些 XML。

示例：

如果源代码包含了如下声明：

/// <include file="docs.xml" path='extradoc/class[@name="IntList"]/*' />

public class IntList { ¼ }

并且外部文件“docs.xml”含有以下内容：

<?xml version="1.0"?>

<extradoc>

<class name="IntList">

<summary>

Contains a list of integers.

</summary>

</class>

<class name="StringList">

<summary>

Contains a list of integers.

</summary>

</class>

</extradoc>

这样输出的文档就与源代码中包含以下内容时一样：

/// <summary>

/// Contains a list of integers.

/// </summary>

public class IntList { ¼ }


B.2.6<list>.2290761

B.2.6 <list>

此标记用于创建列表或项目表。它可以包含 <listheader> 块以定义表或定义列表的标头行。（定义表时，仅需要在标头中为 term 提供一个项。）

列表中的每一项都用一个 <item> 块来描述。创建定义列表时，必须同时指定 term 和 description。但是，对于表、项目符号列表或编号列表，仅需要指定 description。

语法：

<list type="bullet" | "number" | "table">

<listheader>

<term>term</term>

<description>description</description>

</listheader>

<item>

<term>term</term>

<description>description</description>

</item>

...

<item>

<term>term</term>

<description>description</description>

</item>

</list>

其中

term

要定义的术语，其定义位于 description 中。

description

是项目符号列表或编号列表中的项，或者是 term 的定义。

示例：

public class MyClass

{

/// <remarks>Here is an example of a bulleted list:

/// <list type="bullet">

/// <item>

/// <description>Item 1.</description>

/// </item>

/// <item>

/// <description>Item 2.</description>

/// </item>

/// </list>

/// </remarks>

public static void Main () {

// ...

}

}


B.2.7<para>.2295091

B.2.7 <para>

此标记用于其他标记内，如 <remarks> (B.2.11) 或 <returns> (B.2.12)，用于将结构添加到文本中。

语法：

<para>content</para>

其中

content

段落文本。

示例：

/// <summary>This is the entry point of the Point class testing program.

/// <para>This program tests each method and operator, and

/// is intended to be run after any non-trvial maintenance has

/// been performed on the Point class.</para></summary>

public static void Main() {

// ...

}


B.2.8<param>.2296577

B.2.8 <param>

该标记用于描述方法、构造函数或索引器的参数。

语法：

<param name="name">description</param>

其中

name

参数名。

description

参数的描述。

示例：

/// <summary>This method changes the point's location to

/// the given coordinates.</summary>

/// <param><c>xor</c> is the new x-coordinate.</param>

/// <param><c>yor</c> is the new y-coordinate.</param>

public void Move(int xor, int yor) {

X = xor;

Y = yor;

}


B.2.9<paramref>.2298144

B.2.9 <paramref>

该标记表示某单词是一个参数。这样，生成文档文件后经适当处理，可以用某种独特的方法来格式化该参数。

语法：

<paramref name="name"/>

其中

name

参数名。

示例：

/// <summary>This constructor initializes the new Point to

/// (<paramref
name="xor"/>,<paramref
name="yor"/>).</summary>

/// <param><c>xor</c> is the new Point's x-coordinate.</param>

/// <param><c>yor</c> is the new Point's y-coordinate.</param>

public Point(int xor, int yor) {

X = xor;

Y = yor;

}


B.2.10<permission>.2299629

B.2.10 <permission>

该标记用于将成员的安全性和可访问性记入文档。

语法：

<permission cref="member">description</permission>

其中

cref="member"

成员的名称。文档生成器检查给定的代码元素是否存在，并将 member 转换为文档文件中的规范化元素名称。

description

对成员的访问属性的说明。

示例：

/// <permission cref="System.Security.PermissionSet">Everyone can

/// access this method.</permission>

public static void Test() {

// ...

}


B.2.11<remarks>.2301074

B.2.11 <remarks>

该标记用于指定类型的概述信息。（使用 <summary> (B.2.15) 描述类型的成员。）

语法：

<remarks>description</remarks>

其中

description

备注文本。

示例：

/// <remarks>Class <c>Point</c> models a point in a

/// two-dimensional plane.</remarks>

public class Point

{

// ...

}


B.2.12<returns>.2302282

B.2.12 <returns>

该标记用于描述方法的返回值。

语法：

<returns>description</returns>

其中

description

返回值的说明。

示例：

/// <summary>Report a point's location as a string.</summary>

/// <returns>A string representing a point's location, in the form (x,y),

/// without any leading, trailing, or embedded whitespace.</returns>

public override string ToString() {

return "(" + X + "," + Y + ")";

}


B.2.13<see>.2303627

B.2.13 <see>

该标记用于在文本内指定链接。使用 <seealso> (B.2.14) 指示将在“请参见”部分中出现的文本。

语法：

<see cref="member"/>

其中

cref="member"

成员的名称。文档生成器检查给定的代码元素是否存在，并将 member 更改为所生成的文档文件中的元素名称。

示例：

/// <summary>This method changes the point's location to

/// the given coordinates.</summary>

/// <see cref="Translate"/>

public void Move(int xor, int yor) {

X = xor;

Y = yor;

}

/// <summary>This method changes the point's location by

/// the given x- and y-offsets.

/// </summary>

/// <see cref="Move"/>

public void Translate(int xor, int yor) {

X += xor;

Y += yor;

}


B.2.14<seealso>.2305715

B.2.14 <seealso>

该标记用于生成将列入“请参见”部分的项。使用 <see> (B.2.13) 指定来自文本内的链接。

语法：

<seealso cref="member"/>

其中

cref="member"

成员的名称。文档生成器检查给定的代码元素是否存在，并将 member 更改为所生成的文档文件中的元素名称。

示例：

/// <summary>This method determines whether two Points have the same

/// location.</summary>

/// <seealso cref="operator=="/>

/// <seealso cref="operator!="/>

public override bool Equals(object o) {

// ...

}


B.2.15<summary>.2307209

B.2.15 <summary>

可以用此标记描述类型的成员。使用 <remarks> (B.2.11) 描述类型本身。

语法：

<summary>description</summary>

其中

description

关于成员的摘要描述。

示例：

/// <summary>This constructor initializes the new Point to (0,0).</summary>

public Point() : this(0,0) {

}


B.2.16<value>.2308222

B.2.16 <value>

该标记用于描述属性。

语法：

<value>property description</value>

其中

property description

属性的说明。

示例：

/// <value>Property <c>X</c> represents the point's x-coordinate.</value>

public int X

{

get { return x; }

set { x = value; }

}


B.3处理文档文件.2309349

Powered By Gavin http://www.CSharpWeb.com

B.3 处理文档文件

文档生成器为源代码中每个附加了“文档注释标记”的代码元素生成一个 ID 字符串。该 ID 字符串唯一地标识源元素。文档查看器利用此 ID 字符串来标识该文档所描述的对应的元数据/反射项。

文档文件不是源代码的分层表现形式；而是为每个元素生成的 ID 字符串的平面列表。


B.3.1ID字符串格式.2309974

B.3.1 ID 字符串格式

文档生成器在生成 ID 字符串时遵循下列规则：

· 不在字符串中放置空白。

· 字符串的第一部分通过单个字符后跟一个冒号来标识被标识成员的种类。定义以下几种成员：

字符 | 说明
E | 事件
F | 字段
M | 方法（包括构造函数、析构函数和运算符）
N | 命名空间
P | 属性（包括索引器）
T | 类型（如类、委托、枚举，接口和结构）
! | 错误字符串；字符串的其他部分提供有关错误的信息。例如，文档生成器对无法解析的链接生成错误信息。

· 字符串的第二部分是元素的完全限定名，从命名空间的根开始。元素的名称、包含着它的类型和命名空间都以句点分隔。如果项名本身含有句点，则将用 # (U+0023) 字符替换。（这里假定所有元素名中都没有“# (U+0023)”字符。）

· 对于带有参数的方法和属性，接着是用括号括起来的参数列表。对于那些不带参数的方法和属性，则省略括号。多个参数以逗号分隔。各个参数的编码与 CLI 签名相同：参数由其完全限定名表示，如下所示。例如，int 变成 System.Int32、string 变成 System.String、object 变成 System.Object 等。具有 out 或 ref 修饰符的参数在其类型名后跟有 @ 符。对于由值传递或通过 params 传递的参数没有特殊表示法。数组参数表示为 [ 下限 : 大小 , ... , 下限 : 大小 ]，其中逗号数量等于秩减去一，而下限和每个维的大小（如果已知）用十进制数表示。如果未指定下限或大小，它将被省略。如果省略了某个特定维的下限及大小，则“:”也将被省略。交错数组由每个级别一个“[]”来表示。指针类型为非 void 的参数用类型名后面跟一个 * 的形式来表示。void 指针用类型名 System.Void 表示。


B.3.2ID字符串示例.2316981

B.3.2 ID 字符串示例

下列各个示例分别显示一段 C# 代码以及为每个可以含有文档注释的源元素生成的 ID 字符串：

· 类型用它们的完全限定名来表示。

enum Color { Red, Blue, Green }

namespace Acme

{

interface IProcess {...}

struct ValueType {...}

class Widget: IProcess

{

public class NestedClass {...}

public interface IMenuItem {...}

public delegate void Del(int i);

public enum Direction { North, South, East, West }

}

}

"T:Color"

"T:Acme.IProcess"

"T:Acme.ValueType"

"T:Acme.Widget"

"T:Acme.Widget.NestedClass"

"T:Acme.Widget.IMenuItem"

"T:Acme.Widget.Del"

"T:Acme.Widget.Direction"

· 字段用它们的完全限定名来表示。

namespace Acme

{

struct ValueType

{

private int total;

}

class Widget: IProcess

{

public class NestedClass

{

private int value;

}

private string message;

private static Color defaultColor;

private const double PI = 3.14159;

protected readonly double monthlyAverage;

private long[] array1;

private Widget[,] array2;

private unsafe int *pCount;

private unsafe float **ppValues;

}

}

"F:Acme.ValueType.total"

"F:Acme.Widget.NestedClass.value"

"F:Acme.Widget.message"

"F:Acme.Widget.defaultColor"

"F:Acme.Widget.PI"

"F:Acme.Widget.monthlyAverage"

"F:Acme.Widget.array1"

"F:Acme.Widget.array2"

"F:Acme.Widget.pCount"

"F:Acme.Widget.ppValues"

· 构造函数。

namespace Acme

{

class Widget: IProcess

{

static Widget() {...}

public Widget() {...}

public Widget(string s) {...}

}

}

"M:Acme.Widget.#cctor"

"M:Acme.Widget.#ctor"

"M:Acme.Widget.#ctor(System.String)"

· 析构函数。

namespace Acme

{

class Widget: IProcess

{

~Widget() {...}

}

}

"M:Acme.Widget.Finalize"

· 方法。

namespace Acme

{

struct ValueType

{

public void M(int i) {...}

}

class Widget: IProcess

{

public class NestedClass

{

public void M(int i) {...}

}

public static void M0() {...}

public void M1(char c, out float f, ref ValueType v) {...}

public void M2(short[] x1, int[,] x2, long[][] x3) {...}

public void M3(long[][] x3, Widget[][,,] x4) {...}

public unsafe void M4(char *pc, Color **pf) {...}

public unsafe void M5(void *pv, double *[][,] pd) {...}

public void M6(int i, params object[] args) {...}

}

}

"M:Acme.ValueType.M(System.Int32)"

"M:Acme.Widget.NestedClass.M(System.Int32)"

"M:Acme.Widget.M0"

"M:Acme.Widget.M1(System.Char,System.Single@,Acme.ValueType@)"

"M:Acme.Widget.M2(System.Int16[],System.Int32[0:,0:],System.Int64[][])"

"M:Acme.Widget.M3(System.Int64[][],Acme.Widget[0:,0:,0:][])"

"M:Acme.Widget.M4(System.Char*,Color**)"

"M:Acme.Widget.M5(System.Void*,System.Double*[0:,0:][])"

"M:Acme.Widget.M6(System.Int32,System.Object[])"

· 属性和索引器。

namespace Acme

{

class Widget: IProcess

{

public int Width { get {...} set {...} }

public int this[int i] { get {...} set {...} }

public int this[string s, int i] { get {...} set {...} }

}

}

"P:Acme.Widget.Width"

"P:Acme.Widget.Item(System.Int32)"

"P:Acme.Widget.Item(System.String,System.Int32)"

· 事件。

namespace Acme

{

class Widget: IProcess

{

public event Del AnEvent;

}

}

"E:Acme.Widget.AnEvent"

· 一元运算符。

namespace Acme

{

class Widget: IProcess

{

public static Widget operator+(Widget x) {...}

}

}

"M:Acme.Widget.op_UnaryPlus(Acme.Widget)"

下面列出可使用的一元运算符函数名称的完整集合：op_UnaryPlus、op_UnaryNegation、op_LogicalNot、op_OnesComplement、op_Increment、op_Decrement、op_True 和 op_False。

· 二元运算符。

namespace Acme

{

class Widget: IProcess

{

public static Widget operator+(Widget x1, Widget x2) {...}

}

}

"M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)"

下面列出可使用的二元运算符函数名称的完整集合：op_Addition、op_Subtraction、op_Multiply、op_Division、op_Modulus、op_BitwiseAnd、op_BitwiseOr、op_ExclusiveOr、op_LeftShift、op_RightShift、op_Equality、op_Inequality、op_LessThan、op_LessThanOrEqual、op_GreaterThan 和 op_GreaterThanOrEqual。

· 转换运算符具有一个尾随“~”，然后再跟返回类型。

namespace Acme

{

class Widget: IProcess

{

public static explicit operator int(Widget x) {...}

public static implicit operator long(Widget x) {...}

}

}

"M:Acme.Widget.op_Explicit(Acme.Widget)~System.Int32"

"M:Acme.Widget.op_Implicit(Acme.Widget)~System.Int64"


B.4示例.2335466

Powered By Gavin http://www.CSharpWeb.com

B.4 示例

下面几节提供一个示例。


B.4.1C#源代码.2335785

B.4.1 C# 源代码

下面的示例列出一个 Point 类的源代码：

namespace Graphics

{

/// <remarks>Class <c>Point</c> models a point in a two-dimensional plane.

/// </remarks>

public class Point

{

/// <summary>Instance variable <c>x</c> represents the point's

/// x-coordinate.</summary>

private int x;

/// <summary>Instance variable <c>y</c> represents the point's

/// y-coordinate.</summary>

private int y;

/// <value>Property <c>X</c> represents the point's x-coordinate.</value>

public int X

{

get { return x; }

set { x = value; }

}

/// <value>Property <c>Y</c> represents the point's y-coordinate.</value>

public int Y

{

get { return y; }

set { y = value; }

}

/// <summary>This constructor initializes the new Point to

/// (0,0).</summary>

public Point() : this(0,0) {}

/// <summary>This constructor initializes the new Point to

   /// (<paramref
name="xor"/>,<paramref
name="yor"/>).</summary>

/// <param><c>xor</c> is the new Point's x-coordinate.</param>

/// <param><c>yor</c> is the new Point's y-coordinate.</param>

public Point(int xor, int yor) {

X = xor;

Y = yor;

}

/// <summary>This method changes the point's location to

/// the given coordinates.</summary>

/// <param><c>xor</c> is the new x-coordinate.</param>

/// <param><c>yor</c> is the new y-coordinate.</param>

/// <see cref="Translate"/>

public void Move(int xor, int yor) {

X = xor;

Y = yor;

}

/// <summary>This method changes the point's location by

/// the given x- and y-offsets.

/// <example>For example:

/// <code>

/// Point p = new Point(3,5);

/// p.Translate(-1,3);

/// </code>

/// results in <c>p</c>'s having the value (2,8).

/// </example>

/// </summary>

/// <param><c>xor</c> is the relative x-offset.</param>

/// <param><c>yor</c> is the relative y-offset.</param>

/// <see cref="Move"/>

public void Translate(int xor, int yor) {

X += xor;

Y += yor;

}

/// <summary>This method determines whether two Points have the same

/// location.</summary>

/// <param><c>o</c> is the object to be compared to the current object.

/// </param>

/// <returns>True if the Points have the same location and they have

/// the exact same type; otherwise, false.</returns>

/// <seealso cref="operator=="/>

/// <seealso cref="operator!="/>

public override bool Equals(object o) {

if (o == null) {

return false;

}

if (this == o) {

return true;

}

if (GetType() == o.GetType()) {

Point p = (Point)o;

return (X == p.X) && (Y == p.Y);

}

return false;

}

/// <summary>Report a point's location as a string.</summary>

/// <returns>A string representing a point's location, in the form (x,y),

/// without any leading, training, or embedded whitespace.</returns>

public override string ToString() {

return "(" + X + "," + Y + ")";

}

/// <summary>This operator determines whether two Points have the same

/// location.</summary>

/// <param><c>p1</c> is the first Point to be compared.</param>

/// <param><c>p2</c> is the second Point to be compared.</param>

/// <returns>True if the Points have the same location and they have

/// the exact same type; otherwise, false.</returns>

/// <seealso cref="Equals"/>

/// <seealso cref="operator!="/>

public static bool operator==(Point p1, Point p2) {

if ((object)p1 == null || (object)p2 == null) {

return false;

}

if (p1.GetType() == p2.GetType()) {

return (p1.X == p2.X) && (p1.Y == p2.Y);

}

return false;

}

/// <summary>This operator determines whether two Points have the same

/// location.</summary>

/// <param><c>p1</c> is the first Point to be compared.</param>

/// <param><c>p2</c> is the second Point to be compared.</param>

/// <returns>True if the Points do not have the same location and the

/// exact same type; otherwise, false.</returns>

/// <seealso cref="Equals"/>

/// <seealso cref="operator=="/>

public static bool operator!=(Point p1, Point p2) {

return !(p1 == p2);

}

/// <summary>This is the entry point of the Point class testing

/// program.

/// <para>This program tests each method and operator, and

/// is intended to be run after any non-trvial maintenance has

/// been performed on the Point class.</para></summary>

public static void Main() {

// class test code goes here

}

}

}


B.4.2产生的XML.2349933

B.4.2 产生的 XML

以下是文档生成器根据给定类 Point 的源代码（如上所示）所产生的输出：

<?xml version="1.0"?>

<doc>

<assembly>

<name>Point</name>

</assembly>

<members>

<member name="T:Graphics.Point">

<remarks>Class <c>Point</c> models a point in a two-dimensional

plane.

</remarks>

</member>

<member name="F:Graphics.Point.x">

<summary>Instance variable <c>x</c> represents the point's

x-coordinate.</summary>

</member>

<member name="F:Graphics.Point.y">

<summary>Instance variable <c>y</c> represents the point's

y-coordinate.</summary>

</member>

<member name="M:Graphics.Point.#ctor">

<summary>This constructor initializes the new Point to

(0,0).</summary>

</member>

<member name="M:Graphics.Point.#ctor(System.Int32,System.Int32)">

<summary>This constructor initializes the new Point to

(<paramref name="xor"/>,<paramref name="yor"/>).</summary>

<param><c>xor</c> is the new Point's x-coordinate.</param>

<param><c>yor</c> is the new Point's y-coordinate.</param>

</member>

<member name="M:Graphics.Point.Move(System.Int32,System.Int32)">

<summary>This method changes the point's location to

the given coordinates.</summary>

<param><c>xor</c> is the new x-coordinate.</param>

<param><c>yor</c> is the new y-coordinate.</param>

<see cref="M:Graphics.Point.Translate(System.Int32,System.Int32)"/>

</member>

<member

name="M:Graphics.Point.Translate(System.Int32,System.Int32)">

<summary>This method changes the point's location by

the given x- and y-offsets.

<example>For example:

<code>

Point p = new Point(3,5);

p.Translate(-1,3);

</code>

results in <c>p</c>'s having the value (2,8).

</example>

</summary>

<param><c>xor</c> is the relative x-offset.</param>

<param><c>yor</c> is the relative y-offset.</param>

<see cref="M:Graphics.Point.Move(System.Int32,System.Int32)"/>

</member>

<member name="M:Graphics.Point.Equals(System.Object)">

<summary>This method determines whether two Points have the same

location.</summary>

<param><c>o</c> is the object to be compared to the current

object.

</param>

<returns>True if the Points have the same location and they have

the exact same type; otherwise, false.</returns>

<seealso

cref="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"/>

<seealso

cref="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"/>

</member>

<member name="M:Graphics.Point.ToString">

<summary>Report a point's location as a string.</summary>

<returns>A string representing a point's location, in the form

(x,y),

without any leading, training, or embedded whitespace.</returns>

</member>

<member

name="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)">

<summary>This operator determines whether two Points have the

same

location.</summary>

<param><c>p1</c> is the first Point to be compared.</param>

<param><c>p2</c> is the second Point to be compared.</param>

<returns>True if the Points have the same location and they have

the exact same type; otherwise, false.</returns>

<seealso cref="M:Graphics.Point.Equals(System.Object)"/>

<seealso

cref="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"/>

</member>

<member

name="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)">

<summary>This operator determines whether two Points have the

same

location.</summary>

<param><c>p1</c> is the first Point to be compared.</param>

<param><c>p2</c> is the second Point to be compared.</param>

<returns>True if the Points do not have the same location and

the

exact same type; otherwise, false.</returns>

<seealso cref="M:Graphics.Point.Equals(System.Object)"/>

<seealso

cref="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"/>

</member>

<member name="M:Graphics.Point.Main">

<summary>This is the entry point of the Point class testing

program.

<para>This program tests each method and operator, and

is intended to be run after any non-trvial maintenance has

been performed on the Point class.</para></summary>

</member>

<member name="P:Graphics.Point.X">

<value>Property <c>X</c> represents the point's

x-coordinate.</value>

</member>

<member name="P:Graphics.Point.Y">

<value>Property <c>Y</c> represents the point's

y-coordinate.</value>

</member>

</members>

</doc>


C.语法.2367206

C. 语法

此附录是主文档中描述的词法和语法以及不安全代码的语法扩展的摘要。这里，各语法产生式是按它们在主文档中出现的顺序列出的。

C.1 词法

input:（输入：）

input-section opt（输入节可选）

input-section:（输入节：）

input-section-part（输入节部分）
input-section input-section-part（输入节 输入节部分）

input-section-part:（输入节部分：）

input-elements opt new-line（输入元素可选 新行）
pp-directive（pp 指令）

input-elements:（输入元素：）

input-element（输入元素）
input-elements input-element（输入元素 输入元素）

input-element:（输入元素：）

whitespace（空白）
comment（注释）
token（标记）

C.1.1 行结束符

new-line:（新行：）

回车符 (U+000D)
换行符 (U+000A)
回车符 (U+000D) 后跟换行符 (U+000A)
行分隔符 (U+2028)
段落分隔符 (U+2029)

C.1.2 空白

whitespace:（空白：）

任何含 Unicode 类 Zs 的字符
水平制表符 (U+0009)
垂直制表符 (U+000B)
换页符 (U+000C)

C.1.3 注释

comment:（注释：）

single-line-comment（单行注释）
delimited-comment（带分隔符的注释）

single-line-comment:（单行注释：）

// input-characters opt（// 输入字符可选）

input-characters:（输入字符：）

input-character（输入字符）
input-characters input-character（输入字符 输入字符）

input-character:（输入字符：）

除换行符外的任何 Unicode 字符

new-line-character:（换行符：）

回车符 (U+000D)
换行符 (U+000A)
行分隔符 (U+2028)
段落分隔符 (U+2029)

delimited-comment:（带分隔符的注释：）

/* delimited-comment-characters opt */（/* 带分隔符的注释字符可选 */）

delimited-comment-characters:（带分隔符的注释字符：）

delimited-comment-character（带分隔符的注释字符）
delimited-comment-characters delimited-comment-character（带分隔符的注释字符 带分隔符的注释字符）

delimited-comment-character:（带分隔符的注释字符：）

not-asterisk（非星号）
* not-slash（* 非斜杠）

not-asterisk:（非星号：）

除 * 外的任何 Unicode 字符

not-slash:（非斜杠：）

除 / 外的任何 Unicode 字符

C.1.4 标记

token:（标记：）

identifier（标识符）
keyword（关键字）
integer-literal（整数）
real-literal（实数）
character-literal（字符）
string-literal（字符串）
operator-or-punctuator（运算符或标点）

C.1.5 Unicode 字符转义序列

unicode-escape-sequence:（unicode 转义序列：）

\u hex-digit hex-digit hex-digit hex-digit（\u 十六进制数字 十六进制数字 十六进制数字 十六进制数字）
\U hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit（\U 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字 十六进制数字）

C.1.6 标识符

identifier:（标识符：）

available-identifier（可用的标识符）
@ identifier-or-keyword（@ 标识符或关键字）

available-identifier:（可用的标识符：）

不是“关键字”的“标识符或关键字”

identifier-or-keyword:（标识符或关键字：）

identifier-start-character identifier-part-characters opt（标识符开始字符 标识符部分字符可选）

identifier-start-character:（标识符开始字符：）

letter-character（字母字符）
_ (the underscore character U+005F)（下划线字符 U+005F）

identifier-part-characters:（标识符部分字符：）

identifier-part-character（标识符部分字符）
identifier-part-characters identifier-part-character（标识符部分字符 标识符部分字符）

identifier-part-character:（标识符部分字符：）

letter-character（字母字符）
decimal-digit-character（十进制数字字符）
connecting-character（连接字符）
combining-character（组合字符）
formatting-character（格式设置字符）

letter-character:（字母字符：）

类 Lu、Ll、Lt、Lm、Lo 或 Nl 的 Unicode 字符
表示类 Lu、Ll、Lt、Lm、Lo 或 Nl 的字符的 unicode 转义序列

combining-character:（组合字符：）

类 Mn 或 Mc 的 Unicode 字符
表示类 Mn 或 Mc 的字符的 unicode 转义序列

decimal-digit-character:（十进制数字字符：）

类 Nd 的 Unicode 字符
表示类 Nd 的字符的 unicode 转义序列

connecting-character:（连接字符：）

类 Pc 的 Unicode 字符
表示类 Pc 的字符的 unicode 转义序列

formatting-character:（格式设置字符：）

类 Cf 的 Unicode 字符
表示类 Cf 的字符的 unicode 转义序列

C.1.7 关键字

keyword: one of（关键字：下列之一）

abstract | as | base | bool | break | byte | case
catch | char | checked | class | const | continue | decimal
default | delegate | do | double | else | enum | event
explicit | extern | false | finally | fixed | float | for
foreach | goto | if | implicit | in | int | interface
internal | is | lock | long | namespace | new | null
object | operator | out | override | params | private | protected
public | readonly | ref | return | sbyte | sealed | short
sizeof | stackalloc | static | string | struct | switch | this
throw | true | try | typeof | uint | ulong | unchecked
unsafe | ushort | using | virtual | void | volatile | while

C.1.8 文本

literal:（文本：）

boolean-literal（布尔值）
integer-literal（整数）
real-literal（实数）
character-literal（字符）
string-literal（字符串）
null-literal（空值）

boolean-literal:（布尔值：）

true
false

integer-literal:（整数：）

decimal-integer-literal（十进制整数）
hexadecimal-integer-literal（十六进制整数）

decimal-integer-literal:（十进制整数：）

decimal-digits integer-type-suffix opt（十进制数字 整数类型后缀可选）

decimal-digits:（十进制数字：）

decimal-digit（十进制数字）
decimal-digits decimal-digit（十进制数字 十进制数字）

decimal-digit: one of（十进制数字：下列之一）

0 1 2 3 4 5 6 7 8 9

integer-type-suffix: one of（整数类型后缀：下列之一）

U u L l UL Ul uL ul LU Lu lU lu

hexadecimal-integer-literal:（十六进制整数：）

0x hex-digits integer-type-suffix opt（0x 十六进制数字 整型后缀可选）
0X hex-digits integer-type-suffix opt（0X 十六进制数字 整型后缀可选）

hex-digits:（十六进制数字：）

hex-digit（十六进制数字）
hex-digits hex-digit（十六进制数字 十六进制数字）

hex-digit: one of（十六进制数字：下列之一）

0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

real-literal:（实数：）

decimal-digits . decimal-digits exponent-part opt real-type-suffix opt（十进制数字 . 十进制数字 指数部分可选 实数类型后缀可选）
. decimal-digits exponent-part opt real-type-suffix opt（. 十进制数字 指数部分可选 实数类型后缀可选）
decimal-digits exponent-part real-type-suffix opt（十进制数字 指数部分 实数类型后缀可选）
decimal-digits real-type-suffix（十进制数字 实数类型后缀）

exponent-part:（指数部分：）

e sign opt decimal-digits（e 符号可选 十进制数字）
E sign opt decimal-digits（E 符号可选 十进制数字）

sign: one of（符号：下列之一）

+ -

real-type-suffix: one of（实数类型后缀：下列之一）

F f D d M m

character-literal:（字符：）

' character '（' 字符 '）

character:（字符：）

single-character（单字符）
simple-escape-sequence（简单转义序列）
hexadecimal-escape-sequence（十六进制转义序列）
unicode-escape-sequence（unicode 转义序列）

single-character:（单字符：）

除 ' (U+0027)、\ (U+005C) 和换行符外的任何字符

simple-escape-sequence: one of（简单转义序列：下列之一）

\' \" \\ \0 \a \b \f \n \r \t \v

hexadecimal-escape-sequence:（十六进制转义序列：）

\x hex-digit hex-digit opt hex-digit opt hex-digit opt（\x 十六进制数字 十六进制数字可选 十六进制数字可选 十六进制数字可选）

string-literal:（字符串：）

regular-string-literal（规则字符串）
verbatim-string-literal（逐字的字符串）

regular-string-literal:（规则字符串：）

" regular-string-literal-characters opt "（" 规则字符串字符可选 "）

regular-string-literal-characters:（规则字符串字符：）

regular-string-literal-character（规则字符串字符）
regular-string-literal-characters regular-string-literal-character（规则字符串字符 规则字符串字符）

regular-string-literal-character:（规则字符串字符：）

single-regular-string-literal-character（单个规则字符串字符）
simple-escape-sequence（简单转义序列）
hexadecimal-escape-sequence（十六进制转义序列）
unicode-escape-sequence（unicode 转义序列）

single-regular-string-literal-character:（单个规则字符串字符：）

除 " (U+0022)、\ (U+005C) 和换行符外的任何字符

verbatim-string-literal:（逐字的字符串：）

@" verbatim -string-literal-characters opt "（@" 逐字的字符串字符可选 "）

verbatim-string-literal-characters:（逐字的字符串字符：）

verbatim-string-literal-character（逐字的字符串字符）
verbatim-string-literal-characters verbatim-string-literal-character（逐字的字符串字符 逐字的字符串字符）

verbatim-string-literal-character:（逐字的字符串字符：）

single-verbatim-string-literal-character（单个逐字的字符串字符）
quote-escape-sequence（引号转义序列）

single-verbatim-string-literal-character:（单个逐字的字符串字符：）

除 " 外的任何字符

quote-escape-sequence:（引号转义序列：）

""

null-literal:（空文本：）

null

C.1.9 运算符和标点符号

operator-or-punctuator:（运算符或标点符号：下列之一）

{ } [ ] ( ) . , : ;
+ - * / % & |^ ! ~
= < > ? ++ -- && || << >>
== != <= >= += -= *= /= %= &=
|= ^= <<= >>= ->

C.1.10 预处理指令

pp-directive:（pp 指令：）

pp-declaration（pp 声明）
pp-conditional（pp 条件）
pp-line（pp 行）
pp-diagnostic（pp 诊断）
pp-region（pp 区域）

pp-new-line:（pp 新行：）

whitespace opt single-line-comment opt new-line（空白可选 单行注释可选 新行）

conditional-symbol:（条件符号：）

除 true 和 false 外的任何标识符或关键字

pp-expression:（pp 表达式：）

whitespace opt pp-or-expression whitespace opt（空白可选 pp 或表达式 空白可选）

pp-or-expression:（pp 或表达式：）

pp-and-expression（pp 与表达式）
pp-or-expression whitespace opt || whitespace opt pp-and-expression（pp 或表达式 空白可选 || 空白可选 pp 与表达式）

pp-and-expression:（pp 与表达式：）

pp-equality-expression（pp 相等表达式）
pp-and-expression whitespace opt && whitespace opt pp-equality-expression（pp 与表达式 空白可选 && 空白可选 pp 相等表达式）

pp-equality-expression:（pp 相等表达式：）

pp-unary-expression（pp 一元表达式）
pp-equality-expression whitespace opt == whitespace opt pp-unary-expression（pp 相等表达式 空白可选 == 空白可选 pp 一元表达式）
pp-equality-expression whitespace opt != whitespace opt pp-unary-expression（pp 相等表达式 空白可选 != 空白可选 pp 一元表达式）

pp-unary-expression:（pp 一元表达式：）

pp-primary-expression（pp 基本表达式）
! whitespace opt pp-unary-expression（! 空白可选 pp 一元表达式）

pp-primary-expression:（pp 基本表达式：）

true
false
conditional-symbol（条件符号）
( whitespace opt pp-expression whitespace opt )（(空白可选 pp 表达式 空白可选 )）

pp-declaration:（pp 声明：）

whitespace opt # whitespace opt define whitespace conditional-symbol pp-new-line（空白可选 # 空白可选 define 空白 条件符号 pp 新行）
whitespace opt # whitespace opt undef whitespace conditional-symbol pp-new-line（空白可选 # 空白可选 undef 空白 条件符号 pp 新行）

pp-conditional:（pp 条件：）

pp-if-section pp-elif-sections opt pp-else-section opt pp-endif（pp if 节 pp elif 节可选 pp else 节可选 pp endif）

pp-if-section:（pp if 节：）

whitespace opt # whitespace opt if whitespace pp-expression pp-new-line conditional-section opt（空白可选 # 空白可选 if 空白 pp 表达式 pp 新行 条件节可选）

pp-elif-sections:（pp elif 节：）

pp-elif-section（pp elif 节）
pp-elif-sections pp-elif-section（pp elif 节 pp elif 节）

pp-elif-section:（pp elif 节：）

whitespace opt # whitespace opt elif whitespace pp-expression pp-new-line conditional-section opt（空白可选 # 空白可选 elif 空白 pp 表达式 pp 新行 条件节可选）

pp-else-section:（pp-else 节：）

whitespace opt # whitespace opt else pp-new-line conditional-section opt（空白可选 # 空白可选 else pp 新行 条件节可选）

pp-endif:

whitespace opt # whitespace opt endif pp-new-line（空白可选 # 空白可选 endif pp 新行）

conditional-section:（条件节：）

input-section（输入节）
skipped-section（跳过节）

skipped-section:（跳过节：）

skipped-section-part（跳过节部分）
skipped-section skipped-section-part（跳过节 跳过节部分）

skipped-section-part:（跳过节部分：）

skipped-characters opt new-line（跳过字符可选 新行）
pp-directive（pp 指令）

skipped-characters:（跳过字符：）

whitespace opt not-number-sign input-characters opt（空白可选 非数字符号 输入字符可选）

not-number-sign:（非数字符号：）

除 # 外的任何输入字符

pp-line:（pp 行：）

whitespace opt # whitespace opt line whitespace line-indicator pp-new-line（空白可选 # 空白可选 line 空白 行指示符 pp 新行）

line-indicator:（行指示符：）

decimal-digits whitespace file-name（十进制数字 空白 文件名）
decimal-digits（十进制数字）
default（default）

file-name:（文件名：）

" file-name-characters "（" 文件名字符 "）

file-name-characters:（文件名字符：）

file-name-character（文件名字符）
file-name-characters file-name-character（文件名字符 文件名字符）

file-name-character:（文件名字符：）

除 " 外的任何输入字符

pp-diagnostic:（pp 诊断：）

whitespace opt # whitespace opt error pp-message（空白可选 # 空白可选 error pp 消息）
whitespace opt # whitespace opt warning pp-message（空白可选 # 空白可选 warning pp 消息）

pp-message:（pp 消息：）

new-line（新行）
whitespace input-characters opt new-line（空白 输入字符可选 新行）

pp-region:（pp 区域：）

pp-start-region conditional-section opt pp-end-region（pp 开始区域 条件节可选 pp 结束区域）

pp-start-region:（pp 开始区域：）

whitespace opt # whitespace opt region pp-message（空白可选 # 空白可选 region pp 消息）

pp-end-region:（pp 结束区域：）

whitespace opt # whitespace opt endregion pp-message（空白可选 # 空白可选 endregion pp 消息）

C.2 语法

C.2.1 基本概念

namespace-name:（命名空间名称：）

namespace-or-type-name（命名空间或类型名称）

type-name:（类型名：）

namespace-or-type-name（命名空间或类型名称）

namespace-or-type-name:（命名空间或类型名称：）

identifier（标识符）
namespace-or-type-name . identifier（命名空间或类型名称 . 标识符）

C.2.2 类型

type:（类型：）

value-type（值类型）
reference-type（引用类型）

value-type:（值类型：）

struct-type（结构类型）
enum-type（枚举类型）

struct-type:（结构类型：）

type-name（类型名称）
simple-type（简单类型）

simple-type:（简单类型：）

numeric-type（数值类型）
bool

numeric-type:（数值类型：）

integral-type（整型）
floating-point-type（浮点类型）
decimal

integral-type:（整型：）

sbyte
byte
short
ushort
int
uint
long
ulong
char

floating-point-type:（浮点类型：）

float
double

enum-type:（枚举类型：）

type-name（类型名）

reference-type:（引用类型：）

class-type（类类型）
interface-type（接口类型）
array-type（数组类型）
delegate-type（委托类型）

class-type:（类类型：）

type-name（类型名）
object
string

interface-type:（接口类型：）

type-name（类型名）

array-type:（数组类型：）

non-array-type rank-specifiers（非数组类型 秩说明符）

non-array-type:（非数组类型：）

type（类型）

rank-specifiers:（秩说明符：）

rank-specifier（秩说明符）
rank-specifiers rank-specifier（秩说明符 秩说明符）

rank-specifier:（秩说明符：）

[ dim-separators opt ]（[ 维度分隔符可选 ]）

dim-separators:（维度分隔符：）

，
dim-separators ,（维度分隔符 ,）

delegate-type:（委托类型：）

type-name（类型名）

C.2.3 变量

variable-reference:（变量引用：）

expression（表达式）

C.2.4 表达式

argument-list:（参数列表：）

argument（参数）
argument-list , argument（参数列表 , 参数）

argument:（参数：）

expression（表达式）
ref variable-reference（ref 变量引用）
out variable-reference（out 变量引用）

primary-expression:（基本表达式：）

primary-no-array-creation-expression（非数组创建基本表达式）
array-creation-expression（数组创建表达式）

primary-no-array-creation-expression:（非数组创建基本表达式：）

literal（文本）
simple-name（简称）
parenthesized-expression（带括号的表达式）
member-access（成员访问）
invocation-expression（调用表达式）
element-access（元素访问）
this-access（this 访问）
base-access（base 访问）
post-increment-expression（后增量表达式）
post-decrement-expression（后减量表达式）
object-creation-expression（对象创建表达式）
delegate-creation-expression（委托创建表达式）
typeof-expression（typeof 表达式）
sizeof-expression（sizeof 表达式）
checked-expression（checked 表达式）
unchecked-expression（unchecked 表达式）

simple-name:（简称：）

identifier（标识符）

parenthesized-expression:（带括号的表达式：）

( expression )（( 表达式 )）

member-access:（成员访问：）

primary-expression . identifier（基本表达式 . 标识符）
predefined-type . identifier（预定义类型 . 标识符）

predefined-type: one of（预定义类型：下列之一）

bool byte char decimal double float int long
object sbyte short string uint ulong ushort

invocation-expression:（调用表达式：）

primary-expression ( argument-list opt )（基本表达式 ( 参数列表可选 )）

element-access:（元素访问：）

primary-no-array-creation-expression [ expression-list ]（非数组创建基本表达式 [ 表达式列表 ]）

expression-list:（表达式列表：）

expression（表达式）
expression-list , expression（表达式列表 , 表达式）

this-access:（this 访问：）

this

base-access:（base 访问：）

base . identifier（base . 标识符）
base [ expression-list ]（base [ 表达式列表 ]）

post-increment-expression:（后增量表达式：）

primary-expression ++（基本表达式 ++）

post-decrement-expression:（后减量表达式：）

primary-expression --（基本表达式 --）

object-creation-expression:（对象创建表达式：）

new type ( argument-list opt )（new 类型 ( 参数列表可选 )）

array-creation-expression:（数组创建表达式：）

new non-array-type [ expression-list ] rank-specifiers opt array-initializer opt（new 非数组类型 [ 表达式列表 ] 秩说明符可选 数组初始值设定项可选）
new array-type array-initializer（new 数组类型 数组初始值设定项）

delegate-creation-expression:（委托创建表达式：）

new delegate-type ( expression )（new 委托类型 ( 表达式 )）

typeof-expression:（typeof 表达式：）

typeof ( type )（typeof ( 类型 )）
typeof ( void )

checked-expression:（checked 表达式：）

checked ( expression )（checked ( 表达式 )）

unchecked-expression:（unchecked 表达式：）

unchecked ( expression )（unchecked ( 表达式 )）

unary-expression:（一元表达式：）

primary-expression（基本表达式）
+ unary-expression（+ 一元表达式）
- unary-expression（- 一元表达式）
! unary-expression（! 一元表达式）
~ unary-expression（~ 一元表达式）
* unary-expression（* 一元表达式）
pre-increment-expression（前增量表达式）
pre-decrement-expression（前减量表达式）
cast-expression（强制转换表达式）

pre-increment-expression:（前增量表达式：）

++ unary-expression（++ 一元表达式）

pre-decrement-expression:（前减量表达式：）

-- unary-expression（-- 一元表达式）

cast-expression:（强制转换表达式：）

( type ) unary-expression（( 类型 ) 一元表达式）

multiplicative-expression:（乘法表达式：）

unary-expression（一元表达式）
multiplicative-expression * unary-expression（乘法表达式 * 一元表达式）
multiplicative-expression / unary-expression（乘法表达式 / 一元表达式）
multiplicative-expression % unary-expression（乘法表达式 % 一元表达式）

additive-expression:（增量表达式：）

multiplicative-expression（乘法表达式）
additive-expression + multiplicative-expression（加法表达式 + 乘法表达式）
additive-expression – multiplicative-expression（加法表达式 – 乘法表达式）

shift-expression:（移位表达式：）

additive-expression（加法表达式）
shift-expression << additive-expression（移位表达式 << 加法表达式）
shift-expression >> additive-expression（移位表达式 >> 加法表达式）

relational-expression:（关系表达式：）

shift-expression（移位表达式）
relational-expression < shift-expression（关系表达式 < 移位表达式）
relational-expression > shift-expression（关系表达式 > 移位表达式）
relational-expression <= shift-expression（关系表达式 <= 移位表达式）
relational-expression >= shift-expression（关系表达式 >= 移位表达式）
relational-expression is type（关系表达式 is 类型）
relational-expression as type（关系表达式 as 类型）

equality-expression:（相等表达式：）

relational-expression（关系表达式）
equality-expression == relational-expression（相等表达式 == 关系表达式）
equality-expression != relational-expression（相等表达式 != 关系表达式）

and-expression:（与表达式：）

equality-expression（相等表达式）
and-expression & equality-expression（与表达式 & 相等表达式）

exclusive-or-expression:（异或表达式：）

and-expression（与表达式）
exclusive-or-expression ^ and-expression（异或表达式 ^ 与表达式）

inclusive-or-expression:（或表达式：）

exclusive-or-expression（异或表达式）
inclusive-or-expression | exclusive-or-expression（异或表达式 | 异或表达式）

conditional-and-expression:（条件与表达式：）

inclusive-or-expression（或表达式）
conditional-and-expression && inclusive-or-expression（条件与表达式 && 或表达式）

conditional-or-expression:（条件或表达式：）

conditional-and-expression（条件与表达式）
conditional-or-expression || conditional-and-expression（条件或表达式 || 条件与表达式）

conditional-expression:（条件表达式：）

conditional-or-expression（条件或表达式）
conditional-or-expression ? expression : expression（条件或表达式 ? 表达式 : 表达式）

assignment:（赋值：）

unary-expression assignment-operator expression（一元表达式 赋值运算符 表达式）

assignment-operator: one of（赋值运算符：下列之一）

= += -= *= /= %= &= |= ^= <<= >>=

expression:（表达式：）

conditional-expression（条件表达式）
assignment（赋值）

constant-expression:（常数表达式：）

expression（表达式）

boolean-expression:（布尔表达式：）

expression（表达式）

C.2.5 语句

statement:（语句：）

labeled-statement（标记语句）
declaration-statement（声明语句）
embedded-statement（嵌入语句）

embedded-statement:（嵌入语句：）

block（块）
empty-statement（空语句）
expression-statement（表达式语句）
selection-statement（选择语句）
iteration-statement（迭代语句）
jump-statement（跳转语句）
try-statement（try 语句）
checked-statement（checked 语句）
unchecked-statement（unchecked 语句）
lock-statement（lock 语句）
using-statement（using 语句）

block:（块：）

{ statement-list opt }（{ 语句列表可选 }）

statement-list:（语句列表：）

statement（语句）
statement-list statement（语句列表 语句）

empty-statement:（空语句：）

;

labeled-statement:（标记语句：）

identifier : statement（标识符 : 语句）

declaration-statement:（声明语句：）

local-variable-declaration ;（局部变量声明 ;）
local-constant-declaration ;（局部常数声明 ;）

local-variable-declaration:（局部变量声明：）

type local-variable-declarators（类型 局部变量声明符）

local-variable-declarators:（局部变量声明符：）

local-variable-declarator（局部变量声明符）
local-variable-declarators , local-variable-declarator（局部变量声明符 , 局部变量声明符）

local-variable-declarator:（局部变量声明符：）

identifier（标识符）
identifier = local-variable-initializer（标识符 = 局部变量初始值设定项）

local-variable-initializer:（局部变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设定项）

local-constant-declaration:（局部常数声明：）

const type constant-declarators（const 类型 常数声明符）

constant-declarators:（常数声明符：）

constant-declarator（常数声明符）
constant-declarators , constant-declarator（常数声明符 , 常数声明符）

constant-declarator:（常数声明符：）

identifier = constant-expression（标识符 = 常数表达式）

expression-statement:（表达式语句：）

statement-expression ;（语句表达式 ;）

statement-expression:（语句表达式：）

invocation-expression（调用表达式）
object-creation-expression（对象创建表达式）
assignment（赋值）
post-increment-expression（后递增表达式）
post-decrement-expression（后递减表达式）
pre-increment-expression（前递增表达式）
pre-decrement-expression（前递减表达式）

selection-statement:（选择语句：）

if-statement（if 语句）
switch-statement（switch 语句）

if-statement:（if 语句：）

if ( boolean-expression ) embedded-statement（if ( 布尔表达式 ) 嵌入语句）
if ( boolean-expression ) embedded-statement else embedded-statement（if ( 布尔表达式 ) 嵌入语句 else 嵌入语句）

boolean-expression:（布尔表达式：）

expression（表达式）

switch-statement:（switch 语句：）

switch ( expression ) switch-block（switch ( 表达式 ) switch 块）

switch-block:（switch 块：）

{ switch-sections opt }（{ switch 节可选 }）

switch-sections:（switch 节：）

switch-section（switch 节）
switch-sections switch-section（switch 节 switch 节）

switch-section:（switch 节：）

switch-labels statement-list（switch 标签 语句列表）

switch-labels:（switch 标签：）

switch-label（switch 标签）
switch-labels switch-label（switch 标签 switch 标签）

switch-label:（switch 标签：）

case constant-expression :（case 常数表达式 :）
default :（default :）

iteration-statement:（迭代语句：）

while-statement（while 语句）
do-statement（do 语句）
for-statement（for 语句）
foreach-statement（foreach 语句）

while-statement:（while 语句：）

while ( boolean-expression ) embedded-statement（while ( 布尔表达式 ) 嵌入语句）

do-statement:（do 语句：）

do embedded-statement while ( boolean-expression ) ;（do 嵌入语句 while ( 布尔表达式 ) ;）

for-statement:（for 语句：）

for ( for-initializer opt ; for-condition opt ; for-iterator opt ) embedded-statement（for ( for 初始值设定项可选 ; for 条件可选 ; for 迭代程序可选 ) 嵌入语句）

for-initializer:（for 初始值设定项：）

local-variable-declaration（局部变量声明）
statement-expression-list（语句表达式列表）

for-condition:（for 条件：）

boolean-expression（布尔表达式）

for-iterator:（for 迭代程序：）

statement-expression-list（语句表达式列表）

statement-expression-list:（语句表达式列表：）

statement-expression（语句表达式）
statement-expression-list , statement-expression（语句表达式列表 , 语句表达式）

foreach-statement:（foreach 语句：）

foreach ( type identifier in expression ) embedded-statement（foreach ( 类型 标识符 in 表达式 ) 嵌入语句）

jump-statement:（跳转语句：）

break-statement（break 语句）
continue-statement（continue 语句）
goto-statement（goto 语句）
return-statement（return 语句）
throw-statement（throw 语句）

break-statement:（break 语句：）

break ;

continue-statement:（continue 语句：）

continue ;

goto-statement:（goto 语句：）

goto identifier ;（goto 标识符 ;）
goto case constant-expression ;（goto case 常数表达式 ;）
goto default ;（goto default ;）

return-statement:（return 语句：）

return expression opt ;（return 表达式可选 ;）

throw-statement:（throw 语句：）

throw expression opt ;（throw 表达式可选 ;）

try-statement:（try 语句：）

try block catch-clauses（try 块 catch 子句）
try block finally-clause（try 块 finally 子句）
try block catch-clauses finally-clause（try 块 catch 子句 finally 子句）

catch-clauses:（catch 子句：）

specific-catch-clauses general-catch-clause opt（特定 catch 子句 常规 catch 子句可选）
specific-catch-clauses opt general-catch-clause（特定 catch 子句可选 常规 catch 子句）

specific-catch-clauses:（特定 catch 子句：）

specific-catch-clause（特定 catch 子句）
specific-catch-clauses specific-catch-clause（特定 catch 子句 特定 catch 子句）

specific-catch-clause:（特定 catch 子句：）

catch ( class-type identifier opt ) block（catch ( 类类型 标识符可选 ) 块）

general-catch-clause:（常规 catch 子句：）

catch block（catch 块）

finally-clause:（finally 子句：）

finally block（finally 块）

checked-statement:（checked 语句：）

checked block（checked 块）

unchecked-statement:（unchecked 语句：）

unchecked block（unchecked 块）

lock-statement:（lock 语句：）

lock ( expression ) embedded-statement（lock ( 表达式 ) 嵌入语句）

using-statement:（using 语句：）

using ( resource-acquisition ) embedded-statement（using ( 资源获取 ) 嵌入语句）

resource-acquisition:（资源获取：）

local-variable-declaration（局部变量声明）
expression（表达式）

C.2.6 命名空间

compilation-unit:（编译单元：）

using-directives opt global-attributes opt namespace-member-declarations opt（using 指令可选 全局属性可选 命名空间成员声明可选）

namespace-declaration:（命名空间声明：）

namespace qualified-identifier namespace-body ;opt（命名空间 限定标识符 命名空间体 ;可选）

qualified-identifier:（限定标识符：）

identifier（标识符）
qualified-identifier . identifier（限定标识符 . 标识符）

namespace-body:（命名空间体：）

{ using-directives opt namespace-member-declarations opt }（{ using 指令可选 命名空间成员声明可选 }）

using-directives:（using 指令：）

using-directive（using 指令）
using-directives using-directive（using 指令 using 指令）

using-directive:（using 指令：）

using-alias-directive（using 别名指令）
using-namespace-directive（using 命名空间指令）

using-alias-directive:（using 别名指令：）

using identifier = namespace-or-type-name ;（using 标识符 = 命名空间或类型名称 ;）

using-namespace-directive:（using 命名空间指令：）

using namespace-name ;（using 命名空间名称 ;）

namespace-member-declarations:（命名空间成员声明：）

namespace-member-declaration（命名空间成员声明）
namespace-member-declarations namespace-member-declaration（命名空间成员声明 命名空间成员声明）

namespace-member-declaration:（命名空间成员声明：）

namespace-declaration（命名空间声明）
type-declaration（类型声明）

type-declaration:（类型声明：）

class-declaration（类声明）
struct-declaration（结构声明）
interface-declaration（接口声明）
enum-declaration（枚举声明）
delegate-declaration（委托声明）

C.2.7 类

class-declaration:（类声明：）

attributes opt class-modifiers opt class identifier class-base opt class-body ;opt（属性可选 类修饰符可选 class 标识符 类基可选 类体 ;可选）

class-modifiers:（类修饰符：）

class-modifier（类修饰符）
class-modifiers class-modifier（类修饰符 类修饰符）

class-modifier:（类修饰符：）

new
public
protected
internal
private
abstract
sealed

class-base:（类基：）

: class-type（: 类类型）
: interface-type-list（: 接口类型列表）
: class-type , interface-type-list（: 类类型 , 接口类型列表）

interface-type-list:（接口类型列表：）

interface-type（接口类型）
interface-type-list , interface-type（接口类型列表 , 接口类型）

class-body:（类体：）

{ class-member-declarations opt }（{ 类成员声明可选 }）

class-member-declarations:（类成员声明：）

class-member-declaration（类成员声明）
class-member-declarations class-member-declaration（类成员声明 类成员声明）

class-member-declaration:（类成员声明：）

constant-declaration（常数声明）
field-declaration（字段声明）
method-declaration（方法声明）
property-declaration（属性声明）
event-declaration（事件声明）
indexer-declaration（索引器声明）
operator-declaration（运算符声明）
constructor-declaration（构造函数声明）
destructor-declaration（析构函数声明）
static-constructor-declaration（静态构造函数声明）
type-declaration（类型声明）

constant-declaration:（常数声明：）

attributes opt constant-modifiers opt const type constant-declarators ;（属性可选 常数修饰符可选 const 类型 常数声明符 ;）

constant-modifiers:（常数修饰符：）

constant-modifier（常数修饰符）
constant-modifiers constant-modifier（常数修饰符 常数修饰符）

constant-modifier:（常数修饰符：）

new
public
protected
internal
private

constant-declarators:（常数声明符：）

constant-declarator（常数声明符）
constant-declarators , constant-declarator（常数声明符 , 常数声明符）

constant-declarator:（常数声明符：）

identifier = constant-expression（标识符 = 常数表达式）

field-declaration:（字段声明：）

attributes opt field-modifiers opt type variable-declarators ;（属性可选 字段修饰符可选 类型 变量声明符 ;）

field-modifiers:（字段修饰符：）

field-modifier（字段修饰符）
field-modifiers field-modifier（字段修饰符 字段修饰符）

field-modifier:（字段修饰符：）

new
public
protected
internal
private
static
readonly
volatile

variable-declarators:（变量声明符：）

variable-declarator（变量声明符）
variable-declarators , variable-declarator（变量声明符 , 变量声明符）

variable-declarator:（变量声明符：）

identifier（标识符）
identifier = variable-initializer（标识符 = 变量初始值设定项）

variable-initializer:（变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设定项）

method-declaration:（方法声明：）

method-header method-body（方法头 方法体）

method-header:（方法头：）

attributes opt method-modifiers opt return-type member-name ( formal-parameter-list opt )（属性可选 方法修饰符可选 返回类型 成员名 ( 形参表可选 )）

method-modifiers:（方法修饰符：）

method-modifier（方法修饰符）
method-modifiers method-modifier（方法修饰符 方法修饰符）

method-modifier:（方法修饰符：）

new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

return-type:（返回类型：）

type（类型）
void

member-name:（成员名：）

identifier（标识符）
interface-type . identifier（接口类型 . 标识符）

method-body:（方法体：）

block（块）
;

formal-parameter-list:（形参表：）

fixed-parameters（固定参数）
fixed-parameters , parameter-array（固定参数 , 参数数组）
parameter-array（参数数组）

fixed-parameters:（固定参数：）

fixed-parameter（固定参数）
fixed-parameters , fixed-parameter（固定参数 , 固定参数）

fixed-parameter:（固定参数：）

attributes opt parameter-modifier opt type identifier（属性可选 参数修饰符可选 类型 标识符）

parameter-modifier:（参数修饰符：）

ref
out

parameter-array:（参数数组：）

attributes opt params array-type identifier（属性可选 params 数组类型 标识符）

property-declaration:（属性声明：）

attributes opt property-modifiers opt type member-name { accessor-declarations }（属性可选 属性修饰符可选 类型 成员名 { 访问器声明 }）

property-modifiers:（属性修饰符：）

property-modifier（属性修饰符）
property-modifiers property-modifier（属性修饰符 属性修饰符）

property-modifier:（属性修饰符：）

new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

member-name:（成员名：）

identifier（标识符）
interface-type . identifier（接口类型 . 标识符）

accessor-declarations:（访问器声明：）

get-accessor-declaration set-accessor-declaration opt（get 访问器声明 set 访问器声明可选）
set-accessor-declaration get-accessor-declaration opt（set 访问器声明 get 访问器声明可选）

get-accessor-declaration:（get 访问器声明：）

attributes opt get accessor-body（属性可选 get 访问器体）

set-accessor-declaration:（set 访问器声明：）

attributes opt set accessor-body（属性可选 set 访问器体）

accessor-body:（访问器体：）

block（块）
;

event-declaration:（事件声明：）

attributes opt event-modifiers opt event type variable-declarators ;（属性可选 事件修饰符可选 event 类型 变量声明符 ;）
attributes opt event-modifiers opt event type member-name { event-accessor-declarations }（属性可选 事件修饰符可选 event 类型 成员名 { 事件访问器声明 }）

event-modifiers:（事件修饰符：）

event-modifier（事件修饰符）
event-modifiers event-modifier（事件修饰符 事件修饰符）

event-modifier:（事件修饰符：）

new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

event-accessor-declarations:（事件访问器声明：）

add-accessor-declaration remove-accessor-declaration（添加访问器声明 移除访问器声明）
remove-accessor-declaration add-accessor-declaration（移除访问器声明 添加访问器声明）

add-accessor-declaration:（添加访问器声明：）

attributes opt add block（属性可选 add 块）

remove-accessor-declaration:（移除访问器声明：）

attributes opt remove block（属性可选 remove 块）

indexer-declaration:（索引器声明：）

attributes opt indexer-modifiers opt indexer-declarator { accessor-declarations }（属性可选 索引器修饰符可选 索引器声明符 { 访问器声明 }）

indexer-modifiers:（索引器修饰符：）

indexer-modifier（索引器修饰符）
indexer-modifiers indexer-modifier（索引器修饰符 索引器修饰符）

indexer-modifier:（索引器修饰符：）

new
public
protected
internal
private
virtual
sealed
override
abstract
extern

indexer-declarator:（索引器声明符：）

type this [ formal-parameter-list ]（类型 this [ 形参表 ]）
type interface-type . this [ formal-parameter-list ]（类型 接口类型 . this [ 形参表 ]）

operator-declaration:（运算符声明：）

attributes opt operator-modifiers operator-declarator operator-body（属性可选 运算符修饰符 运算符声明符 运算符体）

operator-modifiers:（运算符修饰符：）

operator-modifier（运算符修饰符）
operator-modifiers operator-modifier（运算符修饰符 运算符修饰符）

operator-modifier:（运算符修饰符：）

public
static
extern

operator-declarator:（运算符声明符：）

unary-operator-declarator（一元运算符声明符）
binary-operator-declarator（二元运算符声明符）
conversion-operator-declarator（转换运算符声明符）

unary-operator-declarator:（一元运算符声明符：）

type operator overloadable-unary-operator ( type identifier )（类型 operator 可重载的一元运算符 ( 类型 标识符 )）

overloadable-unary-operator: one of（可重载的一元运算符：下列之一）

+ - ! ~ ++ -- true false

binary-operator-declarator:（二元运算符声明符：）

type operator overloadable-binary-operator ( type identifier , type identifier )（类型 operator 可重载的二元运算符 ( 类型 标识符 , 类型 标识符 )）

overloadable-binary-operator: one of（可重载的二元运算符：下列之一）

+ - * / % & | ^ << >> == != > < >= <=

conversion-operator-declarator:（转换运算符声明符：）

implicit operator type ( type identifier )（implicit operator 类型 ( 类型 标识符 )）
explicit operator type ( type identifier )（explicit operator 类型 ( 类型 标识符 )）

operator-body:（运算符体：）

block（块）
;

constructor-declaration:（构造函数声明：）

attributes opt constructor-modifiers opt constructor-declarator constructor-body（属性可选 构造函数修饰符可选 构造函数声明符 构造函数体）

constructor-modifiers:（构造函数修饰符：）

constructor-modifier（构造函数修饰符）
constructor-modifiers constructor-modifier（构造函数修饰符 构造函数修饰符）

constructor-modifier:（构造函数修饰符：）

public
protected
internal
private
extern

constructor-declarator:（构造函数声明符：）

identifier ( formal-parameter-list opt ) constructor-initializer opt（标识符 ( 形参表可选 ) 构造函数初始值设定项可选）

constructor-initializer:（构造函数初始值设定项：）

: base ( argument-list opt )（: base ( 参数列表可选 )）
: this ( argument-list opt )（: this ( 参数列表可选 )）

constructor-body:（构造函数体：）

block（块）
;

static-constructor-declaration:（静态构造函数声明：）

attributes opt static-constructor-modifiers identifier ( ) static-constructor-body（属性可选 静态构造函数修饰符 标识符 ( ) 静态构造函数体）

static-constructor-modifiers:（静态构造函数修饰符：）

externopt static（extern可选 static）
static externopt（static extern可选）

static-constructor-body:（静态构造函数体：）

block（块）
;

destructor-declaration:（析构函数声明：）

attributes opt externopt ~ identifier ( ) destructor-body（属性可选 extern可选 ~ 标识符 ( ) 析构函数体）

destructor-body:（析构函数体：）

block（块）
;

C.2.8 结构

struct-declaration:（结构声明：）

attributes opt struct-modifiers opt struct identifier struct-interfaces opt struct-body ;opt（属性可选 结构修饰符可选 struct 标识符 结构接口可选 结构体 ;可选）

struct-modifiers:（结构修饰符：）

struct-modifier（结构修饰符）
struct-modifiers struct-modifier（结构修饰符 结构修饰符）

struct-modifier:（结构修饰符：）

new
public
protected
internal
private

struct-interfaces:（结构接口：）

: interface-type-list（: 接口类型列表）

struct-body:（结构体：）

{ struct-member-declarations opt }（{ 结构成员声明可选 }）

struct-member-declarations:（结构成员声明：）

struct-member-declaration（结构成员声明）
struct-member-declarations struct-member-declaration（结构成员声明 结构成员声明）

struct-member-declaration:（结构成员声明：）

constant-declaration（常数声明）
field-declaration（字段声明）
method-declaration（方法声明）
property-declaration（属性声明）
event-declaration（事件声明）
indexer-declaration（索引器声明）
operator-declaration（运算符声明）
constructor-declaration（构造函数声明）
static-constructor-declaration（静态构造函数声明）
type-declaration（类型声明）

C.2.9 数组

array-type:（数组类型：）

non-array-type rank-specifiers（非数组类型 秩说明符）

non-array-type:（非数组类型：）

type（类型）

rank-specifiers:（秩说明符：）

rank-specifier（秩说明符）
rank-specifiers rank-specifier（秩说明符 秩说明符）

rank-specifier:（秩说明符：）

[ dim-separators opt ]（[ 维度分隔符可选 ]）

dim-separators:（维度分隔符：）

，
dim-separators ,（维度分隔符 ,）

array-initializer:（数组初始值设定项：）

{ variable-initializer-list opt }（{ 变量初始值设定项列表可选 }）
{ variable-initializer-list , }（{ 变量初始值设定项列表 , }）

variable-initializer-list:（变量初始值设定项列表：）

variable-initializer（变量初始值设定项）
variable-initializer-list , variable-initializer（变量初始值设定项列表 , 变量初始值设定项）

variable-initializer:（变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设定项）

C.2.10 接口

interface-declaration:（接口声明：）

attributes opt interface-modifiers opt interface identifier interface-base opt interface-body ;opt（属性可选 接口修饰符可选 interface 标识符 接口基可选 接口体 ;可选）

interface-modifiers:（接口修饰符：）

interface-modifier（接口修饰符）
interface-modifiers interface-modifier（接口修饰符 接口修饰符）

interface-modifier:（接口修饰符：）

new
public
protected
internal
private

interface-base:（接口基：）

: interface-type-list（: 接口类型列表）

interface-body:（接口体：）

{ interface-member-declarations opt }（{ 接口成员声明可选 }）

interface-member-declarations:（接口成员声明：）

interface-member-declaration（接口成员声明）
interface-member-declarations interface-member-declaration（接口成员声明 接口成员声明）

interface-member-declaration:（接口成员声明：）

interface-method-declaration（接口方法声明）
interface-property-declaration（接口属性声明）
interface-event-declaration（接口事件声明）
interface-indexer-declaration（接口索引器声明）

interface-method-declaration:（接口方法声明：）

attributes opt newopt return-type identifier ( formal-parameter-list opt ) ;（属性可选 new可选 返回类型 标识符 ( 形参表可选 ) ;）

interface-property-declaration:（接口属性声明：）

attributes opt newopt type identifier { interface-accessors }（属性可选 new可选 类型 标识符 { 接口访问器 }）

interface-accessors:（接口访问器：）

attributes opt get ;（属性可选 get ;）
attributes opt set ;（属性可选 set ;）
attributes opt get ; attributes opt set ;（属性可选 get ; 属性可选 set ;）
attributes opt set ; attributes opt get ;（属性可选 set ; 属性可选 get ;）

interface-event-declaration:（接口事件声明：）

attributes opt newopt event type identifier ;（属性可选 new可选 event 类型 标识符 ;）

interface-indexer-declaration:（接口索引器声明：）

attributes opt newopt type this [ formal-parameter-list ] { interface-accessors }（属性可选 new可选 类型 this [ 形参表 ] { 接口访问器 }）

C.2.11 枚举

enum-declaration:（枚举声明：）

attributes opt enum-modifiers opt enum identifier enum-base opt enum-body ;opt（属性可选 枚举修饰符可选 enum 标识符 枚举基可选 枚举体 ;可选）

enum-base:（枚举基：）

: integral-type（: 整型）

enum-body:（枚举体：）

{ enum-member-declarations opt }（{ 枚举成员声明可选 }）
{ enum-member-declarations , }（{ 枚举成员声明 , }）

enum-modifiers:（枚举修饰符：）

enum-modifier（枚举修饰符）
enum-modifiers enum-modifier（枚举修饰符 枚举修饰符）

enum-modifier:（枚举修饰符：）

new
public
protected
internal
private

enum-member-declarations:（枚举成员声明：）

enum-member-declaration（枚举成员声明）
enum-member-declarations , enum-member-declaration（枚举成员声明 , 枚举成员声明）

enum-member-declaration:（枚举成员声明：）

attributes opt identifier（属性可选 标识符）
attributes opt identifier = constant-expression（属性可选 标识符 = 常数表达式）

C.2.12 委托

delegate-declaration:（委托声明：）

attributes opt delegate-modifiers opt delegate return-type identifier ( formal-parameter-list opt ) ;（属性可选 委托修饰符可选 delegate 返回类型 标识符 ( 形参表可选 ) ;）

delegate-modifiers:（委托修饰符：）

delegate-modifier（委托修饰符）
delegate-modifiers delegate-modifier（委托修饰符 委托修饰符）

delegate-modifier:（委托修饰符：）

new
public
protected
internal
private

C.2.13 属性

global-attributes:（全局属性：）

global-attribute-sections（全局属性节）

global-attribute-sections:（全局属性节：）

global-attribute-section（全局属性节）
global-attribute-sections global-attribute-section（全局属性节全局属性节）

global-attribute-section:（全局属性节：）

[ global-attribute-target-specifier attribute-list ]（[ 全局属性目标说明符 属性列表 ]）
[ global-attribute-target-specifier attribute-list ,]（[ 全局属性目标说明符 属性列表 ,]）

global-attribute-target-specifier:（全局属性目标说明符：）

global-attribute-target:（全局属性目标：）

global-attribute-target:（全局属性目标：）

assembly（程序集）
module（模块）

attributes:（属性：）

attribute-sections（属性节）

attribute-sections:（属性节：）

attribute-section（属性节）
attribute-sections attribute-section（属性节 属性节）

attribute-section:（属性节：）

[ attribute-target-specifieropt attribute-list ]（[ 属性目标说明符可选 属性列表 ]）
[ attribute-target-specifieropt attribute-list , ]（[ 属性目标说明符可选 属性列表 , ]）

attribute-target-specifier:（属性目标说明符：）

attribute-target :（属性目标 :）

attribute-target:（属性目标：）

field（字段）
event（事件）
method（方法）
param（参数）
property（属性）
return（返回）
type（类型）

attribute-list:（属性列表：）

attribute（属性）
attribute-list , attribute（属性列表 , 属性）

attribute:（属性：）

attribute-name attribute-argumentsopt（属性名 属性参数可选）

attribute-name:（属性名：）

type-name（类型名）

attribute-arguments:（属性参数：）

( positional-argument-listopt )（( 定位参数列表可选 )）
( positional-argument-list , named-argument-list )（( 定位参数列表 , 命名参数列表 )）
( named-argument-list )（( 命名参数列表 )）

positional-argument-list:（定位参数列表：）

positional-argument（定位参数）
positional-argument-list , positional-argument（定位参数列表 , 定位参数）

positional-argument:（定位参数：）

attribute-argument-expression（属性参数表达式）

named-argument-list:（命名参数列表：）

named-argument（命名参数）
named-argument-list , named-argument（命名参数列表 , 命名参数）

named-argument:（命名参数：）

identifier = attribute-argument-expression（标识符 = 属性参数表达式）

attribute-argument-expression:（属性参数表达式：）

expression（表达式）

C.3 不安全代码的语法扩展

class-modifier:（类修饰符：）

...
unsafe

struct-modifier:（结构修饰符：）

...
unsafe

interface-modifier:（接口修饰符：）

...
unsafe

delegate-modifier:（委托修饰符：）

...
unsafe

field-modifier:（字段修饰符：）

...
unsafe

method-modifier:（方法修饰符：）

...
unsafe

property-modifier:（属性修饰符：）

...
unsafe

event-modifier:（事件修饰符：）

...
unsafe

indexer-modifier:（索引器修饰符：）

...
unsafe

operator-modifier:（运算符修饰符：）

...
unsafe

constructor-modifier:（构造函数修饰符：）

...
unsafe

destructor-declaration:（析构函数声明：）

attributes opt externopt unsafeopt ~ identifier ( ) destructor-body（属性可选 extern可选 unsafe可选 ~ 标识符 ( ) 析构函数体）
attributes opt unsafeopt externopt ~ identifier ( ) destructor-body（属性可选 unsafe可选 extern可选 ~ 标识符 ( ) 析构函数体）

static-constructor-modifiers:（静态构造函数修饰符：）

externopt unsafeopt static（extern可选 unsafe可选 静态）
unsafeopt externopt static（unsafe可选 extern可选 静态）
externopt static unsafeopt（extern可选 静态 unsafe可选）
unsafeopt static externopt（unsafe可选 静态 extern可选）
static externopt unsafeopt（静态 extern可选 unsafe可选）
static unsafeopt externopt（静态 unsafe可选 extern可选）

embedded-statement:（嵌入语句：）

...
unsafe-statement（不安全语句）

unsafe-statement:（不安全语句：）

unsafe block（unsafe 块）

type:（类型：）

value-type（值类型）
reference-type（引用类型）
pointer-type（指针类型）

pointer-type:（指针类型：）

unmanaged-type *（非托管类型 *）
void *（void *）

unmanaged-type:（非托管类型）

type（类型）

primary-no-array-creation-expression:（非数组创建基本表达式：）

...
pointer-member-access（指针成员访问）
pointer-element-access（指针元素访问）
sizeof-expression（sizeof 表达式）

unary-expression:（一元表达式：）

...
pointer-indirection-expression（指针间接寻址表达式）
addressof-expression（addressof 表达式）

pointer-indirection-expression:（指针间接寻址表达式：）

* unary-expression（* 一元表达式）

pointer-member-access:（指针成员访问：）

primary-expression -> identifier（基本表达式 -> 标识符）

pointer-element-access:（指针元素访问：）

primary-no-array-creation-expression [ expression ]（非数组创建基本表达式 [ 表达式 ]）

addressof-expression:（addressof 表达式：）

& unary-expression（& 一元表达式）

sizeof-expression:（sizeof 表达式：）

sizeof ( unmanaged-type )（sizeof ( 非托管类型 )）

embedded-statement:（嵌入语句：）

...
fixed-statement（固定语句）

fixed-statement:（固定语句：）

fixed ( pointer-type fixed-pointer-declarators ) embedded-statement（fixed ( 指针类型 固定指针声明符 ) 嵌入语句）

fixed-pointer-declarators:（固定指针声明符：）

fixed-pointer-declarator（固定指针声明符）
fixed-pointer-declarators , fixed-pointer-declarator（固定指针声明符 , 固定指针声明符）

fixed-pointer-declarator:（固定指针声明符：）

identifier = fixed-pointer-initializer（标识符 = 固定指针初始值设定项）

fixed-pointer-initializer:（固定指针初始值设定项：）

& variable-reference（& 变量引用）
expression（表达式）

variable-initializer:（变量初始值设定项：）

expression（表达式）
array-initializer（数组初始值设置项）
stackalloc-initializer（stackalloc 初始值设置项）

stackalloc-initializer:（stackalloc 初始值设定项：）

stackalloc unmanaged-type [ expression ]（stackalloc 非托管类型 [ 表达式 ]）


D.参考书目.2727561

Powered By Gavin http://www.CSharpWeb.com

D. 参考书目

Unicode 联合会。The Unicode Standard, Version 3.0（Unicode 标准 3.0 版）。Addison-Wesley，Reading，Massachusetts，2000，ISBN 0-201-616335-5。

IEEE。IEEE Standard for Binary Floating-Point Arithmetic（二进制浮点算法 IEEE 标准）。ANSI/IEEE Standard 754-1985。可从下列站点得到：http://www.ieee.org。

ISO/IEC。C++。ANSI/ISO/IEC 14882:1998。