13 个语句
13.1 常规
C# 提供了各种语句。
注意:大多数在 C 和 C++ 编程的开发人员都熟悉这些陈述。 end note
statement
: labeled_statement
| declaration_statement
| embedded_statement
;
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| unsafe_statement // unsafe code support
| fixed_statement // unsafe code support
;
unsafe_statement(§23.2)和fixed_statement(§23.7)仅在不安全的代码(§23)中可用。
embedded_statement非确定性用于在其他语句中显示的语句。 使用 embedded_statement 而不是 语句 不包括在这些上下文中使用声明语句和标记语句。
示例:代码
void F(bool b) { if (b) int i = 44; }
导致编译时错误,因为
if
语句需要embedded_statement而不是其if
分支的语句。 如果允许此代码,则会声明变量,但永远不能使用该变量i
。 但是,请注意,通过将's 声明放在i
块中,该示例有效。end 示例
13.2 终结点和可访问性
每个语句都有一个 终点。 在直观的术语中,语句的终点是紧跟语句的位置。 复合语句(包含嵌入语句的语句)的执行规则指定在控件到达嵌入语句的终点时执行的操作。
示例:当控件到达块中的语句的终点时,控件将传输到块中的下一个语句。 end 示例
如果可以通过执行来达到语句,则表示该语句是可访问的。 相反,如果不可能执行语句,则声明据说是 无法访问的。
示例:在以下代码中
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }
Console.WriteLine 的第二次调用是无法访问的,因为不可能执行该语句。
end 示例
如果throw_statement、阻止或empty_statement以外的语句无法访问,则会报告警告。 它特别不是无法访问语句的错误。
注意:若要确定特定语句或终结点是可访问的,编译器将根据为每个语句定义的可访问性规则执行流分析。 流分析将考虑控制语句行为的常量表达式(§12.23)的值,但不考虑非常量表达式的可能值。 换句话说,出于控制流分析的目的,给定类型的非常量表达式被视为具有该类型的任何可能值。
在示例中
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
被认为是可访问的,尽管实际上,它永远不会执行。end note
函数 成员或匿名函数的块 始终被视为可访问。 通过连续评估块中每个语句的可访问性规则,可以确定任何给定语句的可访问性。
示例:在以下代码中
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }
第二
Console.WriteLine
个值的可访问性确定如下:
- 第一个
Console.WriteLine
表达式语句是可访问的,因为方法的块是可访问的F
(§13.3)。- 第一个
Console.WriteLine
表达式语句的终点是可访问的,因为该语句是可访问的(§13.7 和 §13.3)。- 该
if
语句是可访问的,因为第一个Console.WriteLine
表达式语句的终点是可访问的(§13.7 和 §13.3)。- 第二
Console.WriteLine
个表达式语句是可访问的,因为语句的if
布尔表达式没有常量值false
。end 示例
在以下两种情况下,它是可访问语句的终点的编译时错误:
switch
由于该语句不允许 switch 节“进入下一个 switch 节”,因此对于可访问 switch 节的语句列表的终点,这是一个编译时错误。 如果发生此错误,则通常指示break
缺少语句。它是函数成员块的终点或计算可访问值的匿名函数的编译时错误。 如果发生此错误,则通常指示
return
缺少语句(§13.10.5)。
13.3 块
13.3.1 常规
使用代码块,可以在允许编写一个语句的上下文中编写多个语句。
block
: '{' statement_list? '}'
;
块由一个可选的statement_list(§13.3.2)组成,括在大括号中。 如果省略语句列表,则表示该块为空。
块可能包含声明语句(§13.6)。 块中声明的局部变量或常量的范围是块。
执行块,如下所示:
- 如果块为空,则控制将传输到块的终点。
- 如果块不为空,则控制权将传输到语句列表。 当控件到达语句列表的终点时,控件将传输到块的终点。
如果块本身可访问,则可访问块的语句列表。
如果块为空,或者可访问语句列表的终结点,则可访问块的终点。
包含一个或多个yield
语句(§13.15)的块称为迭代器块。 迭代器块用于将函数成员实现为迭代器(§15.14)。 一些其他限制适用于迭代器块:
- 这是一个
return
编译时错误,语句出现在迭代器块中(但yield return
允许使用语句)。 - 它是迭代器块包含不安全上下文(§23.2)的编译时错误。 迭代器块始终定义安全上下文,即使其声明嵌套在不安全的上下文中也是如此。
13.3.2 语句列表
语句列表由一个或多个按顺序编写的语句组成。 语句列表出现在块 s (§13.3) 和 switch_blocks (§13.8.3) 中。
statement_list
: statement+
;
语句列表是通过将控件传输到第一个语句来执行的。 当控件到达语句的终点时,控件将传输到下一个语句。 当控件到达最后一个语句的终点时,控件将传输到语句列表的终点。
如果至少有一个语句为 true,则语句列表中的语句是可访问的:
- 该语句是第一个语句,语句列表本身是可访问的。
- 上述语句的终点是可访问的。
- 该语句是一个标记语句,标签由可访问
goto
语句引用。
如果可访问列表中最后一个语句的终点,则语句列表的终点是可访问的。
13.4 空语句
empty_statement不执行任何操作。
empty_statement
: ';'
;
当在需要语句的上下文中没有要执行的操作时,将使用空语句。
执行空语句只会将控制权传输到语句的终点。 因此,如果可访问空语句,则可访问空语句的终点。
示例:使用 null 正文编写
while
语句时,可以使用空语句:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }
此外,空语句可用于在块的结束“”
}
之前声明标签:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }
end 示例
13.5 标记语句
labeled_statement允许以标签为前缀的语句。 标记语句在块中允许,但不允许作为嵌入语句。
labeled_statement
: identifier ':' statement
;
带标签的语句使用标识符提供的名称声明标签。 标签的范围是声明标签的整个块,包括任何嵌套块。 对于同名具有相同名称的两个标签,具有重叠的范围,这是编译时错误。
可以在标签范围内从 goto
语句(§13.10.4)引用标签。
注意:这意味着
goto
语句可以在块和块外传输控制,但永远不会传输到块中。 end note
标签具有自己的声明空间,不会干扰其他标识符。
示例:示例
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }
有效,使用名称 x 作为参数和标签。
end 示例
标记语句的执行与标签后面的语句的执行完全对应。
除了正常控制流提供的可访问性外,如果标签是由可访问goto
语句引用的,则标记语句是可访问的,除非goto
该语句位于块内try
或catch
包含其终结点不可访问的 try_statementfinally
块的块,并且标记的语句位于try_statement之外。
13.6 声明语句
13.6.1 常规
declaration_statement声明一个或多个局部变量、一个或多个局部常量或局部函数。 声明语句在块和开关块中允许,但不允许作为嵌入语句。
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
局部变量是使用 local_variable_declaration (§13.6.2)声明的。 使用local_constant_declaration(§13.6.3)声明本地常量。 使用 local_function_declaration(§13.6.4)声明本地函数。
声明的名称将引入最近的封闭声明空间(§7.3)。
13.6.2 局部变量声明
13.6.2.1 常规
local_variable_declaration声明一个或多个局部变量。
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
隐式类型声明包含上下文关键字(§6.4.4) var
导致三个类别之间的语法歧义性,如下所示:
- 如果没有在范围内命名
var
的类型,并且输入与implicitly_typed_local_variable_declaration匹配,则选择它; - 否则,如果命名
var
的类型在作用域中,则 implicitly_typed_local_variable_declaration 不被视为可能的匹配项。
在local_variable_declaration中,每个变量由声明符引入,声明符是隐式类型化、显式类型和引用局部变量的implicitly_typed_local_variable_declarator、explicitly_typed_local_variable_declarator或ref_local_variable_declarator之一。 声明符定义引入变量的名称(标识符)和初始值(如果有)。
如果声明中有多个声明符,则会处理这些声明符,包括任何初始化表达式(从左到右(§9.4.4.5)。
注意:对于 local_variable_declaration 不作为 for_initializer (§13.9.4)或 resource_acquisition (§13.14)发生,此从左到右的顺序相当于每个声明符位于单独的 local_variable_declaration内。 例如:
void F() { int x = 1, y, z = x * 2; }
等效于:
void F() { int x = 1; int y; int z = x * 2; }
end note
局部变量的值是在表达式中使用 simple_name (§12.8.4) 获取的。 在获取其值的每个位置,应明确分配局部变量(§9.4)。 local_variable_declaration引入的每个局部变量最初都是未分配的(§9.4.3)。 如果声明符具有初始化表达式,则引入的局部变量被分类为 在声明符末尾分配 (§9.4.4.4.5)。
local_variable_declaration引入的局部变量的范围定义如下(§7.7):
- 如果声明作为 for_initializer 发生,则范围是 for_initializer、 for_condition、 for_iterator和 embedded_statement (§13.9.4):
- 如果声明作为 resource_acquisition 发生,则范围是语义上等效扩展 using_statement 的最外层块(§13.14):
- 否则,范围是声明所在的块。
在声明符前面的文本位置或在其声明符内的任何初始化表达式中,按名称引用局部变量是错误的。 在本地变量的作用域内,声明另一个具有相同名称的局部变量、局部函数或常量是编译时错误。
ref 局部变量的 ref-safe-context (§9.7.2) 是其初始化 variable_reference的 ref-safe-context。 非 ref 局部变量的 ref-safe-context 是 声明块。
13.6.2.2 隐式类型局部变量声明
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
implicitly_typed_local_variable_declaration引入了单个局部变量标识符。 表达式或variable_reference应具有编译时类型T
。 第一个替代项声明具有表达式初始值的变量;其类型为T
T?
不可为 null 的引用类型,否则其类型为 T
。 第二种替代方法声明一个 ref 变量,其初始值为 ref
variable_reference;其类型是ref T?
当为不可为 null 的引用类型时T
,否则其类型为 ref T
。 (ref_kind在 §15.6.1 中介绍。
示例:
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;
上述隐式类型局部变量声明与以下显式类型声明完全等效:
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;
下面是不正确的隐式类型局部变量声明:
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itself
end 示例
13.6.2.3 显式键入的局部变量声明
explicitly_typed_local_variable_declaration
: type explicitly_typed_local_variable_declarators
;
explicitly_typed_local_variable_declarators
: explicitly_typed_local_variable_declarator
(',' explicitly_typed_local_variable_declarator)*
;
explicitly_typed_local_variable_declarator
: identifier ('=' local_variable_initializer)?
;
local_variable_initializer
: expression
| array_initializer
;
explicity_typed_local_variable_declaration引入了一个或多个具有指定类型的局部变量。
如果存在local_variable_initializer,则其类型应根据简单赋值(§12.21.2)或数组初始化规则(§17.7)适当,并且其值被分配为变量的初始值。
13.6.2.4 显式键入的 ref 局部变量声明
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
初始化variable_reference的类型应与 ref 赋值的要求相同(§12.21.3)。
如果ref_kindref readonly
,则所声明的标识符是对被视为只读的变量的引用。 否则,如果ref_kind,则声明的ref
标识符是对应可写的变量的引用。
在用 method_modifier async
声明的方法或迭代器(§15.14)内声明的引用局部变量或类型的变量ref struct
是编译时错误。
13.6.3 本地常量声明
local_constant_declaration声明一个或多个本地常量。
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
local_constant_declaration的类型指定声明引入的常量的类型。 该类型后跟constant_declarator列表,每个列表都会引入一个新常量。 constant_declarator包含一个标识符,该标识符命名常量,后跟一个“=
”标记,后跟一个提供常量值的constant_expression(§12.23)。
局部常量声明的类型和constant_expression应遵循与常量成员声明(§15.4)相同的规则。
使用 simple_name (§12.8.4) 在表达式中获取本地常量的值。
本地常量的范围是声明所在的块。 引用位于其constant_declarator末尾的文本位置中的局部常量是错误的。
声明多个常量的本地常量声明等效于具有相同类型的单个常量的多个声明。
13.6.4 本地函数声明
local_function_declaration声明本地函数。
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
语法说明:识别local_function_body如果null_conditional_invocation_expression和表达式替代项都适用,则应选择前者。 (§15.6.1)
示例:本地函数有两个常见用例:迭代器方法和异步方法。 在迭代器方法中,只有在调用枚举返回的序列的代码时才会观察到任何异常。 在异步方法中,只有在等待返回的任务时,才会观察到任何异常。 以下示例演示如何使用本地函数将参数验证与迭代器实现分离:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }
end 示例
除非另有指定,否则所有语法元素的语义与 method_declaration (§15.6.1)的语义相同,而是在本地函数的上下文中读取,而不是方法。
local_function_declaration的标识符在其声明的块范围中应是唯一的,包括任何封闭的局部变量声明空间。 其一个后果是不允许重 载local_function_declaration。
local_function_declaration可能包括一个 async
(§15.15) 修饰符和一个 unsafe
(§23.1) 修饰符。 如果声明包含 async
修饰符,则返回类型应 void
为或类型 «TaskType»
(§15.15.1)。 如果声明包含static
修饰符,则函数是静态本地函数;否则为非静态本地函数。 这是type_parameter_list或parameter_list包含属性的编译时错误。 如果在不安全的上下文(§23.2)中声明了本地函数,则即使本地函数声明不包含 unsafe
修饰符,本地函数也可能包含不安全的代码。
本地函数在块范围内声明。 非静态局部函数可以从封闭范围捕获变量,而静态局部函数不应捕获变量(因此它无权将局部变量、参数、非静态本地函数或 this
)。 如果捕获的变量由非静态本地函数的主体读取,但在每次调用函数之前未明确分配,则这是编译时错误。 编译器应确定在返回时明确分配的变量(§9.4.4.33)。
当类型为结构类型 this
时,它是本地函数正文访问 this
的编译时错误。 无论访问是显式(如 in this.x
)还是隐式(如 x
结构实例成员所在的位置 x
)都是如此。 此规则仅禁止此类访问,并且不会影响成员查找是否导致结构的成员。
对于本地函数的正文,它是一个编译时错误,它包含一个 goto
语句、一个 break
语句或一个 continue
目标在局部函数正文之外。
注意:在 §12.19.3 中,上述匿名函数的规则
this
和goto
镜像规则。 end note
可以在声明之前从词法点调用局部函数。 但是,在局部函数(§7.7)中使用的变量声明之前,对函数进行词法声明是编译时错误。
本地函数声明参数、类型参数或局部变量的名称与任何封闭局部变量声明空间中声明的名称相同,这是本地函数的编译时错误。
本地函数体始终可访问。 如果可访问本地函数声明的起点,则可访问本地函数声明的终结点。
示例:在以下示例中,即使无法访问起始点,
L
也可以访问正文L
。 由于无法访问起点L
,因此无法访问终结点L
后面的语句:class C { int M() { L(); return 1; // Beginning of L is not reachable int L() { // The body of L is reachable return 2; } // Not reachable, because beginning point of L is not reachable return 3; } }
换句话说,本地函数声明的位置不会影响包含函数中任何语句的可访问性。 end 示例
如果局部函数的参数类型为 dynamic
,则调用的函数应在编译时解析,而不是运行时。
不应在表达式树中使用本地函数。
静态本地函数
- 可以从封闭范围引用静态成员、类型参数、常量定义和静态本地函数。
- 不应从隐式
this
引用或局部变量、参数或非静态本地函数中引用this
或base
实例成员。 但是,表达式中nameof()
允许所有这些内容。
13.7 表达式语句
expression_statement计算给定表达式。 表达式计算的值(如果有)将被丢弃。
expression_statement
: statement_expression ';'
;
statement_expression
: null_conditional_invocation_expression
| invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
;
并非所有表达式都允许作为语句。
注意:具体而言,仅计算一个值(将被丢弃)的表达式
x + y
x == 1
不允许作为语句。 end note
执行expression_statement计算包含的表达式,然后将控制权传输到expression_statement的终点。 如果可访问expression_statement,则可访问expression_statement的终点。
13.8 Selection 语句
13.8.1 常规
选择语句根据某些表达式的值选择多个可能的语句之一来执行。
selection_statement
: if_statement
| switch_statement
;
13.8.2 if 语句
该 if
语句根据布尔表达式的值选择要执行的语句。
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
部件 else
与语法允许的前面 if
最接近的词法关联。
示例:因此,
if
窗体的语句if (x) if (y) F(); else G();
等效于
if (x) { if (y) { F(); } else { G(); } }
end 示例
语句 if
按如下方式执行:
- 评估 boolean_expression (§12.24)。
- 如果布尔表达式生成
true
,控件将传输到第一个嵌入语句。 当控件到达该语句的终点时,控件将传输到语句的if
终点。 - 如果布尔表达式生成
false
并且某个else
部分存在,则控件将传输到第二个嵌入语句。 当控件到达该语句的终点时,控件将传输到语句的if
终点。 - 如果布尔表达式生成
false
且部件else
不存在,则控件将传输到语句的if
终点。
如果if
语句可访问且布尔表达式没有常量值false
,则语句的第一个if
嵌入语句是可访问的。
如果语句可访问且布尔表达式没有常量值true
,则语句的第二个if
嵌入语句(如果if
存在)是可访问的。
如果可访问至少一个嵌入语句的终点,则语句的 if
终点是可访问的。 此外,如果if
语句可访问且布尔表达式没有常量值true
,则没有else
部分的语句的终点if
是可访问的。
13.8.3 Switch 语句
该 switch
语句选择用于执行具有与 switch 表达式值对应的关联开关标签的语句列表。
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
switch_statement由关键字switch
组成,后跟带括号表达式(称为 switch 表达式),后跟switch_block。 switch_block由零个或多个switch_sections 组成,括在大括号中。 每个 switch_section 由一个或多个 switch_label组成,后跟 statement_list (§13.3.2)。 每个包含case
switch_label都有一个关联的模式(§11),测试 switch 表达式的值。 如果 存在case_guard ,则其表达式应隐式转换为该类型 bool
,并将该表达式计算为满足该情况的附加条件。
语句的switch
调控类型由 switch 表达式建立。
- 如果 switch 表达式的类型为
sbyte
、byte
、、short
ushort
、int
、uint
、long
ulong
bool
string
char
或enum_type,或者它是对应于其中一种类型的可为 null 值类型,则为语句的switch
调控类型。 - 否则,如果恰好存在一个用户定义的隐式转换,即从 switch 表达式的类型转换为下列可能的治理类型之一:
sbyte
、int
uint
ushort
ulong
long
byte
short
、、、char
或string
与其中一种类型对应的可为 null 的值类型,则转换的类型是语句的switch
调控类型。 - 否则,语句的
switch
治理类型是 switch 表达式的类型。 如果不存在此类类型,则为错误。
语句中最多可以有一个default
switch
标签。
如果任何开关标签的模式不适用于输入表达式的类型(§11.2.1),则这是一个错误。
如果任何开关标签的模式被 (§11.3) 子化(§11.3)的开关语句的早期开关标签的模式集,则为错误,这些模式没有事例防护,或者其大小写防护是值为 true 的常量表达式。
示例:
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }
end 示例
语句 switch
执行如下:
- 将计算开关表达式并将其转换为控制类型。
- 根据转换的开关表达式的值传输控制:
- 与 switch 表达式的值匹配的同
switch
一语句中标签集中case
的词法第一个模式,并且保护表达式不存在或计算结果为 true,会导致控件在匹配case
的标签之后传输到语句列表中。 - 否则,如果存在
default
标签,控件将传输到标签后面的default
语句列表。 - 否则,控制权将传输到语句的
switch
终点。
- 与 switch 表达式的值匹配的同
注意:未定义在运行时匹配模式的顺序。 允许编译器(但不需要)按顺序匹配模式,并重复使用已匹配模式的结果来计算其他模式匹配的结果。 不过,编译器需要确定与表达式匹配的词法第一个模式,并且 guard 子句不存在或计算结果为
true
。 end note
如果可访问 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; }
end 示例
switch_section中允许使用多个标签。
示例:示例
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }
有效。 该示例不违反“无倒退”规则,因为标签
case 2:
属于default:
同 一switch_section。end 示例
注意:“不失败”规则可防止在意外省略语句时
break
在 C 和 C++ 中发生的常见 bug 类。 例如,可以反转上述语句的各个switch
部分,而不会影响语句的行为:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }
end note
注意:开关节的语句列表通常以
break
语句goto case
或goto default
语句结尾,但允许呈现语句列表的终点的任何构造不可访问。 例如,while
已知由布尔表达式true
控制的语句永远不会到达其终点。 同样,或throw
return
语句始终将控制权转移到其他位置,并且永远不会到达其终点。 因此,以下示例有效:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }
end note
示例:语句的
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; } }
end 示例
注意:与字符串相等运算符(§12.12.8)一样,该
switch
语句区分大小写,并且仅在 switch 表达式字符串与标签常量完全匹配case
时才执行给定的 switch 节。 end note When the governing type of aswitch
statement isstring
or a nullable value type, the valuenull
is permitted as acase
label constant.
switch_block的statement_list可能包含声明语句(§13.6)。 在交换机块中声明的局部变量或常量的范围是开关块。
如果以下至少一个为 true,则可访问开关标签:
- switch 表达式是常量值,任一
- 标签是一个
case
模式,其模式 将匹配 该值(§11.2.1),并且标签的防护要么不存在,要么不是值为 false 的常量表达式;或者 - 它是一个标签,并且没有 switch 节包含一个
default
大小写标签,其模式与该值匹配,并且其保护不存在或具有值 true 的常量表达式。
- 标签是一个
- switch 表达式不是常量值,也可以
- 标签是一个
case
没有防护或具有一个防护,其值不是常量 false;或者 - 它是一个
default
标签和- 开关语句中出现的一组模式,这些模式没有防护或具有值为常量 true 的防护,对于开关控制类型来说并不 详尽 (§11.4);或者
- 开关控制类型是可为 null 的类型,并且开关语句中出现的模式集没有防护或具有值为常量 true 的防护,不包含与值
null
匹配的模式。
- 标签是一个
- 开关标签由可访问
goto case
或goto default
语句引用。
如果 switch
语句可访问,并且 switch 节包含可访问的开关标签,则给定 switch 节的语句列表是可访问的。
如果 switch 语句可访问,并且至少有一个 switch
为 true,则语句的终点是可访问的:
- 该
switch
语句包含退出该语句的可访问break
语句switch
。 - 不存在
default
任何标签,或者- switch 表达式是一个非常量值,在开关语句中出现的模式集,这些模式没有保护或具有值为常量 true 的防护,对于开关控制类型来说并不 详尽 (§11.4)。
- switch 表达式是一个可为 null 类型的非常量值,在不具有 guard 或具有值为 true 的 guard 的 switch 语句中不出现模式。该值为 true 的常量与值
null
匹配。 - switch 表达式是一个常量值,没有
case
guard 或其 guard 为 constant true 的标签将匹配该值。
示例:以下代码显示了子句的
when
简洁用法:static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }
var 大小写匹配
null
、空字符串或任何仅包含空格的字符串。 end 示例
13.9 迭代语句
13.9.1 常规
迭代语句重复执行嵌入语句。
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 while 语句
该 while
语句有条件地执行嵌入语句零次或多次。
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
语句 while
执行如下:
- 评估 boolean_expression (§12.24)。
- 如果布尔表达式生成
true
,则控件将传输到嵌入语句。 当控件到达嵌入语句的终点(可能来自语句的执行continue
)时,控件将传输到语句的while
开头。 - 如果布尔表达式生成
false
,则控件将传输到语句的while
终点。
在语句的 while
嵌入语句中, break
语句(§13.10.2)可用于将控制权转移到语句的 while
终点(从而结束嵌入语句的迭代),而 continue
语句(§13.10.3)可用于将控件传输到嵌入语句的终点(从而执行语句的另 while
一次迭代)。
如果while
语句可访问且布尔表达式没有常量值false
,则语句的while
嵌入语句是可访问的。
如果至少有一个 while
语句为 true,则语句的终点是可访问的:
- 该
while
语句包含退出该语句的可访问break
语句while
。 - 该
while
语句是可访问的,布尔表达式没有常量值true
。
13.9.3 do 语句
该 do
语句有条件地执行嵌入语句一次或多次。
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
语句 do
执行如下:
- 控制权将传输到嵌入式语句。
- 当控件到达嵌入语句的终点(可能来自语句的执行
continue
)时,将 评估boolean_expression (§12.24)。 如果布尔表达式生成true
,则控制权将传输到语句的do
开头。 否则,控制权将传输到语句的do
终点。
在语句的 do
嵌入语句中, break
语句(§13.10.2)可用于将控制权转移到语句的 do
终点(从而结束嵌入语句的迭代),而 continue
语句(§13.10.3)可用于将控件传输到嵌入语句的终点(从而执行语句的另 do
一次迭代)。
如果do
可访问语句,则可访问语句的do
嵌入语句。
如果至少有一个 do
语句为 true,则语句的终点是可访问的:
- 该
do
语句包含退出该语句的可访问break
语句do
。 - 嵌入语句的终点是可访问的,布尔表达式没有常量值
true
。
13.9.4 for 语句
该 for
语句计算初始化表达式序列,然后,当条件为 true 时,重复执行嵌入语句并计算迭代表达式序列。
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
如果存在,则for_initializer由local_variable_declaration(§13.6.2)或用逗号分隔的 statement_expressions (§13.7) 列表组成。 由for_initializer声明的局部变量的范围是for_initializer、for_condition、for_iterator和embedded_statement。
如果存在,则for_condition应为boolean_expression(§12.24)。
如果存在,则for_iterator由逗号分隔的 statement_expressions (§13.7) 的列表组成。
语句 for
执行如下:
- 如果存在for_initializer,则按写入顺序执行变量初始值设定项或语句表达式。 此步骤仅执行一次。
- 如果存在for_condition,则会对其进行评估。
- 如果for_condition不存在或计算生成
true
,则控制权将传输到嵌入语句。 当和如果控制到达嵌入语句的终点(可能从执行continue
语句),则for_iterator的表达式(如果有)按顺序计算,然后执行另一次迭代,从上述步骤中的for_condition计算开始。 - 如果for_condition存在并且计算生成
false
,则控制权将传输到语句的for
终点。
在语句的 for
嵌入语句中, break
语句(§13.10.2)可用于将控制权转移到语句的终点(因此嵌入语句的 for
结束迭代),而 continue
语句(§13.10.3)可用于将控件传输到嵌入语句的终点(因此执行 for_iterator 并执行语句的另 for
一次迭代), 从 for_condition开始)。
如果以下任一 for
语句为 true,则可访问语句的嵌入语句:
- 该
for
语句是可访问的,不存在 任何for_condition 。 - 该
for
语句可访问且 存在for_condition ,并且没有常量值false
。
如果至少有一个 for
语句为 true,则语句的终点是可访问的:
- 该
for
语句包含退出该语句的可访问break
语句for
。 - 该
for
语句可访问且 存在for_condition ,并且没有常量值true
。
13.9.5 foreach 语句
该 foreach
语句枚举集合的元素,为集合的每个元素执行嵌入语句。
foreach_statement
: 'foreach' '(' ref_kind? local_variable_type identifier 'in'
expression ')' embedded_statement
;
foreach 语句的local_variable_type和标识符声明语句的迭代变量。 var
如果标识符作为local_variable_type提供,并且没有命名var
的类型在范围内,则迭代变量据说是隐式类型的迭代变量,其类型将被视为语句的foreach
元素类型,如下所示。
如果foreach_statement同时包含这两者ref
,readonly
则迭代变量表示被视为只读的变量。 否则,如果foreach_statement不包含ref
readonly
,则迭代变量表示应可写的变量。
迭代变量对应于一个局部变量,该局部变量的范围扩展了嵌入语句。 在执行 foreach
语句期间,迭代变量表示当前正在执行迭代的集合元素。 如果迭代变量表示只读变量,则当嵌入语句尝试修改它(通过赋值或运算符--
)或将其作为引用或++
输出参数传递时,会发生编译时错误。
在下面的命名空间中,为简洁起见, IEnumerable
IEnumerable<T>
IEnumerator
并IEnumerator<T>
引用命名空间System.Collections
System.Collections.Generic
中的相应类型。
语句的编译时处理首先确定表达式的foreach
集合类型、枚举器类型和迭代类型。 此决定按如下所述进行:
- 如果表达式的类型
X
是数组类型,则从 X 到IEnumerable
接口有隐式引用转换(因为System.Array
实现此接口)。 集合类型是IEnumerable
接口,枚举器类型是IEnumerator
接口,迭代类型是数组类型的元素类型X
。 - 如果表达式
dynamic
类型X
,则存在从表达式到IEnumerable
接口的隐式转换(§10.2.10)。 集合类型是IEnumerable
接口,枚举器类型是IEnumerator
接口。var
如果标识符作为local_variable_type给定,则迭代类型为dynamic
,否则为object
。 - 否则,请确定类型
X
是否具有适当的GetEnumerator
方法:- 使用标识符
GetEnumerator
和无类型参数对类型X
执行成员查找。 如果成员查找不生成匹配项,或者生成不明确的匹配项,或者生成不是方法组的匹配项,请检查可枚举接口,如下所示。 如果成员查找生成除方法组或不匹配之外的任何内容,则建议发出警告。 - 使用生成的方法组和空参数列表执行重载解析。 如果重载解析不会导致任何适用的方法、不明确或导致单一最佳方法,但该方法为静态或不公开方法,请检查是否具有可枚举接口,如下所示。 如果重载解析产生除明确公共实例方法或不适用方法之外的任何内容,则建议发出警告。
- 如果方法的
GetEnumerator
返回类型E
不是类、结构或接口类型,则会生成错误,并且不执行进一步的步骤。 - 成员查找使用
E
标识符Current
执行,并且没有类型参数。 如果成员查找不生成匹配项,则结果为错误,或者结果为允许读取的公共实例属性以外的任何内容,将生成错误,并且不执行进一步的步骤。 - 成员查找使用
E
标识符MoveNext
执行,并且没有类型参数。 如果成员查找不生成匹配项,则结果为错误,或者结果为除方法组之外的任何内容,则会生成错误,并且不执行进一步的步骤。 - 重载解析在具有空参数列表的方法组中执行。 如果重载解析没有产生任何适用的方法、不明确或导致单一最佳方法,但该方法是静态的或不公开的,或者返回类型不是
bool
,则会生成错误,并且不会采取进一步的步骤。 - 集合类型为
X
,枚举器类型为E
,迭代类型为属性的类型Current
。 该Current
属性可能包括ref
修饰符,在这种情况下,返回的表达式是可选的 只读variable_reference (§9.5)。
- 使用标识符
- 否则,请检查可枚举接口:
- 如果存在从隐式转换到的所有类型
Tᵢ
中,则存在一种唯一类型T
,因此T
不是dynamic
,而对于所有其他Tᵢ
类型,则从中隐式转换IEnumerable<T>
IEnumerable<Tᵢ>
,则集合类型为接口,枚举器类型为接口IEnumerator<T>
IEnumerable<T>
,迭代类型为T
。IEnumerable<Tᵢ>
X
- 否则,如果有多个此类类型
T
,则会生成错误,并且不执行进一步的步骤。 - 否则,如果从接口隐式转换
X
System.Collections.IEnumerable
,则集合类型为此接口,枚举器类型为接口System.Collections.IEnumerator
,迭代类型为object
接口。 - 否则,将生成错误,并且不采取进一步的步骤。
- 如果存在从隐式转换到的所有类型
上述步骤(如果成功)明确生成集合类型 C
、枚举器类型和 E
迭代类型 T
, ref T
或 ref readonly T
。 foreach
窗体的语句
foreach (V v in x) «embedded_statement»
然后等效于:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
该变量 e
对表达式 x
或嵌入语句或任何其他程序的源代码不可见或可访问。 v
变量在嵌入式语句中是只读的。 如果没有从(迭代类型)到(语句中的foreach
local_variable_type)T
的显式转换(§10.3),则会生成错误,并且不执行进一步的步骤。V
当迭代变量是引用变量(§9.7)时,窗体 foreach
的语句
foreach (ref V v in x) «embedded_statement»
然后等效于:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
该变量 e
对表达式 x
、嵌入语句或任何其他程序的源代码不可见或可访问。 引用变量 v
在嵌入式语句中是读写的,但 v
不应重新分配 ref-reassigned(§12.21.3)。 如果没有从(迭代类型)到V
(语句中的foreach
local_variable_type)T
的标识转换(§10.2.2),则会生成错误,并且不执行进一步的步骤。
foreach
表单foreach (ref readonly V v in x) «embedded_statement»
的语句具有类似的等效形式,但引用变量v
位于ref readonly
嵌入语句中,因此无法重新分配或重新分配引用。
注意:如果
x
具有值null
,则会在运行时引发 aSystem.NullReferenceException
。 end note
允许实现以不同的方式实现给定 foreach_statement ;例如,出于性能原因,只要行为与上述扩展一致。
循环内部while
的位置v
对于在embedded_statement中发生的任何匿名函数如何捕获它(§12.19.6.2)非常重要。
示例:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();
如果在
v
循环外部while
声明了扩展窗体,则它将在所有迭代之间共享,循环后for
的值将是最终值,13
这就是调用将打印的内容f
。 相反,因为每次迭代都有自己的变量v
,因此在第一次迭代中捕获f
的值将继续保留值,这就是要打印的值7
。 (请注意,在循环外部声明v
的while
早期版本的 C# 。end 示例
块的 finally
主体根据以下步骤构造:
如果存在从
E
System.IDisposable
接口进行隐式转换,则如果
E
为不可为 null 的值类型,则子finally
句将扩展为语义等效项:finally { ((System.IDisposable)e).Dispose(); }
否则,子
finally
句将扩展为语义等效项:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
E
除非是值类型,或者实例化为值类型的类型参数,则转换e
到System.IDisposable
的类型不应导致装箱。
否则,如果
E
为密封类型,则子finally
句将扩展为空块:finally {}
否则,子
finally
句将扩展为:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
任何用户代码都看不到或可访问局部变量 d
。 具体而言,它不会与范围包括 finally
块的任何其他变量冲突。
遍历数组元素的顺序 foreach
如下:对于单维数组元素,按索引顺序遍历,从索引 0 开始,以索引 Length – 1
结尾。 对于多维数组,将遍历元素,以便先增加最右侧维度的索引,再遍历下一个左维度,依此类移。
示例:以下示例按元素顺序输出二维数组中的每个值:
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($"{elementValue} "); } Console.WriteLine(); } }
生成的输出如下所示:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9
end 示例
示例:在以下示例中
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }
推断为
n
int
迭代类型的numbers
类型。end 示例
13.10 Jump 语句
13.10.1 常规
跳转语句无条件地转移控制。
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
jump 语句传输控件的位置称为 jump 语句的目标 。
当跳转语句发生在块内,而跳转语句的目标位于该块之外时,将表示跳转语句退出该块。 虽然 jump 语句可以将控制权从块中转移出来,但它永远无法将控制权转移到块中。
跳转语句的执行因存在干预 try
语句而变得复杂。 在没有此类 try
陈述的情况下,跳转语句无条件地将控制权从跳转语句转移到其目标。 在存在此类干预 try
语句的情况下,执行更为复杂。 如果 jump 语句退出一个或多个 try
具有关联 finally
块的块,则控件最初将传输到 finally
最 try
内部语句的块。 当控件到达块的 finally
终点时,控件将 finally
传输到下一个封闭 try
语句的块。 此过程将重复执行, finally
直到执行所有干预 try
语句的块。
示例:在以下代码中
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"); } }
finally
在将控制转移到 jump 语句的目标之前,将执行与两个try
语句关联的块。 生成的输出如下所示:Before break Innermost finally block Outermost finally block After break
end 示例
13.10.2 中断语句
该break
语句退出最接近的switch
封闭、while
、do
或for
foreach
语句。
break_statement
: 'break' ';'
;
语句的目标break
是最接近的封闭switch
、while
、do
或for
foreach
语句的终点。 break
如果语句未由编译 while
foreach
for
do
时错误括起来switch
,则会发生编译时错误。
当多个switch
、while
、do
或for
foreach
语句相互嵌套时,语句break
仅适用于最内部的语句。 若要跨多个嵌套级别传输控制, goto
应使用语句(§13.10.4)。
语句 break
无法退出 finally
块(§13.11)。 break
当语句在块中finally
发生时,该语句的目标break
应位于同finally
一个块中;否则会发生编译时错误。
语句 break
执行如下:
break
如果语句退出一个或多个try
具有关联finally
块的块,则控件最初会传输到finally
最try
内部语句的块。 当控件到达块的finally
终点时,控件将finally
传输到下一个封闭try
语句的块。 此过程将重复执行,finally
直到执行所有干预try
语句的块。- 控制权将传输到语句的目标
break
。
break
由于语句无条件地将控制权转移到别处,因此break
语句的终点永远无法到达。
13.10.3 继续语句
该continue
语句开始对最近的封闭while
、do
或for
foreach
语句进行新的迭代。
continue_statement
: 'continue' ';'
;
语句的目标continue
是最接近封闭while
、do
或for
foreach
语句的嵌入语句的终点。 continue
如果语句未由编译do
for
foreach
时错误括起来while
,则会发生编译时错误。
当多个 while
语句、 do
语句 for
或 foreach
语句相互嵌套时,语句 continue
仅适用于最内部的语句。 若要跨多个嵌套级别传输控制, goto
应使用语句(§13.10.4)。
语句 continue
无法退出 finally
块(§13.11)。 continue
当语句在块中finally
发生时,该语句的目标continue
应位于同finally
一个块中;否则会发生编译时错误。
语句 continue
执行如下:
continue
如果语句退出一个或多个try
具有关联finally
块的块,则控件最初会传输到finally
最try
内部语句的块。 当控件到达块的finally
终点时,控件将finally
传输到下一个封闭try
语句的块。 此过程将重复执行,finally
直到执行所有干预try
语句的块。- 控制权将传输到语句的目标
continue
。
continue
由于语句无条件地将控制权转移到别处,因此continue
语句的终点永远无法到达。
13.10.4 goto 语句
该 goto
语句将控件传输到由标签标记的语句。
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
标识符语句的目标goto
是带有给定标签的标记语句。 如果当前函数成员中不存在具有给定名称的标签,或者 goto
该语句不在标签范围内,则会发生编译时错误。
注意:此规则允许使用
goto
语句将控制权从嵌套范围转移,但不允许转移到嵌套作用域中。 在示例中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($"{str} not found"); continue; done: Console.WriteLine($"Found {str} at [{row}][{colm}]"); } } }
语句
goto
用于将控制权从嵌套范围转移出去。end note
语句的目标 goto case
是紧闭 switch
语句(§13.8.3)中的语句列表,其中包含 case
具有给定常量值的常量模式且没有防护的标签。 goto case
如果语句未包含在语句中switch
,如果最近的switch
封闭语句不包含此类case
语句,或者如果constant_expression未隐式转换为最接近封闭switch
语句的治理类型(§10.2),则会发生编译时错误。
语句的目标 goto default
是包含标签的紧闭 switch
语句(§13.8.3) default
中的语句列表。 goto default
如果语句未由switch
语句括起来,或者最近的封闭switch
语句不包含default
标签,则会发生编译时错误。
语句 goto
无法退出 finally
块(§13.11)。 goto
当语句在块中发生时,该语句的目标goto
应位于同finally
一finally
个块中,否则会发生编译时错误。
语句 goto
执行如下:
goto
如果语句退出一个或多个try
具有关联finally
块的块,则控件最初会传输到finally
最try
内部语句的块。 当控件到达块的finally
终点时,控件将finally
传输到下一个封闭try
语句的块。 此过程将重复执行,finally
直到执行所有干预try
语句的块。- 控制权将传输到语句的目标
goto
。
goto
由于语句无条件地将控制权转移到别处,因此goto
语句的终点永远无法到达。
13.10.5 返回语句
该 return
语句将控件返回到返回语句出现的函数成员的当前调用方,可以选择返回值或 variable_reference (§9.5)。
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
不带表达式的return_statement称为 return-no-value;一个包含ref
表达式的表达式称为按 ref 返回;一个只包含表达式的表达式称为返回值。
使用声明为“按值返回”或 returns-by-ref(§15.6.1)的方法返回-no-value 是编译时错误。
从声明为 returns-no-value 或 returns-by-value 的方法使用 return-by-ref 的编译时错误。
使用声明为 returns-no-value 或 returns-by-ref 的方法返回值是编译时错误。
如果表达式不是variable_reference,或者对引用的变量不是调用方上下文(§9.7.2),则使用按 ref 返回的编译时错误。
使用通过method_modifierasync
声明的方法返回引用是编译时错误。
如果函数成员是具有 returns-by-value 方法(§15.6.11)、属性或索引器或用户定义的运算符的按值获取访问器,则表示计算值。 返回值-无值的函数成员不计算值,并且是具有有效返回类型 void
、属性和索引器的设置访问器、添加和删除事件访问器、实例构造函数、静态构造函数和终结器的方法。 按 ref 返回的函数成员不计算值。
对于按值返回,包含函数成员的有效返回类型(§15.6.11)应存在隐式转换(§10.2)。 对于按 ref 返回,标识转换(§10.2.2)应存在于表达式的类型和包含函数成员的有效返回类型之间。
return
语句也可以在匿名函数表达式(§12.19)的正文中使用,并参与确定这些函数存在哪些转换(§10.7.1)。
语句出现在块(§13.11)中finally
是编译时错误return
。
语句 return
执行如下:
- 对于返回值, 将计算表达式 ,并通过隐式转换将其值转换为包含函数的有效返回类型。 转换的结果将成为函数生成的结果值。 对于按 ref 返回, 将计算表达式 ,结果应分类为变量。 如果封闭方法的 return-by-ref 包含
readonly
,则生成的变量为只读。 return
如果语句由一个或多个catch
try
具有关联finally
块的块括起来,则控件最初将传输到finally
最try
内部语句的块。 当控件到达块的finally
终点时,控件将finally
传输到下一个封闭try
语句的块。 此过程将重复执行,finally
直到执行所有封闭try
语句的块。- 如果包含函数不是异步函数,则控件将返回给包含函数的调用方以及结果值(如果有)。
- 如果包含函数是异步函数,则控件将返回到当前调用方,并且结果值(如果有)将记录在返回任务中(§15.15.3)。
return
由于语句无条件地将控制权转移到别处,因此return
语句的终点永远无法到达。
13.10.6 throw 语句
该 throw
语句引发异常。
throw_statement
: 'throw' expression? ';'
;
throw
具有表达式的语句引发通过计算表达式生成的异常。 表达式应可隐式转换为 System.Exception
表达式,在引发表达式之前将转换为 System.Exception
该表达式的结果。 如果转换的结果是 null
,则会引发一个 System.NullReferenceException
转换。
throw
没有表达式的语句只能在块中使用catch
,在这种情况下,该语句将重新引发当前由该catch
块处理的异常。
throw
由于语句无条件地将控制权转移到别处,因此throw
语句的终点永远无法到达。
引发异常时,控件将传输到可以处理异常的封闭try
语句中的第一catch
个子句。 从引发的异常点到将控件传输到适当的异常处理程序的点的过程称为异常传播。 异常的传播包括重复评估以下步骤,直到 catch
找到与异常匹配的子句。 在此说明中, 引发点 最初是引发异常的位置。 此行为在 (§21.4) 中指定。
在当前函数成员中,将检查包含引发点的每个
try
语句。 对于每个语句S
,从最try
内部的语句开始,以最try
外部的语句结尾,将评估以下步骤:try
如果引发点S
的块S
和包含一个或多个catch
子句,catch
则会检查子句,以便为异常找到合适的处理程序。 指定异常类型(或运行时表示异常类型的T
T
类型参数)的第一catch
个子句,以便派生自T
的E
运行时类型被视为匹配项。 如果该子句包含异常筛选器,则异常对象将分配给异常变量,并计算异常筛选器。catch
当子句包含异常筛选器时,catch
如果异常筛选器的计算结果为true
,则该子句被视为匹配项。 常规catch
(§13.11) 子句被视为任何异常类型的匹配项。 如果找到匹配catch
子句,则通过将控制权传输到该catch
子句块来完成异常传播。- 否则,如果
try
块或catch
块S
将引发点括起来,并且S
如果具有finally
块,则将控制权传输到finally
块。 如果finally
块引发另一个异常,则终止处理当前异常。 否则,当控制到达块的finally
终点时,将继续处理当前异常。
如果异常处理程序未位于当前函数调用中,函数调用将终止,并发生以下情况之一:
如果异常处理终止当前线程中的所有函数成员调用,指示该线程没有异常的处理程序,则线程本身将终止。 此类终止的影响是实现定义的。
13.11 try 语句
该 try
语句提供了一种机制,用于捕获执行块期间发生的异常。 此外,该 try
语句提供指定在控件离开 try
语句时始终执行的代码块的功能。
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
try_statement由后跟块的关键字try
组成,然后是零个或多个catch_clauses,然后是可选的finally_clause。 应至少有一个catch_clause或finally_clause。
在exception_specifier类型或其有效基类(如果它是type_parameter)应System.Exception
或派生自它的类型。
catch
当子句同时指定class_type和标识符时,将声明给定名称和类型的异常变量。 异常变量将引入specific_catch_clause(§7.3)的声明空间中。 在执行 exception_filter 和 catch
块期间,异常变量表示当前正在处理的异常。 为了进行明确的赋值检查,异常变量被视为在其整个范围内明确分配。
除非子 catch
句包含异常变量名称,否则无法访问筛选器和 catch
块中的异常对象。
一个 catch
指定异常类型或异常变量名称的子句称为常规 catch
子句。 语句 try
只能有一个常规子句,如果存在一个语句 catch
,则它应该是最后 catch
一个子句。
注意:某些编程语言可能支持无法表示为派生
System.Exception
对象的异常,尽管 C# 代码无法生成此类异常。 常规catch
子句可用于捕获此类异常。 因此,常规catch
子句在语义上不同于指定类型的System.Exception
子句,因此,前者也可能捕获其他语言的异常。 end note
为了查找异常的处理程序, catch
子句按词法顺序进行检查。 catch
如果子句指定类型但没有异常筛选器,则为同一语句的后续catch
子句指定与该类型相同的try
类型或派生自该类型的编译时错误。
注意:如果没有此限制,可以编写无法访问
catch
的子句。 end note
catch
在块中,throw
没有表达式的语句(§13.10.6)可用于重新引发该块捕获的catch
异常。 对异常变量的赋值不会更改重新引发的异常。
示例:在以下代码中
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
end 示例
它是一个编译时错误break
,continue
用于将控制权从块传出finally
或goto
语句。 当在break
块中发生或continue
goto
语句时,语句的目标应位于同finally
一finally
个块中,否则会发生编译时错误。
这是语句在块中finally
发生的编译时错误return
。
执行到达 try
语句时,控制权将 try
传输到块。 如果控件到达块的 try
终点,而不会传播异常,则控件将传输到 finally
块(如果存在)。 如果不存在 finally
块,则控制权将传输到语句的 try
终点。
如果已传播异常, catch
则按词法顺序检查子句,以寻求异常的第一个匹配项。 搜索匹配catch
子句将继续包含所有封闭块,如 §13.10.6 中所述。 如果异常类型匹配任何exception_specifier且任何exception_filter为 true,则子catch
句是匹配项。 catch
不包含exception_specifier的子句与任何异常类型匹配。 当exception_specifier指定异常类型或异常类型的基类型时,异常类型与exception_specifier匹配。 如果该子句包含异常筛选器,则异常对象将分配给异常变量,并计算异常筛选器。
如果已传播异常并找到匹配 catch
子句,则会将控件传输到第一个匹配 catch
块。 如果控件到达块的 catch
终点,而不会传播异常,则控件将传输到 finally
块(如果存在)。 如果不存在 finally
块,则控制权将传输到语句的 try
终点。 如果已从 catch
块传播异常,则控件会传输到 finally
块(如果存在)。 异常将传播到下一个封闭 try
语句。
如果已传播异常,并且找不到匹配 catch
子句,则控制会传输到 finally
块(如果存在)。 异常将传播到下一个封闭 try
语句。
当控制离开 finally
语句时,try
块的语句会始终执行。 无论控制传输是由于正常执行、执行、执行break
、goto
continue
还是return
语句而发生,还是由于传播语句异常try
而发生,都是如此。 如果控件到达块的 finally
终点,而不会传播异常,则控件将传输到语句的 try
终点。
如果在执行块期间引发异常,并且未在同一 finally
块中 finally
捕获,则会将异常传播到下一个封闭 try
语句。 如果在传播过程中存在另一个异常,该异常将丢失。 在声明的说明 throw
(§13.10.6)中进一步讨论了传播异常的过程。
示例:在以下代码中
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }
该方法
Method
引发异常。 第一个操作是检查封闭catch
子句,执行任何 异常筛选器。 然后,finally
控件将Method
传输到封闭匹配catch
子句之前执行该子句。 生成的输出为:Filter Finally Catch
end 示例
try
如果try
可访问语句,则可访问语句的try
块。
catch
如果try
可访问语句,则可访问语句块try
。
finally
如果try
可访问语句,则可访问语句的try
块。
如果两个语句均为 true,则语句的终点是可访问的 try
:
- 块的终点是可访问的
try
,或者至少一个catch
块的终点是可访问的。 finally
如果存在块,则可访问块的finally
终点。
13.12 已选中和未选中的语句
和checked
unchecked
语句用于控制整型算术运算和转换的溢出检查上下文。
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
该checked
语句会导致块中的所有表达式在选中的上下文中求值,并且该unchecked
语句会导致在未选中的上下文中计算块中的所有表达式。
和checked
unchecked
语句与运算符(§12.8.20)完全相同checked
unchecked
,只是它们对块而不是表达式进行操作。
13.13 lock 语句
该 lock
语句获取给定对象的互斥锁,执行语句,然后释放锁。
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
语句的lock
表达式应表示已知为引用的类型的值。 从未对语句的lock
表达式执行隐式装箱转换(§10.2.9),因此表达式表示value_type值的编译时错误。
lock
窗体的语句
lock (x)
…
其中x
是reference_type的表达式,精确等效于:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
不同的是 x
只计算一次。
当保留互斥锁时,在同一执行线程中执行的代码也可以获取和释放锁。 但是,在释放锁之前,阻止在其他线程中执行的代码获取锁。
13.14 using 语句
该 using
语句获取一个或多个资源,执行语句,然后释放资源。
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
资源是实现System.IDisposable
接口的类或结构,其中包括名为Dispose
单个无参数方法的类或结构。 使用资源的代码可以调用 Dispose
以指示不再需要资源。
如果resource_acquisition的形式local_variable_declaration,则local_variable_declaration的类型应为dynamic
或可隐式转换为System.IDisposable
的类型。 如果resource_acquisition的形式是表达式,则此表达式应隐式转换为 System.IDisposable
。
在resource_acquisition中声明的局部变量是只读的,并且应包含初始值设定项。 如果嵌入语句尝试通过赋值或运算符修改这些局部变量(通过赋值或 ++
运算符 --
),则会发生编译时错误,或将它们作为引用或输出参数传递。
语句 using
转换为三个部分:购置、使用情况和处置。 资源的用法隐式包含在包含finally
子句的语句中try
。 此 finally
子句释放资源。 null
如果获取资源,则不会调用任何Dispose
资源,也不会引发异常。 如果资源的类型 dynamic
为该资源,则通过隐式动态转换(§10.2.10)在 IDisposable
获取期间动态转换,以确保转换在使用和处置之前成功。
using
窗体的语句
using (ResourceType resource = «expression» ) «statement»
对应于三个可能的扩展之一。 如果 ResourceType
为不可为 null 的值类型或具有值类型约束的类型参数(§15.2.5),则扩展在语义上等效于
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
除非演员的演员resource
System.IDisposable
不应导致拳击发生。
否则,如果 ResourceType
为 dynamic
扩展,则为
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
否则,扩展为
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
在任何扩展中,变量 resource
在嵌入语句中是只读的,并且该 d
变量不可访问,并且对嵌入语句不可见。
只要行为与上述扩展一致,就允许实现给定 using_statement 不同的方式,例如出于性能原因。
using
表单的语句:
using («expression») «statement»
具有相同的三个可能的扩展。 在这种情况下,如果表达式具有编译时类型,则ResourceType
隐式为表达式的编译时类型。 否则,接口 IDisposable
本身将用作 ResourceType
. 该 resource
变量不可访问,并且对嵌入 语句不可见。
当resource_acquisition采用local_variable_declaration的形式时,可以获取给定类型的多个资源。 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的文件,并将两行文本写入文件。 然后,该示例打开同一个文件,用于读取和将包含的文本行复制到控制台。
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
语句来确保基础文件在写入或读取操作后正确关闭。end 示例
13.15 yield 语句
该yield
语句用于迭代器块(§13.3)向枚举器对象(§15.14.5)或可枚举对象(§15.14.6)生成值,或发出迭代结束信号。
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield
是上下文关键字(§6.4.4),仅在紧邻或关键字之前return
break
使用时才具有特殊含义。
语句的显示位置 yield
存在一些限制,如下所述。
- 在method_body、operator_body或accessor_body外部显示语句(任一形式)的编译时错误
yield
。 - 在匿名函数中出现语句(任一形式)的编译时错误
yield
。 - 语句(任何一
yield
种形式)出现在语句的try
子句中finally
是编译时错误。 - 语句在包含任何catch_clauses的
try
语句中的任意位置出现编译时错误yield return
。
示例:以下示例显示了语句的一些有效和无效用法
yield
。delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // Error, yield in finally yield break; // Error, yield in finally } try { yield return 3; // Error, yield return in try/catch yield break; // Ok } catch { yield return 4; // Error, yield return in try/catch yield break; // Ok } D d = delegate { yield return 5; // Error, yield in an anonymous function }; } int MyMethod() { yield return 1; // Error, wrong return type for an iterator block }
end 示例
隐式转换(§10.2)应从语句中的 yield return
表达式类型到迭代器的 yield 类型(§15.14.4)。
语句 yield return
执行如下:
- 计算语句中给定的表达式,隐式转换为 yield 类型,并分配给
Current
枚举器对象的属性。 - 迭代器块的执行挂起。
yield return
如果语句位于一个或多个try
块内,则此时不会执行关联的finally
块。 MoveNext
枚举器对象的方法返回到true
其调用方,指示枚举器对象已成功推进到下一项。
对枚举器对象方法的 MoveNext
下一次调用将从上次暂停的位置恢复迭代器块的执行。
语句 yield break
执行如下:
yield break
如果语句由一个或多个try
具有关联finally
块的块括起来,则控件最初会传输到finally
最try
内部语句的块。 当控件到达块的finally
终点时,控件将finally
传输到下一个封闭try
语句的块。 此过程将重复执行,finally
直到执行所有封闭try
语句的块。- 控件将返回到迭代器块的调用方。 这是
MoveNext
枚举器对象的方法或Dispose
方法。
yield break
由于语句无条件地将控制权转移到别处,因此yield break
语句的终点永远无法到达。