![C# 10核心技术指南](https://wfqqreader-1252317822.image.myqcloud.com/cover/89/52513089/b_52513089.jpg)
2.11 语句
函数是由语句构成的。语句按照出现的字面顺序执行。语句块则是包含在大括号({})中的一系列语句。
2.11.1 声明语句
变量声明语句可以声明新的变量,并可以用表达式初始化变量。我们可以用逗号分隔的列表声明多个同类型的变量:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0094-08.jpg?sign=1739338678-koN80JQhl0vaCXWRxSkbaKWhqCEfNV6l-0-fac144e361cd0b1f1c0f0434890a6068)
常量的声明和变量类似,但是它的值无法在声明之后改变,并且变量初始化必须和声明同时进行(请参见3.1.2节):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-01.jpg?sign=1739338678-oO2AVWfS3mmFchP3qICXvYJFu1zDeBgn-0-22924ed49d0834db828867b8a5218dea)
局部变量
局部变量和常量的作用范围在当前的语句块中。在当前语句块或者嵌套的语句块中无法声明同名的局部变量:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-02.jpg?sign=1739338678-BduFXXfXwTm80qxxJz3EtZRgnfJQBeGz-0-94fd044cb796a0f22dc58ee18ce9b548)
变量的作用范围是其所在的整个代码块(包括前向和后向)。这意味着虽然在变量或常量声明之前引用它是不合法的,但即使将示例中的x初始化移动到方法的末尾我们也会得到相同的错误,这个奇怪的规则和C++是不同的。
2.11.2 表达式语句
表达式语句既是表达式也是合法的语句。表达式语句必须改变状态或者执行某些可能改变状态的调用。状态改变本质上指改变一个变量的值。可能的表达式语句有:
· 赋值表达式(包括自增和自减表达式)
· (有返回值的和没有返回值的)方法调用表达式
· 对象实例化表达式
例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-04.jpg?sign=1739338678-rrRiS2gI3j53FXrUPAKvCbIXfkqzMNRr-0-acbe52e5f8fc77852f46c9c457ee1c93)
即使调用的构造器或方法有返回值,也并不一定要使用该值。因此除非构造器或方法改变了某些状态,否则以下这些语句完全没有用处:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-01.jpg?sign=1739338678-WFpoANK7VIsarMYRTlHXlXrfvbIuMtdX-0-0ba7723b4bd5943436f22df21fdbdfd1)
2.11.3 选择语句
C#使用以下几种机制来有条件地控制程序的执行流:
· 选择语句(if、switch)
· 条件语句(?:)
· 循环语句(while、do..while、for和foreach)
本节将介绍两种最简单的结构:if语句和switch语句。
2.11.3.1 if语句
if语句在bool表达式为真时执行其中的语句,例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-02.jpg?sign=1739338678-oL7NIzsyts5k4AWCDTXDK0wjj5M8NI5h-0-f5e07f9fb7cc2a8cb3db9edd29afaf62)
if中的语句可以是代码块:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-03.jpg?sign=1739338678-rtNvzLvtz6AnWDH8hrZEyUmfvhjiKogj-0-9d86bd0ab29740a4b90341fdb0c80f0a)
2.11.3.2 else子句
if语句之后可以紧跟else子句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-04.jpg?sign=1739338678-HzSKwqWWOIhGirJ2eTkA1NCeFY0SZqU2-0-607e7d5c0f78d36193b4fad2c231e44d)
在else子句中,能嵌套另一个if语句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-05.jpg?sign=1739338678-M3qvkIpRE5Z6Fmi9TbXERIFyPPtz6QzO-0-b135065d2221e45b110e9a5d4246b026)
2.11.3.3 用大括号改变执行流
else子句总是与它之前的语句块中紧邻的未配对的if语句结合:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-01.jpg?sign=1739338678-igOQgHzrld7T3ZTa8kQYUWL4NwPJ1ulu-0-9dc74a5fb3f989a560720bff00edae0b)
语义上等价于:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-02.jpg?sign=1739338678-UbMBBZe67jTYO6NpwHkaN86xLGsJJuWG-0-7a47866f0ed6596b1f5ab956669033e5)
可以通过改变大括号的位置来改变执行流:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-03.jpg?sign=1739338678-wfvpC7PF8VG6SD1apWidPuFekBl2E7mK-0-51f1f5fea1caaf243313f58585bf89b8)
大括号可以明确表明结构,这能提高嵌套if语句的可读性(虽然编译器并不需要)。需要特别指出的是下面的模式:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-04.jpg?sign=1739338678-2O5YtVMC9XRPqxwqykODrjKhJ2y33cuJ-0-ba2db78f9349d36f20a34c550954bed1)
这里,我们参照其他语言的“elseif”结构(以及C#本身的#elif预处理指令)来安排if和else语句。Visual Studio会自动识别这个模式并保持代码缩进。从语义上讲,紧跟着每一个if语句的else语句从功能上都是嵌套在else子句之中的。
2.11.3.4 switch语句
switch语句可以根据变量可能的取值来转移程序的执行。switch语句可以拥有比嵌套if语句更加简洁的代码,因为switch语句仅仅需要一次表达式计算:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-05.jpg?sign=1739338678-keTaYYgPEM0V10YEmR9TfBFUkIEAglaU-0-c5c3ac3b1dac3cde4a85b1d5a519332f)
这个例子演示了最一般的情形,即针对常量的switch。当指定常量时,只能指定内置的整数类型、bool、char、enum类型以及string类型。
每一个case子句结束时必须使用某种跳转指令显式指定下一个执行点(除非你的代码本身就是一个无限循环),这些跳转指令有:
· break(跳转到switch语句的最后)
· goto case x(跳转到另外一个case子句)
· goto default(跳转到default子句)
· 其他的跳转语句,例如,return、throw、continue或者goto label
当多个值需要执行相同的代码时,可以按照顺序列出共同的case条件:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0098-01.jpg?sign=1739338678-qF6bXzookR5jyrt0zojJa0Z9IvltPefE-0-caede5e09c9092dbd098889d18ed03d4)
switch语句的这种特性可以写出比多个if-else更加简洁的代码。
2.11.3.5 按类型switch
按照类型进行switch是带有模式的switch语句的一种特殊的使用情况,最近几版C#语言引入了多种模式。有关模式的完整讨论,请参见4.13节。
C#支持按类型switch(从C# 7开始):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-01.jpg?sign=1739338678-mSjhntsuNi1L2lpyGSQGtW7Qc2N42stH-0-9c39e01b8f38c75e6c609913046be596)
(object类型允许其变量为任何类型,这部分内容将在3.2节和3.3节详细讨论。)
每一个case子句都指定了一种需要匹配的类型和一个变量(模式变量),如果类型匹配成功就对变量赋值。和常量不同,子句对可用的类型并没有进行任何限制。
when关键字可用于对case进行预测,例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-02.jpg?sign=1739338678-fVNAU4edy924WAtW6nAEuewVJ8X3cAnr-0-8ce936a98a618530b0dc89f317d0d704)
case子句的顺序会影响类型的选择(这和选择常量的情况有些不同)。如果交换case的顺序,则上述示例可以得到完全不同的结果(事实上,上述程序甚至无法编译,因为编译器发现第二个case子句是永远不会执行的)。但default子句是一个例外,不论它出现在什么地方都会在最后才执行。
如果希望按照类型进行switch,但对其值却并不关心,这种情况下可以使用“丢弃”变量(_):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-03.jpg?sign=1739338678-v2qowlPKBjNfBEXFLo8lUulix7Wz5prb-0-af71057f752f9e767173b9804d6a97f1)
堆叠多个case子句也是没有问题的。在下面的例子中,Console.WriteLine会在任何浮点类型的值大于1000时执行:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-01.jpg?sign=1739338678-FYphC32Pag77jppvGWnbjhonHvCeK2mL-0-6c0f9f3f1fe835bfcb42ba5a1bd2d6cc)
在上述例子中,编译器仅允许在when子句中使用模式变量f、d和m。当调用Console.WriteLine时,我们并不清楚到底三个模式变量中的哪一个会被赋值,因而编译器会将它们放在作用域之外。
除此以外,还可以混合使用常量选择和模式选择,甚至可以选择null值:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-02.jpg?sign=1739338678-a4tWSgalU8V7fa4cRBKIUwN0Y96yLS7X-0-cf92a860996b40ac8d1725acb72b4f39)
2.11.3.6 switch表达式
从C# 8开始,我们可以在表达式中使用switch。以下示例展示了该功能的使用方法,其中,假定变量cardNumber是int类型:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-03.jpg?sign=1739338678-tKxevRPG0FdaMECt6teKHVhI8ppFiuxR-0-0afa6b98593da6f11e907c83a03b345c)
注意,switch是在变量名称之后出现的,且其中的case子句相应地变为了以逗号结尾的表达式(而不再是语句)。switch表达式相比switch语句更加紧凑,且可以用于LINQ查询(请参见第8章)。
如果在switch表达式中忽略默认表达式(_)同时其他条件匹配失败,则会抛出一个异常。
switch表达式也支持多变量的选择(元组模式):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-04.jpg?sign=1739338678-yvoknAhvfIQUZFf7SrjJnaCTMPS7kba6-0-2c30993928ae770825a98cf5c2a2773b)
switch表达式与各种模式组合可以获得更多的选择效果,详情请参见4.13节。
2.11.4 迭代语句
C#中可以使用while、do-while、for和foreach语句重复执行一系列语句。
2.11.4.1 while和do-while循环
while循环在bool表达式为true的情况下重复执行循环体中的代码。该表达式在循环体执行之前进行检测。例如,以下示例将输出012:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-01.jpg?sign=1739338678-4uQGdtkIRDVb5Lo1KAQd9WJM1J08en4z-0-40c3eb82dfa3c844caffc95af70c68c1)
do-while循环在功能上不同于while循环的地方是前者在语句块执行之后才检查表达式的值(保证语句块至少执行过一次)。以下是用do-while循环重新书写上述例子:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-02.jpg?sign=1739338678-4k3Djx4n8YiAvTS8LQpErc1iJL2OvSbH-0-80a8319fb66e62aeda7cdb9db2afa2ab)
2.11.4.2 for循环
for循环就像一个有特殊子句的while循环,这些特殊子句用于初始化和迭代循环变量。for循环有以下三个子句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-03.jpg?sign=1739338678-noxQfvtWoVmWTzoSOd5CbJkfcHRqwN2J-0-5f3ca0c63283f17779ce126d85a2c8f1)
每一个子句的作用如下:
初始化子句
在循环之前执行,初始化一个或多个迭代变量。
条件子句
它是一个bool表达式,当取值为true时,将执行循环体。
迭代子句
在每次语句块迭代之后执行,通常用于更新迭代变量。
例如,下面的例子将输出0到2的数字:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-04.jpg?sign=1739338678-rrUVJfQOF8nVCtG7cHLiDTQPC3Sclsgq-0-238dabb679518b241131881aa91d5d96)
下面的代码将输出前10个斐波那契数(每一个数都是前面两个数的和):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-01.jpg?sign=1739338678-hxMYCoGP0diJykFG3SIFBCKyPAeZtblY-0-08265b31ba1db5399b2f5623727922c3)
for语句的这三个部分都可以省略,因而可以通过下面的代码来实现无限循环(也可以用while (true)来代替):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-02.jpg?sign=1739338678-9eKrE2RXNhxgxZz9zssZ9aMBehoFUAQP-0-ecf4de1734a349bb4665cb98eccebb00)
2.11.4.3 foreach循环
foreach语句遍历可枚举对象的每一个元素,.NET中大多数表示集合或元素列表的类型都是可枚举的。例如,数组和字符串都是可枚举的。以下示例从头到尾枚举了字符串中的每一个字符:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-03.jpg?sign=1739338678-zxk0JAKCmoxXOtcAtK0wAPmLxc9yY0sD-0-07486b76a77e9b440fc06ba9f72fabd9)
以上程序的输出为:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-04.jpg?sign=1739338678-3EUqHxQoLLKL8M9HYkcDuP2YFW8R3RRv-0-a88b84c0f65da7777b916d3b591cd9a0)
我们将在4.6节详细介绍可枚举对象。
2.11.5 跳转语句
C#的跳转语句有break、continue、goto、return和throw。
跳转语句仍然遵守try语句的可靠性规则(参见4.5节),因此:
· 若跳转语句跳转到try语句块之外,则它总是在达到目标之前执行try语句的finally语句块。
· 跳转语句不能从finally语句块内跳到块外(除非使用throw)。
2.11.5.1 break语句
break语句用于结束迭代或switch语句的执行:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-06.jpg?sign=1739338678-3lFuQhSAomTBfKRU4AvTzcpwWclRbbWG-0-2adf4e642b194c671a8b30ca68a126f7)
2.11.5.2 continue语句
continue语句放弃循环体中后续的语句,继续下一轮迭代。例如,以下的循环跳过了偶数:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-01.jpg?sign=1739338678-tVc4ddvnEnppzmNKrTuw8hMPkI3qvlI1-0-8c59b0bdb90240b982cd6467cf8519a8)
2.11.5.3 goto语句
goto语句将执行点转移到语句块中的指定标签处,格式如下:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-02.jpg?sign=1739338678-FNXkpUBp5P3wxw1dmgndHBjBzzCEK9Cw-0-12da8c73190dbf8ff1133abe0e98902b)
或用于switch语句内:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-03.jpg?sign=1739338678-0Dq6KBkTR5xwqWXqDgz36eJNnGiJjmh1-0-bb16c16cd9b968486c7d1eaba1fc079c)
标签语句仅仅是代码块中的占位符,位于语句之前,用冒号后缀表示。下面的代码模拟for循环来遍历从1到5的数字:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-04.jpg?sign=1739338678-WSdi3jbmq0aqnsphjsvgj3dD09Ww4IfP-0-916ac21d12ae8a2434146a318bfcd777)
goto case case-constant会将执行点转移到switch语句块中的另一个条件上(参见2.11.3.4节)。
2.11.5.4 return语句
return语句用于退出方法。如果这个方法有返回值,则必须返回方法指定返回类型的表达式。
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-05.jpg?sign=1739338678-vexAVZ9KTlUiLGs5h3Jw8KUTQev4nPlh-0-e0854c1dae2d2bb14c06d4ca98cfc91d)
return语句能够出现在方法的任意位置(除finally块中),并且可以多次出现。
2.11.5.5 throw语句
throw语句抛出异常来表示有错误发生(参见4.5节):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0104-01.jpg?sign=1739338678-f7OIUq6XVNhqdzt8b0zLixmgBB5xm9ek-0-693b0dc72e7ef76be576dd163ddc35e7)
2.11.6 其他语句
using语句用一种优雅的语法在finally块中调用实现了IDisposable接口对象的Dispose方法(请参见4.5节和12.1节)。
C#重载了using关键字,使它在不同上下文中有不同的含义。特别地,using指令和using语句是不同的。
lock语句是调用Mintor类型的Enter和Exit方法的简化写法(请参见第14章和第23章)。