![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=1739673799-Tl4XsvHKMLQxX9FX4QaUaBdpUmGjMyX9-0-c301721c7c233508fcf082fa40b1bcff)
常量的声明和变量类似,但是它的值无法在声明之后改变,并且变量初始化必须和声明同时进行(请参见3.1.2节):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-01.jpg?sign=1739673799-zlv8qS0Dgmvoq0Y49DvoL0W4xk7JXMmN-0-d64fb59a6f504ad3ac898f77d14a1f85)
局部变量
局部变量和常量的作用范围在当前的语句块中。在当前语句块或者嵌套的语句块中无法声明同名的局部变量:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-02.jpg?sign=1739673799-CiZiv8YjM213mGYY97veZ5Mu5lBK3ITo-0-d40208f1ae6ff9de963dc3fc669d1613)
变量的作用范围是其所在的整个代码块(包括前向和后向)。这意味着虽然在变量或常量声明之前引用它是不合法的,但即使将示例中的x初始化移动到方法的末尾我们也会得到相同的错误,这个奇怪的规则和C++是不同的。
2.11.2 表达式语句
表达式语句既是表达式也是合法的语句。表达式语句必须改变状态或者执行某些可能改变状态的调用。状态改变本质上指改变一个变量的值。可能的表达式语句有:
· 赋值表达式(包括自增和自减表达式)
· (有返回值的和没有返回值的)方法调用表达式
· 对象实例化表达式
例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-04.jpg?sign=1739673799-D9H0PODfaxY9dnokfcOE9xW9WSZC0fSO-0-a5dc9ea4b57640f3080a38e0ff53bbda)
即使调用的构造器或方法有返回值,也并不一定要使用该值。因此除非构造器或方法改变了某些状态,否则以下这些语句完全没有用处:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-01.jpg?sign=1739673799-VI4Q4K2i9otVKeYFC31izFRcdXhdntx1-0-81ae2d30716b4f0b2ff408b88ed6bee2)
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=1739673799-qWaM6Xb3F9ENsoJF59J4BUupXGmWghQb-0-d541347390c7b7954cb0574710635012)
if中的语句可以是代码块:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-03.jpg?sign=1739673799-t90kT9DLvyAXC2ren3wksopKZd29Jhv9-0-191eec2844b14db97bc0af541ea55d9f)
2.11.3.2 else子句
if语句之后可以紧跟else子句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-04.jpg?sign=1739673799-Q1DKxywYJgbbZFAJvxPaEvBFckyNtft8-0-2570e2e8865ba87b85b760f1faf7c214)
在else子句中,能嵌套另一个if语句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-05.jpg?sign=1739673799-fOzqQBrkQDXejtWIonPbo7r1HvNnKNxQ-0-f9c3f635f1872f4d4c9f13650bb237d0)
2.11.3.3 用大括号改变执行流
else子句总是与它之前的语句块中紧邻的未配对的if语句结合:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-01.jpg?sign=1739673799-ld44hAgK3f0yjDLF1z5ktaQPWaZfmEKq-0-060d1465b1651e5ddefca439298684f6)
语义上等价于:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-02.jpg?sign=1739673799-vsNWK8Qb2JNN8EMgtO1xmZgaqFQUsTvx-0-d0df8e2395cb3ca8da77379683e1ad5d)
可以通过改变大括号的位置来改变执行流:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-03.jpg?sign=1739673799-aIc4ANlb713QXLSsPDpiKsYd4ocp7O9N-0-dcd565e3a1cc14c31db18bf8e7b30c2e)
大括号可以明确表明结构,这能提高嵌套if语句的可读性(虽然编译器并不需要)。需要特别指出的是下面的模式:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-04.jpg?sign=1739673799-dgD9feLCPdtg7M2VnGxxRnJIi60OOJwv-0-d751d25c337c74af2d0fb092b0a9c4bf)
这里,我们参照其他语言的“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=1739673799-Wo4LWQ5owQwwVpDwtqJppDS7l1xqjA8U-0-fc7be99dc02bc87f5fab0915b81beb4f)
这个例子演示了最一般的情形,即针对常量的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=1739673799-8eC4xHhqe8BkyRzUR6QFMhCHbiHeF7Yb-0-f0bcbbd341523d9aa79f230e90fbef51)
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=1739673799-mnD9kaWfDkt59NKuEG45Mh1cSZZveGkS-0-4fead9e34604018b4a46cbefaeda9998)
(object类型允许其变量为任何类型,这部分内容将在3.2节和3.3节详细讨论。)
每一个case子句都指定了一种需要匹配的类型和一个变量(模式变量),如果类型匹配成功就对变量赋值。和常量不同,子句对可用的类型并没有进行任何限制。
when关键字可用于对case进行预测,例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-02.jpg?sign=1739673799-1XHGBxuXR2pVxLpJK2egkQngmcRS0oX5-0-09714f6492e8404cdc73ce33c1b0cd61)
case子句的顺序会影响类型的选择(这和选择常量的情况有些不同)。如果交换case的顺序,则上述示例可以得到完全不同的结果(事实上,上述程序甚至无法编译,因为编译器发现第二个case子句是永远不会执行的)。但default子句是一个例外,不论它出现在什么地方都会在最后才执行。
如果希望按照类型进行switch,但对其值却并不关心,这种情况下可以使用“丢弃”变量(_):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-03.jpg?sign=1739673799-JYoD8aq9ffDhwcHEXAJ0nDB6zQ3aGdRn-0-475194a880773e64490dd13b26cca390)
堆叠多个case子句也是没有问题的。在下面的例子中,Console.WriteLine会在任何浮点类型的值大于1000时执行:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-01.jpg?sign=1739673799-wMSHmosWRl8sg5973f3FiuXS1eswgZH9-0-2b659950b13c7d6ae2a358620d53bdd2)
在上述例子中,编译器仅允许在when子句中使用模式变量f、d和m。当调用Console.WriteLine时,我们并不清楚到底三个模式变量中的哪一个会被赋值,因而编译器会将它们放在作用域之外。
除此以外,还可以混合使用常量选择和模式选择,甚至可以选择null值:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-02.jpg?sign=1739673799-azfKJCPpzBzKi9hBzdATtcQx5UIhGnY9-0-abf590881d1f62dc4b8d36d0d7c4f081)
2.11.3.6 switch表达式
从C# 8开始,我们可以在表达式中使用switch。以下示例展示了该功能的使用方法,其中,假定变量cardNumber是int类型:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-03.jpg?sign=1739673799-dXjVWQ6nHtqWbXchHBLU6MyzHeosMU9S-0-697d23a221046e197008af3ac11cc0b2)
注意,switch是在变量名称之后出现的,且其中的case子句相应地变为了以逗号结尾的表达式(而不再是语句)。switch表达式相比switch语句更加紧凑,且可以用于LINQ查询(请参见第8章)。
如果在switch表达式中忽略默认表达式(_)同时其他条件匹配失败,则会抛出一个异常。
switch表达式也支持多变量的选择(元组模式):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-04.jpg?sign=1739673799-SDPo9hjd5ro3tulLLTYhE3lqPg0gHcB1-0-a84e649f833c9e5e091785ca9e980f30)
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=1739673799-AakgLwLyiDw49iR8cRmjsmLmDRYKrSRW-0-7cb50bb055462146732054b51e594697)
do-while循环在功能上不同于while循环的地方是前者在语句块执行之后才检查表达式的值(保证语句块至少执行过一次)。以下是用do-while循环重新书写上述例子:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-02.jpg?sign=1739673799-xkGhUQttJ6O3x4xG4ahZdgDtZIYLcGOU-0-33097163cecf3189f533eed63d71a817)
2.11.4.2 for循环
for循环就像一个有特殊子句的while循环,这些特殊子句用于初始化和迭代循环变量。for循环有以下三个子句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-03.jpg?sign=1739673799-II7duy6lp4pofCpIEGm58C5fYKLA47sS-0-26988d1601859fefcd7434d50db7571c)
每一个子句的作用如下:
初始化子句
在循环之前执行,初始化一个或多个迭代变量。
条件子句
它是一个bool表达式,当取值为true时,将执行循环体。
迭代子句
在每次语句块迭代之后执行,通常用于更新迭代变量。
例如,下面的例子将输出0到2的数字:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-04.jpg?sign=1739673799-oHxewJZTooHCfbQozHD4csdzoAGr8yvd-0-71f918cd3e05dbe24ede0c460c5d52e0)
下面的代码将输出前10个斐波那契数(每一个数都是前面两个数的和):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-01.jpg?sign=1739673799-xQnkeDm8YwHAkbbnjj0B3s2gDZEpnj9x-0-035c6eba830799adfc488f8111d57484)
for语句的这三个部分都可以省略,因而可以通过下面的代码来实现无限循环(也可以用while (true)来代替):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-02.jpg?sign=1739673799-aOPdEkYZrzqVLnxc3ZVqznJwysevn2PS-0-b37d42c9cb8babb267fa4da68afffb1a)
2.11.4.3 foreach循环
foreach语句遍历可枚举对象的每一个元素,.NET中大多数表示集合或元素列表的类型都是可枚举的。例如,数组和字符串都是可枚举的。以下示例从头到尾枚举了字符串中的每一个字符:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-03.jpg?sign=1739673799-mZrKmAehn4ATiQPLAJT6gWWGKTRvYGOM-0-0f9bba9b67d73443ca5b40be585169aa)
以上程序的输出为:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-04.jpg?sign=1739673799-dbpdIOUKD22kU6LbxTSrClvO4qAMoJsz-0-864f2e2e470c3cb10484383c2673299c)
我们将在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=1739673799-zpG7cDIh6fmlcMhwRd3hldtQEjq1S77n-0-f5441e2bb5fb6262055f992b61b7341e)
2.11.5.2 continue语句
continue语句放弃循环体中后续的语句,继续下一轮迭代。例如,以下的循环跳过了偶数:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-01.jpg?sign=1739673799-HUSzuStDwHFlo97ZEenV0KpMKWZ8CLmH-0-76cdb558bc21f99182b5a2a3d130821c)
2.11.5.3 goto语句
goto语句将执行点转移到语句块中的指定标签处,格式如下:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-02.jpg?sign=1739673799-Dh4xhBhfOJL0TldfAzkfifRKP01tWKjE-0-720c66ebcd9f1f39604da6ec8fa8c470)
或用于switch语句内:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-03.jpg?sign=1739673799-XwbGNvLO3cpigETdB0a2jRQohHgwsYDJ-0-796011b65ac49218f7c768f71678ca15)
标签语句仅仅是代码块中的占位符,位于语句之前,用冒号后缀表示。下面的代码模拟for循环来遍历从1到5的数字:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-04.jpg?sign=1739673799-Fld3HNQx4C5qIFoSh62QreQuZejH7Irg-0-aa07bfc7c2c6d053c405ad8b07118589)
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=1739673799-lXCvKLJxzVl90OU7olG5ihAlOyYQCws3-0-df69c730f4f84f3e68c60dc3c5ff991c)
return语句能够出现在方法的任意位置(除finally块中),并且可以多次出现。
2.11.5.5 throw语句
throw语句抛出异常来表示有错误发生(参见4.5节):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0104-01.jpg?sign=1739673799-0uWquzvw5ZbWGhzho9zMXqWcn4wIXodN-0-56b68a1739e422434f21441170aed93c)
2.11.6 其他语句
using语句用一种优雅的语法在finally块中调用实现了IDisposable接口对象的Dispose方法(请参见4.5节和12.1节)。
C#重载了using关键字,使它在不同上下文中有不同的含义。特别地,using指令和using语句是不同的。
lock语句是调用Mintor类型的Enter和Exit方法的简化写法(请参见第14章和第23章)。