8.1结束点和可到达性
在介绍各个语句之前先说一下结束点和可到达性的概念,因为每个语句都有一个结束点,就是紧跟在语句后面的那个位置,到达结束点时,控制就转移到该块中的下一个语句;执行流程可能到达的语句称为该语句是可到达的,反之是不可到达的。比如:
第二个Console.WriteLine语句就不能只能,goto语句直接跳过去了,所以该语句就是不可能到达的,其他的语句是可到达的。
为了确定某个特定的语句或结束点是否可到达,编译器根据为各语句定义的可到达性规则进行控制流分析,但控制流分析只考虑控制语句是常数表达式的值,至于非常数表达式被认为具有该类型的任何可能指。比如:
其中if的判断语句是常数表达式,因为i被设定为常数,所以Console.WriteLine被认为是不可到达的;若去了const,把i设定为局部变量int i=1,那么判断语句就是非常数表达式,Console.WriteLine就被认为是可到达的,即使它实际上永远不会被执行,虽然i已经被赋值是1,但流程分析会给出当i=2的情况(虽然我们不会做)。
函数成员的块被认为是始终可以到达的。
8.2块
块用于在只能使用单个语句的上下文中编写多个语句。
块中可以没有语句列表,没有时称块是空的;块也可以包含声明语句,声明的局部变量或常数的范围就是块本身。
如果块本身是可到达的,则块的语句列表也是可到达的;若果块是空的或语句列表的结束点是可到达的,那么块的结束点也是可到达的。
语句列表由一个或多个按顺序编写的语句组成的,可以出现在块和switch块中。
8.3空语句
空语句什么都不用做,在要求有语句的上下文中不执行任何操作时使用空语句,执行空语句就是将控制转移到该语句的结束点。
8.4标记语言
标记语言可以给语句加上一个标签作为前缀。标记语句可以出现在块中,但不允许作为嵌入语句。
标记语句声明了一个标签,标签由标识符命名,标签的范围是在其中声明了该标签的整个块,包括任何嵌套块;标签有自己的声明空间,不影响其他标识符。
8.5声明语句
声明语句声明局部变量和局部常数;声明语句可以出现在块中,但不允许它们作为嵌入语句使用。
8.5.1 局部变量声明
局部变量声明的类型由引入的变量类型决定,在使用局部变量的每个地方必须先赋值,后使用。
8.5.2 局部常数声明
局部常数声明的类型由引入的常数类型决定。
8.6表达式语句
表达式语句顾名思义就是用于计算所给定的表达式。
执行表达式语句就是对它所包含的表达式进行计算,然后将控制转移到该表达式语句的结束点。
8.7选择语句
8.7.1 if语句
else部分与语法允许的、词法上最相近的上一个if语句相关联。比如if() if() else; 相当于 if(){if() else};
if执行规则:首先计算布尔表达式;
如果结果是true,则控制转移到第一个嵌入语句,当控制到达语句的结束点时,控制转移到if语句的结束点;
如果结果时false,且存在else,则控制转移到第二个嵌入语句;若果不存在else,则直接转移到if语句的结束点。
8.7.2 switch语句
switch语句是在语句列表中选择一个符合要求的语句进行执行。
switch语句的主导类型由switch表达式建立,否则必须有且只有一个用户定义的从switch表达式的类型到下列某个可能的主导类型的隐式转化:sbyte、byte、short、ushort、int、uint、long、ulong、char、string或枚举类型;如果不存在这样的隐式转化,或存在多个这样的隐式转化,则会发生编译时错误。
每个case标签的常数表达式的值必须属于这种类型:它可以隐式转化为switch语句的主导类型。如果在一个switch语句中有两个或多个case标签指定同一个值,则会导致编译时错误。
一个switch语句有且只有一个default标签。
switch语句的执行规则:先计算switch表达式并将其转换为主导类型;
如果在该switch语句的case标签中,有一个指定的常数恰好等于switch表达式的值,控制将转移到该case标签后的语句列表;
若果没一个指定的常数与其匹配,且存在一个default标签,则将控制转移到default标签后的语句列表;如果没有default,则将控制转移到switch语句的结束点。
switch节的语句列表通常以break语句、goto case语句或goto default语句结束。
switch语句的主导类型可以是string类型,与字符串相等运算符一样,switch语句区分大小写;当主导类型是string时,允许null作为case标签常数。
8.8迭代语句
迭代语句就是重复执行嵌入语句。
8.8.1while语句
while语句:按不同条件执行一个嵌入语句零次或多次。
while语句的执行规则:先计算布尔表达式;
如果是true,控制转移到嵌入语句,运行到嵌入语句的结束点,将控制转移到while语句的开头;
如果是false,控制将转移到while语句的结束点。
在while语句的嵌入语句内,break语句可以将控制转移到while语句的结束点;而continue语句是将控制转移到嵌入语句的结束点。
8.8.2do语句
do语句:按不同条件执行一个嵌入语句一次或多次。
do语句的执行规则:首先将控制转移到嵌入语句;
当控制到达嵌入语句的结束顶啊,计算布尔表达式,若是true,将控制转移到do语句开头;否则转移到do语句的结束点。
在do语句的嵌入语句内,break语句可以将控制转移到do语句的结束点;而do语句是将控制转移到嵌入语句的结束点。
8.8.3for语句
for语句计算一个初始化表达式序列,然后当某个条件为真时,重复执行相关的嵌套语句并计算一个迭代表达式序列。
for语句的执行规则:如果存在for初始值设定项,则按变量初始值设定项或语句表达式的编写顺序执行,此步骤只执行一次;
如果存在for条件,则计算它;
如果不存在for条件或计算产生true,控制将转移到嵌入语句,结束时,按顺序计算for迭代程序的表达式,重复上述步骤;
如果存在for,且计算产生false,控制将转移到for语句的结束点。
8.8.4 foreach语句
foreach语句用于枚举一个集合的元素,并对该集合中的每个元素执行一次相关的嵌入语句。
foreach语句的类型和标识符声明该语句的迭代变量,迭代变量相当于一个其范围覆盖整个嵌入语句的只读局部变量。在foreach语句执行期间,迭代变量表示当前正在为其执行迭代的集合元素。如果嵌入语句试图修改迭代变量或将迭代变量作为ref或out参数传递,将会发生编译时错误。
foreach语句的表达式的类型必须是集合类型,且必须有一个从该集合的元素类型到迭代变量的类型的显式转换。如果该表达式具有null值,则将引发System.NullReferenceException。
类型集合:如果类型C实现了System.Collections.IEnumerable接口,或满足下列条件,就称它是集合类型:
C包含一个public实例方法,它带有签名GetEnumerator(),且返回值属于结构类型、类类型或接口类型(下面用E表示该返回值的类型);
E包含一个public实例方法,此方法具有签名MoveNext()和返回类型bool;
E包含一个 名为Current的public实例属性,此属性允许读取当前值。此属性的类型称为该集合类型的元素类型。
注:一个实现IEnumerable的类型也是集合类型,即使不满足上述条件(这是可能的,如果该类型通过"显式接口成员实现"的方式实现某些IEnumerable成员)
System.Array类型是集合类型,而由于所有数组类型都是它的派生类,所以foreach语句中允许使用任何数组类型表达式。
8.9跳转语句
跳转语句用于无条件转移控制。
跳转语句会将控制转移到某个位置,这个位置称为跳转语句的目标。跳转语句可以从块内跳转到块外,但不能跳转到一个块的内部。当跳转涉及try语句时就变得复杂了,如果跳转语句欲退出的是一个或多个具有相关联的finally块的try块,则控制最初转移到最里层的try语句的finally块,运行完就转移到下一个封闭的try语句中的finally块,不断重复,直到执行完所有涉及try语句的finally块。
8.9.1 break语句
break语句退出直接封闭它的switch,while,do,for或foreach语句;如果封闭的不是这些语句,则会发生编译时错误。
当多个switch,while,do,for或foreach语句彼此嵌套时,break语句只应用于最里层的语句,若要穿越多个嵌套直接转移控制,必须用goto语句。但break语句不能退出finally块。
由于break语句无条件地将控制转移,所以永远都无法到达break语句的结束点。
8.9.2 continue语句
continue语句的目标是直接封闭着它的while,do,for或foreach语句的嵌套语句的结束点;若continue语句不在这些语句中封闭,则发生编译时错误。
同样有多个while,do,for或foreach语句互相嵌套时,continue语句只应用于最里层的那个语句。要穿越多个嵌套也必须使用goto语句。continue同样不能退出finally块。
8.9.3 goto语句
goto语句将控制转移到由标签标记的语句。
goto语句也不能退出finally块。
8.9.4 return语句
return语句将控制返回到出现return语句的函数成员的调用方。
不带表达式的return语句只能用在返回类型为void的方法、属性或索引器的set访问器、事件的add和remove访问器、实例构造函数、静态构造函数或析构函数中;带表达式的return语句只能用在非void的方法、属性或索引器的get访问器或用户定义的运算符。return语句不饿能出现在finally块中。
由于return语句无条件地将控制转移到别处,所以永远无法到达return语句的结束点。
8.9.5 throw语句
throw语句抛出一个异常。
带表达式的throw语句抛出通过计算该表达式而产生的值。该值必须属于类类型System.Exception或从System.Exception派生的类类型。如果表达式的计算产生null,则抛出System.NullReferenceException。
不带表达式的throw语句只能用在catch块中,这种情况,该语句重新抛出当前正由该catch块处理的那个异常。
同样,永远无法到达throw语句的结束点。
抛出一个异常时,控制转移到封闭着它的try语句中能够处理该异常的第一个catch子句。从抛出一个异常开始直至将控制转移到关于该异常的一个合适的异常处理过程为止,这个过程称为异常传播。
8.10try语句
try语句提供一种机制,用于捕获在块的执行期间发生的各种异常。此外,try语句还能让你指定一个代码块,并保证当控制离开try语句时,先执行该代码。
有三种可能的try语句形式:一个try块后跟一个或多个catch块;
一个try块后跟一个finally块;
一个try块后跟一个或多个catch块,后面再跟一个finally块。
当catch子句指定类类型时,此类型必须为System.Exception或从System.Exception派生的类型。
当catch子句同时指定类类型和标识符时,相当于声明了一个具有给定名称和类型的异常变量,此异常变量相当于一个范围覆盖整个catch块的局部变量。再catch块的执行期间,此异常变量表示当前正在处理的异常。出于明确赋值检查的目的,此异常变量被认为在它的整个范围内是明确赋值的。
除非catch子句包含一个异常变量名,否则在该catch块中不可能访问当前发生的异常对象。
即不指定异常类型,也不指定异常变量名的catch子句称为常规catch子句,一个try语句只能有一个常规catch子句,而且它必须是最后一个catch子句。
在catch块内,不具有表达式的throw语句可用于重新抛出由catch块捕获的异常。对异常变量的赋值不会改变上述被重新抛出的异常。
8.11checked语句和unchecked语句
checked和unchecked语句用于控制整型算术运算和转换的溢出检查上下文。
check语句导致它指定的块中的所有表达式都在一个选中的上下文中进行计算,而unchecked语句导致它们在一个未选中的上下文中进行计算。
check和unchecked语句完全等效于check和unchecked运算符,不同的只是它们作用于块,而不是表达式。
8.12lock语句
lock语句用于获取某个给定对象的互斥锁,执行一个语句,然后释放该锁。
lock语句的表达式必须表示一个引用类型的值。永远不会为lock语句中的表达式执行隐式装箱转换。因此如果该表达式表示的是一个值类型的值,则会导致编译时错误。
8.13using语句
using语句获取一个或多个资源,执行一个语句,然后处置该资源。
资源是实现了System.IDisposable的类或结构,它只包含一个名为Dispose的不带参数的方法。正在使用资源的代码可以调用Dispose以表明不再需要该资源,如果不调用Dispose,最终将由垃圾回收而对该资源进行自动处置。
如果资源获取的形式是局部变量声明,那么此局部变量声明的类型必须为System.IDisposable或可以隐式转化为System.IDisposable的类型;如果获取形式是表达式,那么此表达式必须是System.IDisposable类型或可以隐式转换为System.IDisposable的类型。
在资源获取中声明的局部变量是只读的,并且必须包含一个初始值设定项,如过试图修改这些局部变量或作为ref或out参数传递,会发生编译时错误。
using语句被翻译分成三个部分:获取、使用、处置。资源的使用部分被隐式封闭在一个含有finally子句的try语句中,此finally子句用于处置资源。如果所获取资源是null,则不会对Dispose进行调用,也不会引发任何异常。