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该语句位于块内trycatch包含其终结点不可访问的 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.4var 导致三个类别之间的语法歧义性,如下所示:

  • 如果没有在范围内命名var的类型,并且输入与implicitly_typed_local_variable_declaration匹配,则选择它;
  • 否则,如果命名 var 的类型在作用域中,则 implicitly_typed_local_variable_declaration 不被视为可能的匹配项。

在local_variable_declaration中,每个变量由声明符引入,声明符是隐式类型化、显式类型和引用局部变量的implicitly_typed_local_variable_declaratorexplicitly_typed_local_variable_declaratorref_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_initializerfor_conditionfor_iteratorembedded_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。 第一个替代项声明具有表达式初始值的变量;其类型为TT?不可为 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_listparameter_list包含属性的编译时错误。 如果在不安全的上下文(§23.2)中声明了本地函数,则即使本地函数声明不包含 unsafe 修饰符,本地函数也可能包含不安全的代码。

本地函数在块范围内声明。 非静态局部函数可以从封闭范围捕获变量,而静态局部函数不应捕获变量(因此它无权将局部变量、参数、非静态本地函数或 this)。 如果捕获的变量由非静态本地函数的主体读取,但在每次调用函数之前未明确分配,则这是编译时错误。 编译器应确定在返回时明确分配的变量(§9.4.4.33)。

当类型为结构类型 this 时,它是本地函数正文访问 this的编译时错误。 无论访问是显式(如 in this.x)还是隐式(如 x 结构实例成员所在的位置 x )都是如此。 此规则仅禁止此类访问,并且不会影响成员查找是否导致结构的成员。

对于本地函数的正文,它是一个编译时错误,它包含一个 goto 语句、一个 break 语句或一个 continue 目标在局部函数正文之外。

注意:在 §12.19.3,上述匿名函数的规则thisgoto镜像规则。 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引用或局部变量、参数或非静态本地函数中引用thisbase实例成员。 但是,表达式中 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 + yx == 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_blockswitch_block由零个或多个switch_sections 组成,括在大括号中。 每个 switch_section 由一个或多个 switch_label组成,后跟 statement_list§13.3.2)。 每个包含caseswitch_label都有一个关联的模式(§11),测试 switch 表达式的值。 如果 存在case_guard ,则其表达式应隐式转换为该类型 bool ,并将该表达式计算为满足该情况的附加条件。

语句的switch调控类型由 switch 表达式建立。

  • 如果 switch 表达式的类型为 sbytebyte、、shortushortintuintlongulongboolstringchar或enum_type,或者它是对应于其中一种类型的可为 null 值类型,则为语句的switch调控类型。
  • 否则,如果恰好存在一个用户定义的隐式转换,即从 switch 表达式的类型转换为下列可能的治理类型之一:sbyteintuintushortulonglongbyteshort、、、charstring与其中一种类型对应的可为 null 的值类型,则转换的类型是语句的switch调控类型。
  • 否则,语句的 switch 治理类型是 switch 表达式的类型。 如果不存在此类类型,则为错误。

语句中最多可以有一个defaultswitch标签。

如果任何开关标签的模式不适用于输入表达式的类型(§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 终点。

注意:未定义在运行时匹配模式的顺序。 允许编译器(但不需要)按顺序匹配模式,并重复使用已匹配模式的结果来计算其他模式匹配的结果。 不过,编译器需要确定与表达式匹配的词法第一个模式,并且 guard 子句不存在或计算结果为 trueend 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 casegoto 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 casegoto default 语句结尾,但允许呈现语句列表的终点的任何构造不可访问。 例如, while 已知由布尔表达式 true 控制的语句永远不会到达其终点。 同样,或throwreturn语句始终将控制权转移到其他位置,并且永远不会到达其终点。 因此,以下示例有效:

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 a switch statement is string or a nullable value type, the value null is permitted as a case label constant.

switch_blockstatement_list可能包含声明语句(§13.6)。 在交换机块中声明的局部变量或常量的范围是开关块。

如果以下至少一个为 true,则可访问开关标签:

  • switch 表达式是常量值,任一
    • 标签是一个 case 模式,其模式 将匹配 该值(§11.2.1),并且标签的防护要么不存在,要么不是值为 false 的常量表达式;或者
    • 它是一个标签,并且没有 switch 节包含一个 default 大小写标签,其模式与该值匹配,并且其保护不存在或具有值 true 的常量表达式。
  • switch 表达式不是常量值,也可以
    • 标签是一个 case 没有防护或具有一个防护,其值不是常量 false;或者
    • 它是一个 default 标签和
      • 开关语句中出现的一组模式,这些模式没有防护或具有值为常量 true 的防护,对于开关控制类型来说并不 详尽§11.4);或者
      • 开关控制类型是可为 null 的类型,并且开关语句中出现的模式集没有防护或具有值为常量 true 的防护,不包含与值 null匹配的模式。
  • 开关标签由可访问 goto casegoto 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_initializerfor_conditionfor_iteratorembedded_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同时包含这两者refreadonly则迭代变量表示被视为只读的变量。 否则,如果foreach_statement不包含refreadonly,则迭代变量表示应可写的变量。

迭代变量对应于一个局部变量,该局部变量的范围扩展了嵌入语句。 在执行 foreach 语句期间,迭代变量表示当前正在执行迭代的集合元素。 如果迭代变量表示只读变量,则当嵌入语句尝试修改它(通过赋值或运算符--)或将其作为引用或++输出参数传递时,会发生编译时错误。

在下面的命名空间中,为简洁起见, IEnumerableIEnumerable<T> IEnumeratorIEnumerator<T>引用命名空间System.CollectionsSystem.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>,迭代类型为TIEnumerable<Tᵢ>X
    • 否则,如果有多个此类类型 T,则会生成错误,并且不执行进一步的步骤。
    • 否则,如果从接口隐式转换XSystem.Collections.IEnumerable,则集合类型为此接口,枚举器类型为接口System.Collections.IEnumerator,迭代类型为object接口。
    • 否则,将生成错误,并且不采取进一步的步骤。

上述步骤(如果成功)明确生成集合类型 C、枚举器类型和 E 迭代类型 Tref Tref readonly Tforeach窗体的语句

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变量在嵌入式语句中是只读的。 如果没有从(迭代类型)到(语句中的foreachlocal_variable_typeT的显式转换(§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(语句中的foreachlocal_variable_typeT的标识转换(§10.2.2),则会生成错误,并且不执行进一步的步骤。

foreach表单foreach (ref readonly V v in x) «embedded_statement»的语句具有类似的等效形式,但引用变量v位于ref readonly嵌入语句中,因此无法重新分配或重新分配引用。

注意:如果 x 具有值 null,则会在运行时引发 a System.NullReferenceExceptionend 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。 (请注意,在循环外部声明 vwhile 早期版本的 C# 。

end 示例

块的 finally 主体根据以下步骤构造:

  • 如果存在从ESystem.IDisposable接口进行隐式转换,则

    • 如果 E 为不可为 null 的值类型,则子 finally 句将扩展为语义等效项:

      finally
      {
          ((System.IDisposable)e).Dispose();
      }
      
    • 否则,子 finally 句将扩展为语义等效项:

      finally
      {
          System.IDisposable d = e as System.IDisposable;
          if (d != null)
          {
              d.Dispose();
          }
      }
      

      E除非是值类型,或者实例化为值类型的类型参数,则转换eSystem.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);
}

推断为nint迭代类型的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 块的块,则控件最初将传输到 finallytry 内部语句的块。 当控件到达块的 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封闭、whiledoforforeach语句。

break_statement
    : 'break' ';'
    ;

语句的目标break是最接近的封闭switchwhiledoforforeach语句的终点。 break如果语句未由编译 whileforeach fordo时错误括起来switch,则会发生编译时错误。

当多个switchwhiledoforforeach语句相互嵌套时,语句break仅适用于最内部的语句。 若要跨多个嵌套级别传输控制, goto 应使用语句(§13.10.4)。

语句 break 无法退出 finally 块(§13.11)。 break当语句在块中finally发生时,该语句的目标break应位于同finally一个块中;否则会发生编译时错误。

语句 break 执行如下:

  • break如果语句退出一个或多个try具有关联finally块的块,则控件最初会传输到finallytry内部语句的块。 当控件到达块的 finally 终点时,控件将 finally 传输到下一个封闭 try 语句的块。 此过程将重复执行, finally 直到执行所有干预 try 语句的块。
  • 控制权将传输到语句的目标 break

break由于语句无条件地将控制权转移到别处,因此break语句的终点永远无法到达。

13.10.3 继续语句

continue语句开始对最近的封闭whiledoforforeach语句进行新的迭代。

continue_statement
    : 'continue' ';'
    ;

语句的目标continue是最接近封闭whiledoforforeach语句的嵌入语句的终点。 continue如果语句未由编译doforforeach时错误括起来while,则会发生编译时错误。

当多个 while语句、 do语句 forforeach 语句相互嵌套时,语句 continue 仅适用于最内部的语句。 若要跨多个嵌套级别传输控制, goto 应使用语句(§13.10.4)。

语句 continue 无法退出 finally 块(§13.11)。 continue当语句在块中finally发生时,该语句的目标continue应位于同finally一个块中;否则会发生编译时错误。

语句 continue 执行如下:

  • continue如果语句退出一个或多个try具有关联finally块的块,则控件最初会传输到finallytry内部语句的块。 当控件到达块的 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.3default 中的语句列表。 goto default如果语句未由switch语句括起来,或者最近的封闭switch语句不包含default标签,则会发生编译时错误。

语句 goto 无法退出 finally 块(§13.11)。 goto当语句在块中发生时,该语句的目标goto应位于同finallyfinally个块中,否则会发生编译时错误。

语句 goto 执行如下:

  • goto如果语句退出一个或多个try具有关联finally块的块,则控件最初会传输到finallytry内部语句的块。 当控件到达块的 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如果语句由一个或多个catchtry具有关联finally块的块括起来,则控件最初将传输到finallytry内部语句的块。 当控件到达块的 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则会检查子句,以便为异常找到合适的处理程序。 指定异常类型(或运行时表示异常类型的TT类型参数)的第一catch个子句,以便派生自TE运行时类型被视为匹配项。 如果该子句包含异常筛选器,则异常对象将分配给异常变量,并计算异常筛选器。 catch当子句包含异常筛选器时,catch如果异常筛选器的计算结果为 true,则该子句被视为匹配项。 常规 catch§13.11) 子句被视为任何异常类型的匹配项。 如果找到匹配 catch 子句,则通过将控制权传输到该 catch 子句块来完成异常传播。
    • 否则,如果 try 块或 catchS 将引发点括起来,并且 S 如果具有 finally 块,则将控制权传输到 finally 块。 如果 finally 块引发另一个异常,则终止处理当前异常。 否则,当控制到达块的 finally 终点时,将继续处理当前异常。
  • 如果异常处理程序未位于当前函数调用中,函数调用将终止,并发生以下情况之一:

    • 如果当前函数为非异步函数,上述步骤将重复给函数的调用方,其引发点对应于调用函数成员的语句。

    • 如果当前函数是异步函数和任务返回,则异常记录在返回任务中,该异常将放入错误或取消状态,如 §15.15.3 中所述

    • 如果当前函数为异步和 void-returning,则按 §15.15.4 中所述通知当前线程的同步上下文。

  • 如果异常处理终止当前线程中的所有函数成员调用,指示该线程没有异常的处理程序,则线程本身将终止。 此类终止的影响是实现定义的。

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_filtercatch 块期间,异常变量表示当前正在处理的异常。 为了进行明确的赋值检查,异常变量被视为在其整个范围内明确分配。

除非子 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 示例

它是一个编译时错误breakcontinue用于将控制权从块传出finallygoto语句。 当在break块中发生或continuegoto语句时,语句的目标应位于同finallyfinally个块中,否则会发生编译时错误。

这是语句在块中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 块的语句会始终执行。 无论控制传输是由于正常执行、执行、执行breakgotocontinue还是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 已选中和未选中的语句

checkedunchecked语句用于控制整型算术运算和转换的溢出检查上下文

checked_statement
    : 'checked' block
    ;

unchecked_statement
    : 'unchecked' block
    ;

checked语句会导致块中的所有表达式在选中的上下文中求值,并且该unchecked语句会导致在未选中的上下文中计算块中的所有表达式。

checkedunchecked语句与运算符(§12.8.20)完全相同checkedunchecked,只是它们对块而不是表达式进行操作。

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();
    }
}

除非演员的演员resourceSystem.IDisposable不应导致拳击发生。

否则,如果 ResourceTypedynamic扩展,则为

{
    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),仅在紧邻或关键字之前returnbreak使用时才具有特殊含义。

语句的显示位置 yield 存在一些限制,如下所述。

  • 在method_bodyoperator_bodyaccessor_body外部显示语句(任一形式)的编译时错误yield
  • 在匿名函数中出现语句(任一形式)的编译时错误 yield
  • 语句(任何一yield种形式)出现在语句的try子句中finally是编译时错误。
  • 语句在包含任何catch_clausestry语句中的任意位置出现编译时错误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块的块括起来,则控件最初会传输到finallytry内部语句的块。 当控件到达块的 finally 终点时,控件将 finally 传输到下一个封闭 try 语句的块。 此过程将重复执行, finally 直到执行所有封闭 try 语句的块。
  • 控件将返回到迭代器块的调用方。 这是 MoveNext 枚举器对象的方法或 Dispose 方法。

yield break由于语句无条件地将控制权转移到别处,因此yield break语句的终点永远无法到达。