![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=1739250762-V6MmCUQeWdivhCuf7KObUZtdQAFJXCMZ-0-3bc3738cdc5bd644859327e91736e335)
常量的声明和变量类似,但是它的值无法在声明之后改变,并且变量初始化必须和声明同时进行(请参见3.1.2节):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-01.jpg?sign=1739250762-0OjuuztzIpqRzI6mjauvaOlTOQN7Ybwy-0-2a04d48eac0553d01be8533caf088779)
局部变量
局部变量和常量的作用范围在当前的语句块中。在当前语句块或者嵌套的语句块中无法声明同名的局部变量:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-02.jpg?sign=1739250762-0s94QtaBnrTahJznOFsXCfYLDdHiXljc-0-8a1362df2e0357bc631a567dd0071669)
变量的作用范围是其所在的整个代码块(包括前向和后向)。这意味着虽然在变量或常量声明之前引用它是不合法的,但即使将示例中的x初始化移动到方法的末尾我们也会得到相同的错误,这个奇怪的规则和C++是不同的。
2.11.2 表达式语句
表达式语句既是表达式也是合法的语句。表达式语句必须改变状态或者执行某些可能改变状态的调用。状态改变本质上指改变一个变量的值。可能的表达式语句有:
· 赋值表达式(包括自增和自减表达式)
· (有返回值的和没有返回值的)方法调用表达式
· 对象实例化表达式
例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0095-04.jpg?sign=1739250762-8rFRSqPPLiexNLcWsN6feYFSGc76PWAP-0-a4a5ca627721fc8bc21d63f25d8cbabb)
即使调用的构造器或方法有返回值,也并不一定要使用该值。因此除非构造器或方法改变了某些状态,否则以下这些语句完全没有用处:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-01.jpg?sign=1739250762-zfRwRmWHezq8iSQ7FxaKsBEzfpdlzYiv-0-6a8ce73630eaa03a4a696438d81efcea)
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=1739250762-WNWf3FaFW1FKVdveK2n0azjt4RsOBX5G-0-5ba1e749033002941c5b90cb00ff1790)
if中的语句可以是代码块:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-03.jpg?sign=1739250762-SdVwCK231s3NeVRRqNxdmsChugT4pALt-0-7a28616ee45e310d87f087059c829593)
2.11.3.2 else子句
if语句之后可以紧跟else子句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-04.jpg?sign=1739250762-dbOJsVakDjMCA6LbILOLYaXRCipZvLd3-0-985599b0715c6cf0c746dbea73647864)
在else子句中,能嵌套另一个if语句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0096-05.jpg?sign=1739250762-3xTV2N8XyKIbefQz235oeKc0axwsBlsG-0-3c7118e067be8da54c507ff8fbe2a3d6)
2.11.3.3 用大括号改变执行流
else子句总是与它之前的语句块中紧邻的未配对的if语句结合:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-01.jpg?sign=1739250762-Mmfsn2HQhTmUcV7VJxxdtXgVUE5HAiVK-0-b5261ffb2efec5b9a6602c91709dc49f)
语义上等价于:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-02.jpg?sign=1739250762-0M4xVe0xDbZg8SulFCoHO3KaRmPoFhNF-0-264d51ce06fefd4e834999449e906080)
可以通过改变大括号的位置来改变执行流:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-03.jpg?sign=1739250762-GzwKZGpiEgvWxXULTSmukiKvKyOuAx96-0-08f0039fab2ee04f2a419589422a9a91)
大括号可以明确表明结构,这能提高嵌套if语句的可读性(虽然编译器并不需要)。需要特别指出的是下面的模式:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0097-04.jpg?sign=1739250762-l8AZjvoJz9xUNS3ACWM20u4RcjAVwNHW-0-ae5a98de6b21883ff80f822304087a8a)
这里,我们参照其他语言的“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=1739250762-i7Ni1hPJDAqdV7a9D9nhBbjKVwMLLRJO-0-0406f9ccbda7e0bce02dfd350c9891f7)
这个例子演示了最一般的情形,即针对常量的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=1739250762-FyokXKhY8vmwfLuLJZAcEldpXTcwufRB-0-c7a6a44bb32d5759187f3c908f9df33f)
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=1739250762-YRpu7C08Qt2wJkOKKS553uLopyphqey5-0-d91bc3abcc7c454e304711c57192872e)
(object类型允许其变量为任何类型,这部分内容将在3.2节和3.3节详细讨论。)
每一个case子句都指定了一种需要匹配的类型和一个变量(模式变量),如果类型匹配成功就对变量赋值。和常量不同,子句对可用的类型并没有进行任何限制。
when关键字可用于对case进行预测,例如:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-02.jpg?sign=1739250762-cmyhUPbCljIqDkpR798XHDq71mv14O6Z-0-a75266d35316bcc782158a4cdde49242)
case子句的顺序会影响类型的选择(这和选择常量的情况有些不同)。如果交换case的顺序,则上述示例可以得到完全不同的结果(事实上,上述程序甚至无法编译,因为编译器发现第二个case子句是永远不会执行的)。但default子句是一个例外,不论它出现在什么地方都会在最后才执行。
如果希望按照类型进行switch,但对其值却并不关心,这种情况下可以使用“丢弃”变量(_):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0099-03.jpg?sign=1739250762-l1CJIFeAuVARGsdBnnC0VMbZROnn4LHK-0-b6a566c9c8965ff30ff00272d76e408c)
堆叠多个case子句也是没有问题的。在下面的例子中,Console.WriteLine会在任何浮点类型的值大于1000时执行:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-01.jpg?sign=1739250762-Nyl3LmScn1Bu5UWik8fr8JIgbiZAeVLJ-0-f5d9bc4ddf11ca0aaa156c6d74f3ba72)
在上述例子中,编译器仅允许在when子句中使用模式变量f、d和m。当调用Console.WriteLine时,我们并不清楚到底三个模式变量中的哪一个会被赋值,因而编译器会将它们放在作用域之外。
除此以外,还可以混合使用常量选择和模式选择,甚至可以选择null值:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-02.jpg?sign=1739250762-wIHAHZEq5VNsxSo6V9bVgL1hOlUmaUzF-0-8996fb5f8bd85c98566d529418343aa2)
2.11.3.6 switch表达式
从C# 8开始,我们可以在表达式中使用switch。以下示例展示了该功能的使用方法,其中,假定变量cardNumber是int类型:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-03.jpg?sign=1739250762-39QnGGnqkKJQQjnrlLEFeke44q06n7U1-0-f76f9c3335b214018faa9c8189f98708)
注意,switch是在变量名称之后出现的,且其中的case子句相应地变为了以逗号结尾的表达式(而不再是语句)。switch表达式相比switch语句更加紧凑,且可以用于LINQ查询(请参见第8章)。
如果在switch表达式中忽略默认表达式(_)同时其他条件匹配失败,则会抛出一个异常。
switch表达式也支持多变量的选择(元组模式):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0100-04.jpg?sign=1739250762-2NebwGn1lWk8Ht8YhZXfzyIhCZDgxljp-0-87ef98f4ad38df3a18eb21e176f47490)
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=1739250762-XFkog6JDRyIbYVQOs8EFV3WPQdaKSQHB-0-1208bfd346676bc887f37abdefe8f2ab)
do-while循环在功能上不同于while循环的地方是前者在语句块执行之后才检查表达式的值(保证语句块至少执行过一次)。以下是用do-while循环重新书写上述例子:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-02.jpg?sign=1739250762-vbjq2bWb6yqOT38zggd9xfM7bYDUcwlG-0-ba2de96da0846d375971d19eb26c2b15)
2.11.4.2 for循环
for循环就像一个有特殊子句的while循环,这些特殊子句用于初始化和迭代循环变量。for循环有以下三个子句:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-03.jpg?sign=1739250762-eMI9kwOPQk3Pv5GV0bwRPE1vSLcuflrf-0-8059ba95a5de9f7123a7ad81d48307bd)
每一个子句的作用如下:
初始化子句
在循环之前执行,初始化一个或多个迭代变量。
条件子句
它是一个bool表达式,当取值为true时,将执行循环体。
迭代子句
在每次语句块迭代之后执行,通常用于更新迭代变量。
例如,下面的例子将输出0到2的数字:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0101-04.jpg?sign=1739250762-BWfcTFGuYXekoc8sL5II90XMJI0uwjKH-0-05b683edcc709fc01f278284424b2dcc)
下面的代码将输出前10个斐波那契数(每一个数都是前面两个数的和):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-01.jpg?sign=1739250762-RyUE3yx4Qhtr2fXDM6VvgoI0W5TZKbwo-0-de5bf5b16ba514c44d182fbcfea7623b)
for语句的这三个部分都可以省略,因而可以通过下面的代码来实现无限循环(也可以用while (true)来代替):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-02.jpg?sign=1739250762-il6tdcfTfyoIFHJJDvhs559IG4eIkacX-0-fbdb363b7f95f3e5c651c6245b38b37d)
2.11.4.3 foreach循环
foreach语句遍历可枚举对象的每一个元素,.NET中大多数表示集合或元素列表的类型都是可枚举的。例如,数组和字符串都是可枚举的。以下示例从头到尾枚举了字符串中的每一个字符:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-03.jpg?sign=1739250762-bBipHWpHuF9aVqkQapqTdpKTEwXaIOjB-0-dec4baeefaa6263a0599c519baeb0ba5)
以上程序的输出为:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0102-04.jpg?sign=1739250762-AQXOKuaokGwIu0uXB1ZN5kdUqXJwn8yp-0-ed0a237caf43967eab2bdf43d2ca420c)
我们将在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=1739250762-hmHekqjGjF1fjVmBisUiYCVozhMORCFV-0-1dee4ba72756b29de3d17137b98d7c66)
2.11.5.2 continue语句
continue语句放弃循环体中后续的语句,继续下一轮迭代。例如,以下的循环跳过了偶数:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-01.jpg?sign=1739250762-IGlbSZRq5eMMyHbRx69al6uQP5rIz27h-0-be6a6ff8425f770962f9516c83ec6822)
2.11.5.3 goto语句
goto语句将执行点转移到语句块中的指定标签处,格式如下:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-02.jpg?sign=1739250762-9R43ZTfxIOtvTSmEcgDQdSQaDa1yj4IP-0-1c875d333f291f1a0b5b77b7dc69dca7)
或用于switch语句内:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-03.jpg?sign=1739250762-B6KVpdmglKRLSk0GL12qpdgRA2vpcp8S-0-5dae24a229b151df42f88f2de5e8cd1b)
标签语句仅仅是代码块中的占位符,位于语句之前,用冒号后缀表示。下面的代码模拟for循环来遍历从1到5的数字:
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0103-04.jpg?sign=1739250762-x29IeKzyaRcY3i8qFAL6tDdqjzSreWK6-0-0388e1c9ea100c5e7bca837b789d8b22)
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=1739250762-x3HxmNy4Kr6DKUYwxBKwnb1aiVchjlT2-0-858cc24f7789bb36332c136a8349cecd)
return语句能够出现在方法的任意位置(除finally块中),并且可以多次出现。
2.11.5.5 throw语句
throw语句抛出异常来表示有错误发生(参见4.5节):
![](https://epubservercos.yuewen.com/77D764/31147986804769406/epubprivate/OEBPS/Images/0104-01.jpg?sign=1739250762-eBGdbFWMs92IRMEpH5n0C3YIeSHXAdYB-0-efca61282078dce5cb70b48010327a83)
2.11.6 其他语句
using语句用一种优雅的语法在finally块中调用实现了IDisposable接口对象的Dispose方法(请参见4.5节和12.1节)。
C#重载了using关键字,使它在不同上下文中有不同的含义。特别地,using指令和using语句是不同的。
lock语句是调用Mintor类型的Enter和Exit方法的简化写法(请参见第14章和第23章)。