C#提供了各种语句。大多数这些语句对于使用C和C ++编程的开发人员来说都很熟悉。
1 statement 2 : labeled_statement 3 | declaration_statement 4 | embedded_statement 5 ; 6 7 embedded_statement 8 : block 9 | empty_statement 10 | expression_statement 11 | selection_statement 12 | iteration_statement 13 | jump_statement 14 | try_statement 15 | checked_statement 16 | unchecked_statement 17 | lock_statement 18 | using_statement 19 | yield_statement 20 | embedded_statement_unsafe 21 ;
该embedded_statement非终结用于出现其他语句中的语句。使用embedded_statement而不是语句排除了在这些上下文中使用声明语句和带标签的语句。这个例子
1 void F(bool b) { 2 if (b) 3 int i = 44; 4 }
导致编译时错误,因为if
语句需要embedded_statement而不是if分支的语句。如果允许此代码,则会i
声明变量,但永远不会使用它。但请注意,通过i
在块中放置声明,该示例是有效的。
终点和可达性
每个陈述都有一个终点。直观地说,语句的结束点是紧跟语句的位置。复合语句(包含嵌入语句的语句)的执行规则指定控件到达嵌入语句的结束点时所采取的操作。例如,当控件到达块中语句的结束点时,控制权将转移到块中的下一个语句。
如果可以通过执行来达到语句,则说该语句是可访问的。相反,如果不可能执行语句,则说该语句无法访问。
在这个例子中
1 void F() { 2 Console.WriteLine("reachable"); 3 goto Label; 4 Console.WriteLine("unreachable"); 5 Label: 6 Console.WriteLine("reachable"); 7 }
第二次调用Console.WriteLine
是无法访问的,因为不可能执行该语句。
如果编译器确定语句无法访问,则会报告警告。声明无法访问并不是错误。
要确定特定语句或端点是否可访问,编译器将根据为每个语句定义的可访问性规则执行流分析。流分析考虑了控制语句行为的常量表达式(常量表达式)的值,但不考虑非常量表达式的可能值。换句话说,出于控制流分析的目的,给定类型的非常量表达式被认为具有该类型的任何可能值。
在这个例子中
1 void F() { 2 const int i = 1; 3 if (i == 2) Console.WriteLine("unreachable"); 4 }
if
语句的布尔表达式是一个常量表达式,因为运算==
符的两个操作数都是常量。由于常量表达式是在编译时计算的,因此生成该值时false
,Console.WriteLine
调用被视为无法访问。但是,如果i
将其更改为局部变量
1 void F() { 2 int i = 1; 3 if (i == 2) Console.WriteLine("reachable"); 4 }
在Console.WriteLine
调用被认为是可到达的,即使在现实中,它永远不会被执行。
功能成员的块始终被视为可访问。通过连续评估块中每个语句的可达性规则,可以确定任何给定语句的可达性。
在这个例子中
1 void F(int x) { 2 Console.WriteLine("start"); 3 if (x < 0) Console.WriteLine("negative"); 4 }
第二个的可达性Console.WriteLine
确定如下:
- 第一个
Console.WriteLine
表达式语句是可到达的,因为该F
方法的块是可访问的。 Console.WriteLine
可以访问第一个表达式语句的结束点,因为该语句是可访问的。- 该
if
语句是可访问的,因为第一个Console.WriteLine
表达式语句的结束点是可到达的。 - 第二个
Console.WriteLine
表达式语句是可到达的,因为语句的布尔表达式if
没有常量值false
。
在两种情况下,语句的结束点可以访问是编译时错误:
- 因为该
switch
语句不允许切换部分“通过”到下一个切换部分,所以切换部分的语句列表的结束点可以到达是编译时错误。如果发生此错误,通常表示break
缺少语句。 - 它是函数成员块的结束点的编译时错误,它计算可以访问的值。如果发生此错误,则通常表示
return
缺少语句。
块
一个块允许在一个单一的语句允许上下文中编写多条语句。
1 block 2 : '{' statement_list? '}' 3 ;
一个块由一个可选的statement_list(语句列表)组成,括在括号中。如果省略语句列表,则该块被认为是空的。
一个块可能包含声明语句(声明声明)。块中声明的局部变量或常量的范围是块。
块执行如下:
- 如果块为空,则控制转移到块的结束点。
- 如果块不为空,则将控制转移到语句列表。当控制到达语句列表的结束点时,控制转移到块的结束点。
如果块本身可访问,则可以访问块的语句列表。
如果块为空或者可以访问语句列表的结束点,则可以访问块的结束点。
阿块包含一个或多个yield
语句(yield语句)被称为迭代器块。迭代器块用于将函数成员实现为迭代器(迭代器)。迭代器块有一些额外的限制:
return
语句出现在迭代器块中是一个编译时错误(但yield return
允许使用语句)。- 迭代器块包含不安全的上下文(Unsafe contexts)是编译时错误。迭代器块总是定义一个安全上下文,即使它的声明嵌套在不安全的上下文中也是如此。
语句列表
一个语句列表由顺序写入的一个或多个语句。语句列表出现在块 s(块)和switch_blocks(switch语句)中。
1 statement_list 2 : statement+ 3 ;
通过将控制转移到第一个语句来执行语句列表。当控制到达语句的结束点时,控制权转移到下一个语句。当控制到达最后一个语句的结束点时,控制权转移到语句列表的结束点。
如果至少满足下列条件之一,则可以访问语句列表中的语句:
- 该语句是第一个语句,语句列表本身是可访问的。
- 可以访问上一个语句的结束点。
- 该语句是带标签的语句,标签由可访问的
goto
语句引用。
如果列表中最后一个语句的结束点可达,则可以访问语句列表的结束点。
空语句
一个empty_statement什么都不做。
1 empty_statement 2 : ';' 3 ;
如果在需要语句的上下文中没有要执行的操作,则使用空语句。
执行空语句只是将控制转移到语句的结束点。因此,如果可以访问空语句,则可以访问空语句的结束点。
使用while
null主体编写语句时可以使用空语句:
1 bool ProcessMessage() {...} 2 3 void ProcessMessages() { 4 while (ProcessMessage()) 5 ; 6 }
此外,可以使用空语句}
在块的结束“ ” 之前声明标签:
1 void F() { 2 ... 3 if (done) goto exit; 4 ... 5 exit: ; 6 }
标签语句
一个labeled_statement允许一个标签作为前缀的声明。块中允许使用带标签的语句,但不允许使用嵌入语句。
1 labeled_statement 2 : identifier ':' statement 3 ;
带标签的语句声明一个标签,其名称由标识符指定。标签的范围是声明标签的整个块,包括任何嵌套块。具有相同名称的两个标签具有重叠范围是编译时错误。
可以从标签范围内的goto
语句(goto语句)引用标签。这意味着goto
语句可以在块内和块之外传输控制,但永远不会转移到块中。
标签有自己的声明空间,不会干扰其他标识符。这个例子
1 int F(int x) { 2 if (x >= 0) goto x; 3 x = -x; 4 x: return x; 5 }
是有效的,并将名称x
用作参数和标签。
标签语句的执行完全对应于标签后面的语句的执行。
除了正常控制流提供的可访问性之外,如果标签由可访问goto
语句引用,则可以访问带标签的语句。(例外:如果goto
语句位于try
包含finally
块的内部,并且带标签的语句在该区域之外try
,并且该finally
块的结束点无法访问,则无法从该goto
语句访问带标签的语句。)
声明语句
一个declaration_statement声明一个局部变量或常量。声明语句在块中是允许的,但不允许作为嵌入语句。
1 declaration_statement 2 : local_variable_declaration ';' 3 | local_constant_declaration ';' 4 ;
局部变量声明
一个local_variable_declaration声明一个或多个局部变量。
1 local_variable_declaration 2 : local_variable_type local_variable_declarators 3 ; 4 5 local_variable_type 6 : type 7 | 'var' 8 ; 9 10 local_variable_declarators 11 : local_variable_declarator 12 | local_variable_declarators ',' local_variable_declarator 13 ; 14 15 local_variable_declarator 16 : identifier 17 | identifier '=' local_variable_initializer 18 ; 19 20 local_variable_initializer 21 : expression 22 | array_initializer 23 | local_variable_initializer_unsafe 24 ;
所述local_variable_type一个的local_variable_declaration直接指定由该声明引入的变量的类型,或与该标识符指示var
该类型应该根据一个初始化来推断。该类型后跟一个local_variable_declarator列表,每个都引入一个新变量。甲local_variable_declarator由一个的标识符名称变量,任选地随后通过“ =
”令牌和local_variable_initializer,使该变量的初始值。
在局部变量声明的上下文中,标识符var充当上下文关键字(关键字)。当local_variable_type被指定为var
并且没有命名的类型var
在范围内时,声明是隐式类型的局部变量声明,其类型是从关联的初始化表达式的类型。隐式类型的局部变量声明受以下限制:
- 该local_variable_declaration不能包含多个local_variable_declarator秒。
- 该local_variable_declarator必须包括local_variable_initializer。
- 所述local_variable_initializer必须是表达。
- 初始化表达式必须具有编译时类型。
- 初始化表达式不能引用声明的变量本身
以下是不正确的隐式类型局部变量声明的示例:
1 var x; // Error, no initializer to infer type from 2 var y = {1, 2, 3}; // Error, array initializer not permitted 3 var z = null; // Error, null does not have a type 4 var u = x => x + 1; // Error, anonymous functions do not have a type 5 var v = v++; // Error, initializer cannot refer to variable itself
使用simple_name(简单名称)在表达式中获取局部变量的值,并使用赋值(赋值运算符)修改局部变量的值。必须在获得其值的每个位置明确赋值(定义赋值)局部变量。
在local_variable_declaration中声明的局部变量的范围是声明发生的块。在局部变量的local_variable_declarator之前的文本位置引用局部变量是错误的。在局部变量的范围内,声明另一个具有相同名称的局部变量或常量是编译时错误。
声明多个变量的局部变量声明等效于具有相同类型的单个变量的多个声明。此外,局部变量声明中的变量初始值设定项与声明后立即插入的赋值语句完全对应。
这个例子
1 void F() { 2 int x = 1, y, z = x * 2; 3 }
完全对应于
1 void F() { 2 int x; x = 1; 3 int y; 4 int z; z = x * 2; 5 }
在隐式类型的局部变量声明中,声明的局部变量的类型与用于初始化变量的表达式的类型相同。例如:
1 var i = 5; 2 var s = "Hello"; 3 var d = 1.0; 4 var numbers = new int[] {1, 2, 3}; 5 var orders = new Dictionary<int,Order>();
上面隐式类型化的局部变量声明与以下显式类型声明完全等效:
1 int i = 5; 2 string s = "Hello"; 3 double d = 1.0; 4 int[] numbers = new int[] {1, 2, 3}; 5 Dictionary<int,Order> orders = new Dictionary<int,Order>();
局部常量声明
一个local_constant_declaration声明一个或多个局部常量。
1 local_constant_declaration 2 : 'const' type constant_declarators 3 ; 4 5 constant_declarators 6 : constant_declarator (',' constant_declarator)* 7 ; 8 9 constant_declarator 10 : identifier '=' constant_expression 11 ;
该类型一的local_constant_declaration指定由该声明引入的常数的类型。该类型后面是一个constant_declarator列表,每个都引入一个新的常量。甲constant_declarator由一个的标识符名称的恒定,接着是“ =
”标记,接着是constant_expression(常量表达式给出的常数的值)。
本地常量声明的类型和constant_expression必须遵循与常量成员声明(常量)相同的规则。
使用simple_name(简单名称)在表达式中获取局部常量的值。
局部常量的范围是声明发生的块。在constant_declarator之前的文本位置引用局部常量是错误的。在局部常量的范围内,声明另一个具有相同名称的局部变量或常量是编译时错误。
声明多个常量的局部常量声明等效于具有相同类型的单个常量的多个声明。
表达式语句
一个expression_statement计算所给定的表达。表达式计算的值(如果有)将被丢弃。
1 expression_statement 2 : statement_expression ';' 3 ; 4 5 statement_expression 6 : invocation_expression 7 | null_conditional_invocation_expression 8 | object_creation_expression 9 | assignment 10 | post_increment_expression 11 | post_decrement_expression 12 | pre_increment_expression 13 | pre_decrement_expression 14 | await_expression 15 ;
并非所有表达式都被允许作为语句。特别是,诸如x + y
和x == 1
仅仅计算一个值(将被丢弃)的表达式不允许作为语句。
执行expression_statement会计算包含的表达式,然后将控制权转移到expression_statement的结束点。如果可以访问expression_statement,则可以访问expression_statement的结束点。
Selection语句
Selection语句根据某个表达式的值选择一些可能的语句来执行。
1 selection_statement 2 : if_statement 3 | switch_statement 4 ;
if语句
该if
语句根据布尔表达式的值选择要执行的语句。
1 if_statement 2 : 'if' '(' boolean_expression ')' embedded_statement 3 | 'if' '(' boolean_expression ')' embedded_statement 'else' embedded_statement 4 ;
一个else
部分与词法最近的前一个相关联的if
由该句法允许的。因此,if
形式的陈述
1 if (x) if (y) F(); else G();
相当于
1 if (x) { 2 if (y) { 3 F(); 4 } 5 else { 6 G(); 7 } 8 }
一个if
语句的执行方式如下:
- 所述逻辑表达式(布尔表达式)进行评价。
- 如果布尔表达式产生
true
,则控制转移到第一个嵌入语句。当控制到达该语句的结束点时,控制权转移到if
语句的结束点。 - 如果布尔表达式产生
false
并且如果存在else
部件,则控制转移到第二个嵌入语句。当控制到达该语句的结束点时,控制权转移到if
语句的结束点。 - 如果布尔表达式产生
false
并且如果else
部件不存在,则控制转移到if
语句的结束点。
if
如果if
语句可达且布尔表达式没有常量值,则可以访问语句的第一个嵌入语句false
。
if
如果if
语句可访问且布尔表达式不具有常量值,则语句的第二个嵌入语句(如果存在)是可到达的true
。
一个的结束点if
语句是可到达如果嵌入语句中至少一个的结束点是可到达。此外,如果语句可以访问且布尔表达式没有常量值,则可以访问if
没有任何else
部分的语句的结束点。if
true
switch语句
switch语句选择执行一个语句列表,该列表具有与switch表达式的值相对应的关联开关标签。
1 switch_statement 2 : 'switch' '(' expression ')' switch_block 3 ; 4 5 switch_block 6 : '{' switch_section* '}' 7 ; 8 9 switch_section 10 : switch_label+ statement_list 11 ; 12 13 switch_label 14 : 'case' constant_expression ':' 15 | 'default' ':' 16 ;
在switch_statement由关键字switch
,随后是括号表达式(称为开关表达),接着是switch_block。所述switch_block由零个或多个switch_section S,在大括号。每个switch_section包含一个或多个switch_label,后跟一个statement_list(语句列表)。
该管理型 A的switch
陈述,由开关式成立。
- 如果开关表达式的类型是
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,bool
,char
,string
,或enum_type,或者如果它是对应于这些类型之一的空类型,那么这就是的主导类型switch
的语句。 - 否则,只有一个用户定义的隐式转换(用户定义的转换)必须从开关表达式的类型存在以下可能的主导类型之一:
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,string
,或,对应于空类型其中一种类型。 - 否则,如果不存在此类隐式转换,或者如果存在多个此类隐式转换,则会发生编译时错误。
每个case
标签的常量表达式必须表示一个可隐式转换的值(隐式转换)到该switch
语句的控制类型。如果case
同一switch
语句中的两个或多个标签指定相同的常量值,则会发生编译时错误。
default
switch语句中最多只能有一个标签。
一个switch
语句的执行过程如下:
- 评估switch表达式并将其转换为管理类型。
- 如果
case
同一switch
语句中的标签中指定的常量之一等于switch表达式的值,则控制将转移到匹配case
标签后面的语句列表中。 - 如果
case
同一switch
语句中的标签中指定的常量都不等于switch表达式的值,并且如果存在default
标签,则控制将转移到default
标签后面的语句列表中。 - 如果
case
同一switch
语句中的标签中指定的常量都不等于switch表达式的值,并且如果不存在default
标签,则控制将转移到switch
语句的结束点。
如果可以访问switch部分的语句列表的结束点,则会发生编译时错误。这被称为“不通过”规则。这个例子
1 switch (i) { 2 case 0: 3 CaseZero(); 4 break; 5 case 1: 6 CaseOne(); 7 break; 8 default: 9 CaseOthers(); 10 break; 11 }
是有效的,因为没有切换部分具有可到达的终点。与C和C ++不同,开关部分的执行不允许“通过”到下一个开关部分和示例
1 switch (i) { 2 case 0: 3 CaseZero(); 4 case 1: 5 CaseZeroOrOne(); 6 default: 7 CaseAny(); 8 }
导致编译时错误。当执行交换机部分后执行另一个交换机部分时,必须使用显式goto case
或goto default
语句:
1 switch (i) { 2 case 0: 3 CaseZero(); 4 goto case 1; 5 case 1: 6 CaseZeroOrOne(); 7 goto default; 8 default: 9 CaseAny(); 10 break; 11 }
switch_section中允许使用多个标签。这个例子
1 switch (i) { 2 case 0: 3 CaseZero(); 4 break; 5 case 1: 6 CaseOne(); 7 break; 8 case 2: 9 default: 10 CaseTwo(); 11 break; 12 }
已验证。该示例不“通过不落”的规则违反,因为标签case 2:
和default:
是相同的部分switch_section。
“no fall through”规则可以防止在break
语句被意外省略时出现在C和C ++中的常见错误类。此外,由于此规则,switch
语句的切换部分可以任意重新排列,而不会影响语句的行为。例如,switch
可以颠倒上述语句的各个部分,而不会影响语句的行为:
1 switch (i) { 2 default: 3 CaseAny(); 4 break; 5 case 1: 6 CaseZeroOrOne(); 7 goto default; 8 case 0: 9 CaseZero(); 10 goto case 1; 11 }
switch部分的语句列表通常以a break
,goto case
或goto default
语句结尾,但允许呈现语句列表的端点不可达的任何构造。例如,已知while
由布尔表达式控制的语句true
永远不会到达其终点。同样,throw
或者return
语句总是将控制转移到其他地方并且永远不会到达其终点。因此,以下示例有效:
1 switch (i) { 2 case 0: 3 while (true) F(); 4 case 1: 5 throw new ArgumentException(); 6 case 2: 7 return; 8 }
switch
声明的管理类型可以是类型string
。例如:
1 void DoCommand(string command) { 2 switch (command.ToLower()) { 3 case "run": 4 DoRun(); 5 break; 6 case "save": 7 DoSave(); 8 break; 9 case "quit": 10 DoQuit(); 11 break; 12 default: 13 InvalidCommand(command); 14 break; 15 } 16 }
与字符串相等运算符(字符串相等运算符)一样,该switch
语句区分大小写,并且仅当开关表达式字符串与case
标签常量完全匹配时才会执行给定的开关部分。
当switch
语句的控制类型为时string
,该值null
被允许作为案例标签常量。
该STATEMENT_LIST一个第switch_block可以包含声明语句(声明语句)。在switch块中声明的局部变量或常量的范围是switch块。
如果switch
语句可访问且至少满足下列条件之一,则可以访问给定switch部分的语句列表:
- switch表达式是一个非常量值。
- switch表达式是一个与
case
switch部分中的标签匹配的常量值。 - switch表达式是一个与任何
case
标签都不匹配的常量值,switch部分包含default
标签。 - 交换机部分的交换机标签由可达
goto case
或goto default
语句引用。
switch
如果至少满足下列条件之一,则可以访问语句的结束点:
- 该
switch
语句包含break
退出switch
语句的可访问语句。 - 该
switch
语句是可到达的,switch表达式是不恒定的值,并且没有default
标签存在。 - 该
switch
语句是可访问的,switch表达式是一个与任何case
标签都不匹配的常量值,并且不存在default
标签。
迭代语句
迭代语句重复执行嵌入语句。
1 iteration_statement 2 : while_statement 3 | do_statement 4 | for_statement 5 | foreach_statement 6 ;
while语句
该while
语句有条件地执行嵌入语句零次或多次。
1 while_statement 2 : 'while' '(' boolean_expression ')' embedded_statement 3 ;
一个while
语句的执行过程如下:
- 所述逻辑表达式(布尔表达式)进行评价。
- 如果布尔表达式产生
true
,则控制转移到嵌入语句。当控制到达嵌入语句的结束点时(可能来自continue
语句的执行),控制转移到while
语句的开头。 - 如果布尔表达式产生
false
,则控制转移到while
语句的结束点。
在语句的嵌入语句中while
,break
语句(break语句)可用于将控制转移到while
语句的结束点(从而结束嵌入语句的迭代),并且continue
语句(continue语句)可用于将控制转移到嵌入语句的结束点(从而执行语句的另一次迭代while
)。
while
如果while
语句可访问且布尔表达式没有常量值,则可以访问语句的嵌入语句false
。
while
如果至少满足下列条件之一,则可以访问语句的结束点:
- 该
while
语句包含break
退出while
语句的可访问语句。 - 该
while
语句是可访问的,并且布尔表达式没有常量值true
。
do
语句
该do
语句有条件地执行嵌入语句一次或多次。
1 do_statement 2 : 'do' embedded_statement 'while' '(' boolean_expression ')' ';' 3 ;
一个do
语句的执行过程如下:
- 控制转移到嵌入语句。
- 当控件到达嵌入语句的结束点时(可能来自
continue
语句的执行),将评估boolean_expression(布尔表达式)。如果布尔表达式产生true
,则控制转移到do
语句的开头。否则,控制转移到do
语句的结束点。
在语句的嵌入语句中do
,break
语句(break语句)可用于将控制转移到do
语句的结束点(从而结束嵌入语句的迭代),并且continue
语句(continue语句)可用于将控制转移到嵌入语句的结束点。
do
如果do
语句可访问,则可以访问语句的嵌入语句。
do
如果至少满足下列条件之一,则可以访问语句的结束点:
- 该
do
语句包含break
退出do
语句的可访问语句。 - 嵌入语句的结束点是可到达的,布尔表达式没有常量值
true
。
for语句
该for
语句评估一系列初始化表达式,然后在条件为真时重复执行嵌入语句并计算迭代表达式序列。
1 for_statement 2 : 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')' embedded_statement 3 ; 4 5 for_initializer 6 : local_variable_declaration 7 | statement_expression_list 8 ; 9 10 for_condition 11 : boolean_expression 12 ; 13 14 for_iterator 15 : statement_expression_list 16 ; 17 18 statement_expression_list 19 : statement_expression (',' statement_expression)* 20 ;
for_initializer,如果存在的话,由一个或者的local_variable_declaration(本地变量声明)或列表statement_expression S(表达式语句由逗号分隔)。由for_initializer声明的局部变量的范围从变量的local_variable_declarator开始,并延伸到嵌入语句的末尾。范围包括for_condition和for_iterator。
的for_condition,如果存在的话,必须是一个逻辑表达式(布尔表达式)。
的for_iterator,如果存在,包含的列表的statement_expression S(表达式语句由逗号分隔)。
for语句执行如下:
- 如果存在for_initializer,则变量初始值设定项或语句表达式按其写入顺序执行。此步骤仅执行一次。
- 如果存在for_condition,则对其进行评估。
- 如果for_condition不存在或者评估结果
true
,则控制权转移到嵌入语句。当控件到达嵌入语句的结束点时(可能来自语句的执行continue
),for_iterator的表达式(如果有的话)按顺序计算,然后执行另一次迭代,从评估for_condition开始。上面的步骤。 - 如果存在for_condition并且评估结果
false
,则控制转移到for
语句的结束点。
在语句的嵌入语句中for
,break
语句(break语句)可用于将控制转移到for
语句的结束点(从而结束嵌入语句的迭代),并且continue
语句(continue语句)可用于将控制转移到嵌入语句的结束点(从而执行for_iterator并执行for
语句的另一次迭代,从for_condition开始)。
for
如果满足下列条件之一,则可以访问语句的嵌入语句:
- 该
for
语句是可访问的,并且不存在for_condition。 - 该
for
语句是可访问的,并且存在for_condition并且没有常量值false
。
for
如果至少满足下列条件之一,则可以访问语句的结束点:
- 该
for
语句包含break
退出for
语句的可访问语句。 - 该
for
语句是可访问的,并且存在for_condition并且没有常量值true
。
foreach语句
该foreach
语句枚举集合的元素,为集合的每个元素执行嵌入式语句。
1 foreach_statement 2 : 'foreach' '(' local_variable_type identifier 'in' expression ')' embedded_statement 3 ;
的类型和标识符一个的foreach
声明声明迭代变量的声明。如果var
标识符作为local_variable_type给出,并且没有命名的类型var
在范围内,则迭代变量被称为隐式类型的迭代变量,并且其类型被视为foreach
语句的元素类型,如下所述。迭代变量对应于只读局部变量,其范围扩展到嵌入语句。执行期间foreach
声明,迭代变量表示当前正在执行迭代的集合元素。如果嵌入语句试图修改迭代变量(通过赋值或发生编译时间错误++
和--
操作员)或通过迭代变量作为ref
或out
参数。
在下文中,为了简洁,IEnumerable
,IEnumerator
,IEnumerable<T>
和IEnumerator<T>
指的是命名空间的相应类型System.Collections
和System.Collections.Generic
。
foreach语句的编译时处理首先确定表达式的集合类型,枚举器类型和元素类型。该决定如下:
- 如果类型
X
的表达是一个数组类型,那么存在来自隐式引用转换X
到IEnumerable
接口(因为System.Array
实现该接口)。的集合类型是IEnumerable
接口,该枚举类型是IEnumerator
接口和元素类型是阵列类型的元素类型X
。 - 如果类型
X
的表达是dynamic
则存在从隐式转换表达式的IEnumerable
接口(隐式动态转换)。该集合类型是IEnumerable
接口和枚举类型是IEnumerator
接口。如果var
标识符是local_variable_type,那么元素类型是dynamic
,否则它是object
。 -
否则,确定类型
X
是否具有适当的GetEnumerator
方法:- 对
X
具有标识符GetEnumerator
且没有类型参数的类型执行成员查找。如果成员查找不产生匹配,或者产生歧义,或产生不是方法组的匹配,请检查可枚举接口,如下所述。如果成员查找产生除方法组或不匹配之外的任何内容,建议发出警告。 - 使用生成的方法组和空参数列表执行重载解析。如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,请检查可枚举的接口,如下所述。如果重载决策产生除明确的公共实例方法或没有适用的方法之外的任何内容,建议发出警告。
- 如果返回类型
E
的的GetEnumerator
方法不是类,结构或接口类型,则产生一个错误,并且不采取进一步的步骤。 E
使用标识符执行成员查找Current
,不使用类型参数。如果成员查找不产生匹配,则结果是错误,或者结果是除允许读取的公共实例属性之外的任何内容,产生错误并且不执行进一步的步骤。E
使用标识符执行成员查找MoveNext
,不使用类型参数。如果成员查找不产生匹配,则结果是错误,或者结果是除方法组之外的任何内容,产生错误并且不执行进一步的步骤。- 使用空参数列表对方法组执行重载分辨率。如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,或者其返回类型不是
bool
,则产生错误并且不采取进一步的步骤。 - 的集合类型是
X
,该枚举类型是E
,和元素类型是类型Current
属性。
- 对
-
否则,检查可枚举的接口:
- 如果所有的类型中
Ti
对于其存在从隐式转换X
到IEnumerable<Ti>
,有一种独特类型T
,使得T
不dynamic
和所有其他Ti
有从隐式转换IEnumerable<T>
到IEnumerable<Ti>
,则该集合类型是接口IEnumerable<T>
,该枚举类型是接口IEnumerator<T>
,元素类型是T
。 - 否则,如果存在多个这样的类型
T
,则产生错误并且不采取进一步的步骤。 - 否则,如果存在的隐式转换从
X
到System.Collections.IEnumerable
接口,则集合类型是这样的接口,所述枚举类型是接口System.Collections.IEnumerator
,并且元素类型是object
。 - 否则,将产生错误,并且不会采取进一步的步骤。
- 如果所有的类型中
上述步骤如果成功,则明确地生成集合类型C
,枚举器类型E
和元素类型T
。表格的foreach声明
foreach (V v in x) embedded_statement
然后扩展到:
1 { 2 E e = ((C)(x)).GetEnumerator(); 3 try { 4 while (e.MoveNext()) { 5 V v = (V)(T)e.Current; 6 embedded_statement 7 } 8 } 9 finally { 10 ... // Dispose e 11 } 12 }
变量e
对表达式x
或嵌入语句或程序的任何其他源代码不可见或不可访问。该变量v
在嵌入语句中是只读的。如果没有从(元素类型)到(foreach语句中的local_variable_type)的显式转换(显式转换),则会产生错误,并且不会采取进一步的步骤。如果有值,则在运行时抛出a 。T
V
x
null
System.NullReferenceException
允许实现以不同方式实现给定的foreach语句,例如出于性能原因,只要行为与上述扩展一致即可。
v
while循环内部的位置对于embedded_statement中发生的任何匿名函数如何捕获它非常重要。
例如:
1 int[] values = { 7, 9, 13 }; 2 Action f = null; 3 4 foreach (var value in values) 5 { 6 if (f == null) f = () => Console.WriteLine("First value: " + value); 7 } 8 9 f();
如果v
在while循环之外声明,它将在所有迭代之间共享,并且它在for循环之后的值将是最终值13
,这f
将是打印的调用。相反,因为每次迭代都有自己的变量v
,f
在第一次迭代中捕获的变量将继续保持值7
,即将打印的值。(注意:早期版本的C#v
在while循环之外声明。)
finally块的主体按照以下步骤构造:
-
如果存在从一个隐式转换
E
到System.IDisposable
接口,则-
如果
E
是非可空值类型,则finally子句将扩展为语义等效于:
-
-
-
1 finally { 2 ((System.IDisposable)e).Dispose(); 3 }
- 否则,finally子句将扩展为语义等效于:
-
1 finally { 2 if (e != null) ((System.IDisposable)e).Dispose(); 3 }
除了if
E
是值类型,或实例化为值类型的类型参数之外,to的强制e
转换System.IDisposable
不会导致装箱发生。
-
-
否则,如果
E
是密封类型,则finally子句将扩展为空块:
1 finally { 2 }
-
否则,finally子句扩展为:
1 finally { 2 System.IDisposable d = e as System.IDisposable; 3 if (d != null) d.Dispose(); 4 }
d
任何用户代码都看不到或访问本地变量。特别是,它不会与其范围包含finally块的任何其他变量冲突。
foreach
遍历数组元素的顺序如下:对于单维数组,元素以递增的索引顺序遍历,从索引开始,以索引0
结束Length - 1
。对于多维数组,遍历元素,使得最右边的维度的索引首先增加,然后是下一个左维度,依此类推到左边。
以下示例按元素顺序打印出二维数组中的每个值:
1 using System; 2 3 class Test 4 { 5 static void Main() { 6 double[,] values = { 7 {1.2, 2.3, 3.4, 4.5}, 8 {5.6, 6.7, 7.8, 8.9} 9 }; 10 11 foreach (double elementValue in values) 12 Console.Write("{0} ", elementValue); 13 14 Console.WriteLine(); 15 } 16 }
产生的产出如下:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9
在这个例子中
1 int[] numbers = { 1, 3, 5, 7, 9 }; 2 foreach (var n in numbers) Console.WriteLine(n);
n
推断的类型是int
,元素类型numbers
。
跳转语句
跳转语句无条件地转移控制。
1 jump_statement 2 : break_statement 3 | continue_statement 4 | goto_statement 5 | return_statement 6 | throw_statement 7 ;
跳转语句转移控制的位置称为跳转语句的目标。
当一个块内发生跳转语句,并且该跳转语句的目标位于该块之外时,跳转语句被称为退出该块。虽然跳转语句可以将控制转移出块,但它永远不会将控制转移到块中。
由于存在干预try
语句,跳转语句的执行变得复杂。在没有这样的try
语句的情况下,跳转语句无条件地将控制从跳转语句转移到其目标。在存在这样的干预try
陈述的情况下,执行更加复杂。如果跳转语句退出try
具有关联finally
块的一个或多个块,则控制最初被转移到finally
最内层try
语句的块。当控制到达finally
块的结束点时,控制转移到finally
下一个封闭try
语句的块。重复该过程,直到执行finally
了所有干预try
语句的块。
在这个例子中
1 using System; 2 3 class Test 4 { 5 static void Main() { 6 while (true) { 7 try { 8 try { 9 Console.WriteLine("Before break"); 10 break; 11 } 12 finally { 13 Console.WriteLine("Innermost finally block"); 14 } 15 } 16 finally { 17 Console.WriteLine("Outermost finally block"); 18 } 19 } 20 Console.WriteLine("After break"); 21 } 22 }
在将控制转移到跳转语句的目标之前,执行finally
与两个try
语句关联的块。
产生的产出如下:
1 Before break 2 Innermost finally block 3 Outermost finally block 4 After break
break
语句
该break
语句退出直接封闭switch
,while
,do
,for
,或foreach
语句。
1 break_statement 2 : 'break' ';' 3 ;
一个目标break
的语句是最近的封闭的结束点switch
,while
,do
,for
,或foreach
语句。如果break
语句不是由一个封闭的switch
,while
,do
,for
,或foreach
语句,将发生编译时错误。
当多个switch
,while
,do
,for
,或foreach
语句相互嵌套,一个break
声明仅适用于最里面的语句。要跨多个嵌套级别传输控制,必须使用goto
语句(goto语句)。
一个break
语句不能退出一个finally
块(try语句)。当一个break
语句出现在一个finally
块中时,该break
语句的目标必须在同一个finally
块内; 否则,发生编译时错误。
一个break
语句的执行过程如下:
- 如果
break
语句退出try
具有关联finally
块的一个或多个块,则控制最初被转移到finally
最内层try
语句的块。当控制到达finally
块的结束点时,控制转移到finally
下一个封闭try
语句的块。重复该过程,直到执行finally
了所有干预try
语句的块。 - 控制权转移到
break
声明的目标。
因为break
语句无条件地将控制权转移到其他地方,所以break
语句的终点永远不可达。
continue
语句
该continue
语句开始直接封闭的一个新的迭代while
,do
,for
,或foreach
语句。
1 continue_statement 2 : 'continue' ';' 3 ;
一个目标continue
的语句是最近的封闭的嵌入语句的结束点while
,do
,for
,或foreach
语句。如果continue
语句不是由一个封闭的while
,do
,for
,或foreach
语句,将发生编译时错误。
当多个while
,do
,for
,或foreach
语句相互嵌套,一个continue
声明仅适用于最里面的语句。要跨多个嵌套级别传输控制,必须使用goto
语句(goto语句)。
一个continue
语句不能退出一个finally
块(try语句)。当一个continue
语句出现在一个finally
块中时,该continue
语句的目标必须在同一个finally
块内; 否则会发生编译时错误。
一个continue
语句的执行过程如下:
- 如果
continue
语句退出try
具有关联finally
块的一个或多个块,则控制最初被转移到finally
最内层try
语句的块。当控制到达finally
块的结束点时,控制转移到finally
下一个封闭try
语句的块。重复该过程,直到执行finally
了所有干预try
语句的块。 - 控制权转移到
continue
声明的目标。
因为continue
语句无条件地将控制权转移到其他地方,所以continue
语句的终点永远不可达。
goto语句
该goto
语句将控制转移到由标签标记的语句。
1 goto_statement 2 : 'goto' identifier ';' 3 | 'goto' 'case' constant_expression ';' 4 | 'goto' 'default' ';' 5 ;
goto
标识符语句的目标是带有给定标签的带标签语句。如果当前函数成员中不存在具有给定名称的标签,或者该goto
语句不在标签的范围内,则会发生编译时错误。此规则允许使用goto
语句将控制权移出嵌套作用域,但不能转移到嵌套作用域中。在这个例子中
1 using System; 2 3 class Test 4 { 5 static void Main(string[] args) { 6 string[,] table = { 7 {"Red", "Blue", "Green"}, 8 {"Monday", "Wednesday", "Friday"} 9 }; 10 11 foreach (string str in args) { 12 int row, colm; 13 for (row = 0; row <= 1; ++row) 14 for (colm = 0; colm <= 2; ++colm) 15 if (str == table[row,colm]) 16 goto done; 17 18 Console.WriteLine("{0} not found", str); 19 continue; 20 done: 21 Console.WriteLine("Found {0} at [{1}][{2}]", str, row, colm); 22 } 23 } 24 }
一个goto
语句是用来传输控制出嵌套范围。
goto case
语句的目标是直接封闭switch
语句(switch语句)中的语句列表,其中包含case
具有给定常量值的标签。如果goto case
语句没有被switch
语句包含,如果constant_expression不能隐式转换(隐式转换)到最近的封闭switch
语句的控制类型,或者如果最近的封闭switch
语句不包含case
具有给定常量值的标签,则编译发生时间错误。
goto default
语句的目标是直接包含switch
语句(switch语句)中的语句列表,其中包含default
标签。如果goto default
语句未包含在switch
语句中,或者最近的封闭switch
语句不包含default
标签,则会发生编译时错误。
一个goto
语句不能退出一个finally
块(try语句)。当goto
语句在finally
块中发生时,goto
语句的目标必须在同一个finally
块内,否则会发生编译时错误。
一个goto
语句的执行过程如下:
- 如果
goto
语句退出try
具有关联finally
块的一个或多个块,则控制最初被转移到finally
最内层try
语句的块。当控制到达finally
块的结束点时,控制转移到finally
下一个封闭try
语句的块。重复该过程,直到执行finally
了所有干预try
语句的块。 - 控制权转移到
goto
声明的目标。
因为goto
语句无条件地将控制权转移到其他地方,所以goto
语句的终点永远不可达。
return
语句
该return
语句将控制权返回给return
语句出现的函数的当前调用者。
1 return_statement 2 : 'return' expression? ';' 3 ;
return
不带表达式语句可用于仅在一个功能部件,其不计算的值,即,与结果类型的方法(方法体)void
,所述set
属性或索引的访问,所述add
与remove
事件的存取器,一个实例构造函数,静态构造函数或析构函数。
return
具有表达式的语句只能在计算值的函数成员中使用,即具有非void结果类型的方法,get
属性或索引器的访问者或用户定义的运算符。隐式转换(隐式转换)必须存在,从表达式的类型到包含函数成员的返回类型。
返回语句也可以在匿名函数表达式(匿名函数表达式)的主体中使用,并参与确定这些函数存在哪些转换。
return
语句出现在finally
块(try语句)中是编译时错误。
一个return
语句的执行过程如下:
- 如果
return
语句指定了表达式,则计算表达式,并通过隐式转换将结果值转换为包含函数的返回类型。转换的结果成为函数产生的结果值。 - 如果
return
语句被一个或多个try
或catch
带有关联finally
块的块包围,则控制最初被转移到finally
最内层try
语句的块。当控制到达finally
块的结束点时,控制转移到finally
下一个封闭try
语句的块。重复此过程,直到执行finally
了所有封闭try
语句的块。 - 如果包含函数不是异步函数,则控制将返回包含函数的调用者以及结果值(如果有)。
- 如果包含函数是异步函数,则控制将返回到当前调用者,并且结果值(如果有)将记录在返回任务中,如(枚举器接口)中所述。
因为return
语句无条件地将控制权转移到其他地方,所以return
语句的终点永远不可达。
throw
语句
该throw
语句抛出异常。
1 throw_statement 2 : 'throw' expression? ';' 3 ;
throw
带有表达式的语句会抛出通过计算表达式生成的值。表达式必须表示类类型的值,该类类型System.Exception
的类类型派生自System.Exception
或具有System.Exception
(或其子类)作为其有效基类的类型参数类型。如果表达式的评估产生null
,System.NullReferenceException
则抛出a 。
throw
没有表达式的语句只能在catch
块中使用,在这种情况下,该语句会重新抛出该catch
块当前正在处理的异常。
因为throw
语句无条件地将控制权转移到其他地方,所以throw
语句的终点永远不可达。
抛出异常时,控制权转移到可以处理异常catch
的封闭try
语句中的第一个子句。从抛出异常点到将控制转移到合适的异常处理程序的过程发生的过程称为异常传播。异常的传播包括重复评估以下步骤,直到catch
找到与异常匹配的子句。在本说明书中,抛出点最初是抛出异常的位置。
-
在当前函数成员中,
try
将检查包含抛出点的每个语句。对于每个语句S
,从最内层try
语句开始到以最外层try
语句结束,将评估以下步骤:-
如果
try
块S
包含抛出点并且如果S有一个或多个catch
子句,则catch
根据出现的顺序检查子句,以根据try语句一节中指定的规则找到异常的合适处理程序。如果找到匹配catch
子句,则通过将控制转移到该catch
子句的块来完成异常传播。 -
否则,如果
try
块或catch
块S
包围抛出点并且如果S
有finally
块,则控制转移到finally
块。如果finally
块抛出另一个异常,则终止当前异常的处理。否则,当控制到达finally
块的结束点时,继续处理当前异常。
-
-
如果当前函数调用中未找到异常处理程序,则终止函数调用,并发生以下任一情况:
-
如果当前函数是非异步的,则对函数的调用者重复上述步骤,其抛出点对应于调用函数成员的语句。
-
如果当前函数是异步和任务返回,则异常将记录在返回任务中,该任务将进入故障或取消状态,如枚举器接口中所述。
-
如果当前函数是async和void-returns,则会按照Enumerable接口中的描述通知当前线程的同步上下文。
-
-
如果异常处理终止当前线程中的所有函数成员调用,指示该线程没有该异常的处理程序,则该线程本身终止。这种终止的影响是实现定义的。
try语句
该try
语句提供了一种机制,用于捕获在执行块期间发生的异常。此外,该try
语句还提供了指定在控制离开try
语句时始终执行的代码块的功能。
1 try_statement 2 : 'try' block catch_clause+ 3 | 'try' block finally_clause 4 | 'try' block catch_clause+ finally_clause 5 ; 6 7 catch_clause 8 : 'catch' exception_specifier? exception_filter? block 9 ; 10 11 exception_specifier 12 : '(' type identifier? ')' 13 ; 14 15 exception_filter 16 : 'when' '(' expression ')' 17 ; 18 19 finally_clause 20 : 'finally' block 21 ;
有三种可能的try
陈述形式:
- 一个
try
块后跟一个或多个catch
块。 - 一个
try
块然后是一个finally
块。 - 一个
try
块后跟一个或多个catch
块,后跟一个finally
块。
当catch
子句指定exception_specifier时,类型必须是System.Exception
,派生自System.Exception
的类型System.Exception
或具有(或其子类)作为其有效基类的类型参数类型。
当catch
子句指定两者exception_specifier与标识符,一个异常变量给定名称和类型的声明。异常变量对应于局部变量,其范围扩展到catch
子句。在执行exception_filter和block期间,异常变量表示当前正在处理的异常。出于明确赋值检查的目的,异常变量在其整个范围内被认为是明确赋值的。
除非catch
子句包含异常变量名称,否则无法访问过滤器和catch
块中的异常对象。
一个catch
不指定条款exception_specifier被称为一般catch
条款。
一些编程语言可能支持不能表示为从中派生的对象的System.Exception
异常,尽管这些异常永远不会由C#代码生成。一般catch
条款可用于捕获此类例外。因此,一般catch
子句在语义上与指定类型的子句不同System.Exception
,因为前者也可以捕获来自其他语言的异常。
为了找到异常的处理程序,catch
以词法顺序检查子句。如果catch
子句指定了类型但没有异常过滤器,则catch
同一try
语句中的后一个子句的编译时错误是指定与该类型相同或派生的类型。如果catch
子句指定没有类型且没有指定过滤器,则它必须是该语句的最后一个catch
子句try
。
在catch
块中,没有表达式的throw
语句(throw语句)可用于重新抛出catch
块捕获的异常。对异常变量的赋值不会改变重新抛出的异常。
在这个例子中
1 using System; 2 3 class Test 4 { 5 static void F() { 6 try { 7 G(); 8 } 9 catch (Exception e) { 10 Console.WriteLine("Exception in F: " + e.Message); 11 e = new Exception("F"); 12 throw; // re-throw 13 } 14 } 15 16 static void G() { 17 throw new Exception("G"); 18 } 19 20 static void Main() { 21 try { 22 F(); 23 } 24 catch (Exception e) { 25 Console.WriteLine("Exception in Main: " + e.Message); 26 } 27 } 28 }
该方法F
捕获异常,将一些诊断信息写入控制台,更改异常变量,并重新抛出异常。重新抛出的异常是原始异常,因此产生的输出是:
1 Exception in F: G 2 Exception in Main: G
这是一个编译时错误break
,continue
或者goto
语句将控制转移出的finally
块。当a break
,continue
或goto
语句出现在finally
块中时,语句的目标必须位于同一个finally
块中,否则会发生编译时错误。
return
在finally
块中发生语句是编译时错误。
一个try
语句的执行过程如下:
- 控制转移到
try
块。 -
当控件到达
try
块的结束点时:- 如果
try
语句有finally
块,finally
则执行该块。 - 控制权转移到
try
声明的终点。
- 如果
-
如果
try
在执行try
块期间将异常传播到语句:- 这些
catch
子句(如果有的话)按照外观的顺序进行检查,以便为异常找到合适的处理程序。如果catch
子句未指定类型,或指定异常类型或异常类型的基类型:- 如果
catch
子句声明了异常变量,则将异常对象分配给异常变量。 - 如果
catch
子句声明了异常过滤器,则会评估过滤器。如果计算结果为false
,则catch子句不匹配,并且搜索将继续通过catch
适当处理程序的任何后续子句。 - 否则,该
catch
子句被视为匹配,并且控制被转移到匹配catch
块。 - 当控件到达
catch
块的结束点时:- 如果
try
语句有finally
块,finally
则执行该块。 - 控制权转移到
try
声明的终点。
- 如果
- 如果
try
在执行catch
块期间将异常传播到语句:- 如果
try
语句有finally
块,finally
则执行该块。 - 该异常将传播到下一个封闭
try
语句。
- 如果
- 如果
- 如果
try
语句没有catch
子句或没有catch
子句匹配异常:- 如果
try
语句有finally
块,finally
则执行该块。 - 该异常将传播到下一个封闭
try
语句。
- 如果
- 这些
一的语句finally
在控制离开块总是被执行try
的语句。这是真实的,控制传送是否发生正常的执行结果,作为执行的结果break
,continue
,goto
,或return
语句,或作为关于传播异常出的结果try
说明。
如果在执行finally
块期间抛出异常,并且未在同一finally块中捕获,则异常将传播到下一个封闭try
语句。如果另一个异常处于传播过程中,则该异常将丢失。传播异常的过程将在throw
语句描述(throw语句)中进一步讨论。
在try
一个块try
语句是可到达如果try
语句是可到达。
一个catch
一个的块try
语句是可到达如果try
语句是可到达。
在finally
一个块try
语句是可到达如果try
语句是可到达。
try
如果满足以下两个条件,则可以访问语句的结束点:
- 所述的结束点
try
块是可到达的或者至少一个的结束点catch
块是可到达的。 - 如果
finally
存在finally
块,则可以访问块的结束点。
checked
和unchecked
语句
checked
和unchecked
语句用于控制溢出检查上下文对整型算术运算和转换。
1 checked_statement 2 : 'checked' block 3 ; 4 5 unchecked_statement 6 : 'unchecked' block 7 ;
checked
语句使得块中的所有表达式都在已检查的上下文中进行计算,并且该unchecked
语句会导致在未检查的上下文中计算块中的所有表达式。
该checked
和unchecked
语句完全等效于checked
与unchecked
运营商(checked和unchecked经营者,除了他们在块而不是表达操作)。
lock
语句
该lock
语句获取给定对象的互斥锁,执行语句,然后释放锁。
1 lock_statement 2 : 'lock' '(' expression ')' embedded_statement 3 ;
lock
语句的表达式必须表示已知为reference_type的类型的值。没有为语句的表达式执行隐式装箱转换(Boxing conversions),因此表达式的lock
编译时错误表示value_type的值。
一个lock
形式的声明
lock (x) ...
其中x
是reference_type的表达式,恰好相当于
1 bool __lockWasTaken = false; 2 try { 3 System.Threading.Monitor.Enter(x, ref __lockWasTaken); 4 ... 5 } 6 finally { 7 if (__lockWasTaken) System.Threading.Monitor.Exit(x); 8 }
除了x
仅评估一次。
在保持互斥锁定的同时,在同一执行线程中执行的代码也可以获取并释放锁定。但是,在锁定释放之前,阻止在其他线程中执行的代码获取锁定。
System.Type
建议不要锁定对象以同步对静态数据的访问。其他代码可能会锁定相同的类型,这可能导致死锁。更好的方法是通过锁定私有静态对象来同步对静态数据的访问。例如:
1 class Cache 2 { 3 private static readonly object synchronizationObject = new object(); 4 5 public static void Add(object x) { 6 lock (Cache.synchronizationObject) { 7 ... 8 } 9 } 10 11 public static void Remove(object x) { 12 lock (Cache.synchronizationObject) { 13 ... 14 } 15 } 16 }
using
语句
该using
语句获取一个或多个资源,执行语句,然后处置该资源。
1 using_statement 2 : 'using' '(' resource_acquisition ')' embedded_statement 3 ; 4 5 resource_acquisition 6 : local_variable_declaration 7 | expression 8 ;
资源是一个类或实现结构System.IDisposable
,它包括一个名为单个参数方法Dispose
。使用资源的代码可以调用Dispose
以指示不再需要该资源。如果Dispose
未调用,则最终会因垃圾收集而自动处理。
如果形式resource_acquisition是local_variable_declaration则类型local_variable_declaration必须是dynamic
或可以隐式转换为类型System.IDisposable
。如果resource_acquisition的形式是表达式,那么此表达式必须可以隐式转换为System.IDisposable
。
在resource_acquisition中声明的局部变量是只读的,并且必须包含初始化程序。如果嵌入语句试图修改这些局部变量(通过赋值或发生编译时间错误++
和--
操作员),把它们的地址,或者将它们传递作为ref
或out
参数。
一个using
语句被翻译成三个部分:获取,使用和处置。资源的使用隐式包含在try
包含finally
子句的语句中。该finally
子句处理资源。如果null
获取了资源,则不会进行任何调用Dispose
,也不会抛出任何异常。如果资源属于类型dynamic
,则在获取期间通过隐式动态转换(隐式动态转换)动态转换IDisposable
,以确保在使用和处置之前转换成功。
一个using
形式的声明
using (ResourceType resource = expression) statement
对应于三种可能的扩展之一。何时ResourceType
是非可空值类型,扩展为
1 { 2 ResourceType resource = expression; 3 try { 4 statement; 5 } 6 finally { 7 ((IDisposable)resource).Dispose(); 8 } 9 }
否则,当ResourceType
是可以为空的值类型或除了以外的引用类型时dynamic
,扩展为
1 { 2 ResourceType resource = expression; 3 try { 4 statement; 5 } 6 finally { 7 if (resource != null) ((IDisposable)resource).Dispose(); 8 } 9 }
否则,当ResourceType
是dynamic
,扩展
1 { 2 ResourceType resource = expression; 3 IDisposable d = (IDisposable)resource; 4 try { 5 statement; 6 } 7 finally { 8 if (d != null) d.Dispose(); 9 } 10 }
在任一扩展中,resource
变量在嵌入语句中是只读的,并且d
变量在嵌入语句中是不可访问的,并且对嵌入语句是不可见的。
允许实现以不同方式实现给定的using语句,例如出于性能原因,只要行为与上述扩展一致即可。
一个using
形式的声明
using (expression) statement
有三个可能的扩展。在这种情况下ResourceType
隐含的是编译时类型expression
,如果有的话。否则接口IDisposable
本身用作ResourceType
。该resource
变量在嵌入语句中不可访问且不可访问。
当resource_acquisition采用local_variable_declaration的形式时,可以获取给定类型的多个资源。一个using
形式的声明
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement
恰好等同于一系列嵌套using
语句:
1 using (ResourceType r1 = e1) 2 using (ResourceType r2 = e2) 3 ... 4 using (ResourceType rN = eN) 5 statement
下面的示例创建一个名为的文件,log.txt
并将两行文本写入该文件。然后,该示例打开相同的文件进行读取,并将包含的文本行复制到控制台。
1 using System; 2 using System.IO; 3 4 class Test 5 { 6 static void Main() { 7 using (TextWriter w = File.CreateText("log.txt")) { 8 w.WriteLine("This is line one"); 9 w.WriteLine("This is line two"); 10 } 11 12 using (TextReader r = File.OpenText("log.txt")) { 13 string s; 14 while ((s = r.ReadLine()) != null) { 15 Console.WriteLine(s); 16 } 17 18 } 19 } 20 }
由于TextWriter
和TextReader
类实现了IDisposable
接口,因此该示例可以使用using
语句来确保在写入或读取操作之后正确关闭基础文件。
yield
语句
yield
语句在迭代器块(Blocks)中使用,以产生迭代器的枚举器对象(Enumerator对象)或可枚举对象(Enumerable对象)的值,或表示迭代结束。
1 yield_statement 2 : 'yield' 'return' expression ';' 3 | 'yield' 'break' ';' 4 ;
yield
不是保留字; 它有之前用过的,只有当特殊意义return
或break
关键字。在其他情况下,yield
可以用作标识符。
yield
语句的出现位置有几个限制,如下所述。
yield
语句(任一形式)出现在method_body,operator_body或accessor_body之外是编译时错误yield
语句(任一形式)出现在匿名函数中是编译时错误。yield
语句(任一形式)出现在语句的finally
子句中是编译时错误try
。yield return
语句出现在try
包含任何catch
子句的语句中的任何位置都是编译时错误。
以下示例显示了yield
语句的一些有效和无效用法。
1 delegate IEnumerable<int> D(); 2 3 IEnumerator<int> GetEnumerator() { 4 try { 5 yield return 1; // Ok 6 yield break; // Ok 7 } 8 finally { 9 yield return 2; // Error, yield in finally 10 yield break; // Error, yield in finally 11 } 12 13 try { 14 yield return 3; // Error, yield return in try...catch 15 yield break; // Ok 16 } 17 catch { 18 yield return 4; // Error, yield return in try...catch 19 yield break; // Ok 20 } 21 22 D d = delegate { 23 yield return 5; // Error, yield in an anonymous function 24 }; 25 } 26 27 int MyMethod() { 28 yield return 1; // Error, wrong return type for an iterator block 29 }
隐式转换(隐式转换)必须存在于yield return
语句中表达式的类型到迭代器的yield类型(Yield类型)。
一个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
语句的终点永远不可达。