Поделиться через


13 Инструкции

13.1 Общие

C# предоставляет различные инструкции.

Примечание. Большинство этих инструкций будут знакомы разработчикам, которые запрограммировали в C и C++. конечная заметка

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 будет объявлена, но ее нельзя использовать. Обратите внимание, что, поместив iобъявление в блок, пример действителен.

конечный пример

13.2 Конечные точки и доступность

Каждая инструкция имеет конечную точку. Интуитивно конечная точка утверждения — это местоположение, которое сразу же следует за заявлением. Правила выполнения составных операторов (операторы, содержащие вложенные операторы) указывают действие, которое выполняется, когда управление достигает конечной точки вложенного оператора.

Пример. Когда элемент управления достигает конечной точки инструкции в блоке, элемент управления передается в следующую инструкцию в блоке. конец примера

Если заявление может быть достигнуто путем выполнения, заявление, как утверждается , доступно. И наоборот, если нет возможности, что заявление будет выполнено, говорится, что это заявление является недоступным.

Пример. В следующем коде

void F()
{
    Console.WriteLine("reachable");
    goto Label;
    Console.WriteLine("unreachable");
  Label:
    Console.WriteLine("reachable");
}

Второй вызов Console.WriteLine недоступен, так как невозможно выполнить инструкцию.

заключительный пример

Предупреждение сообщается, если недоступен оператор, отличный от 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 Вызов считается доступным, хотя в действительности он никогда не будет выполнен.

конечная заметка

Блокировка члена функции или анонимной функции всегда считается доступной. Последовательно оценивая правила доступности каждого оператора в блоке, можно определить доступность любого данного оператора.

Пример. В следующем коде

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.

конец примера

Существуют две ситуации, в которых возникает ошибка компиляции из-за достижимой конечной точки инструкции:

  • switch Так как оператор не разрешает переход к следующему разделу коммутатора, это ошибка во время компиляции для конечной точки списка инструкций раздела коммутатора, доступного к нему. Если эта ошибка возникает, это обычно указывает на отсутствие оператора break.

  • Это ошибка во время компиляции для конечной точки блока элемента функции или анонимной функции, которая вычисляет значение, доступное для достижения. Если эта ошибка возникает, обычно это означает, что return инструкция отсутствует (§13.10.5).

13.3 Блоки

13.3.1 Общие

Блок block разрешает использовать множественные операторы в контекстах, где разрешён только один оператор.

block
    : '{' statement_list? '}'
    ;

Блок состоит из необязательного списка инструкций (statement_list) (§13.3.2), заключенного в фигурные скобки. Если список заявлений опущен, блок считается пустым.

Блок может содержать инструкции декларации (§13.6). Область локальной переменной или константы, объявленной в блоке, является блоком.

Блок выполняется следующим образом:

  • Если блок пуст, элемент управления передается в конечную точку блока.
  • Если блок не пуст, элемент управления передается в список инструкций. Когда и если элемент управления достигает конечной точки списка инструкций, элемент управления передается в конечную точку блока.

Список инструкций блока достижим, если сам блок достижим.

Конечная точка блока может быть достигнута, если блок пуст или если конечный пункт списка инструкций доступен.

Блок, содержащий одну или несколько yield инструкций (§13.15), называется блоком итератора. Блоки итератора используются для реализации элементов функции в качестве итераторов (§15.14). Некоторые дополнительные ограничения применяются к блокам итератора:

  • Это ошибка компиляции для оператора return, появляющегося в блоке итератора (но операторы yield return разрешены).
  • Это ошибка во время компиляции для блока итератора, содержащего небезопасный контекст (§23.2). Блок итератора всегда определяет безопасный контекст, даже если его объявление вложено в небезопасный контекст.

13.3.2 Список выражений

Список операторов состоит из одного или нескольких операторов, написанных последовательно. Списки операторов встречаются в blockах (§13.3) и в switch_blockах (§13.8.3).

statement_list
    : statement+
    ;

Список инструкций выполняется путем передачи элемента управления в первую инструкцию. Когда и если элемент управления достигает конечной точки инструкции, элемент управления передается в следующую инструкцию. Когда и если элемент управления достигает конечной точки последней инструкции, элемент управления передается в конечную точку списка инструкций.

Утверждение в списке утверждений доступно, если хотя бы одно из следующих условий истинно:

  • Выражение является первым выражением, и сам список выражений доступен.
  • Конечная точка предыдущего утверждения достижима.
  • Оператор является помеченной строкой, и на метку ссылается доступный goto оператор.

Конечная точка списка инструкций может быть достигнута, если конечный пункт последней инструкции в списке доступен.

13.4 Пустой оператор

empty_statement ничего не делает.

empty_statement
    : ';'
    ;

Пустой оператор выполнения используется, когда отсутствуют операции в контексте, в котором требуется оператор выполнения.

Выполнение пустой инструкции просто передает контроль в конечную точку инструкции. Таким образом, конечная точка пустого оператора доступна, если доступна сама пустая инструкция.

Пример: пустой оператор можно использовать при написании инструкции while с пустым текстом:

bool ProcessMessage() {...}
void ProcessMessages()
{
    while (ProcessMessage())
        ;
}

Кроме того, пустую инструкцию можно использовать для объявления метки непосредственно перед закрытием блока }:

void F(bool done)
{
    ...
    if (done)
    {
        goto exit;
    }
    ...
  exit:
    ;
}

завершающий пример

Операторы с метками 13.5

Labeled_statement позволяет оператору префиксировать метку. Операторы с метками разрешены в блоках, но не допускаются как внедренные инструкции.

labeled_statement
    : identifier ':' statement
    ;

Оператор с метками объявляет метку с именем, заданным идентификатором. Область метки — это весь блок, в котором объявлена метка, включая все вложенные блоки. Это ошибка времени компиляции, если две метки с одинаковым именем имеют перекрывающиеся области видимости.

Метка может ссылаться на goto инструкции (§13.10.4) в пределах области метки.

Примечание. Это означает, что goto операторы могут передавать управление внутри блоков и вне блоков, но никогда не в блоки. конечная заметка

Метки имеют собственное пространство объявления и не вмешиваются в другие идентификаторы.

Пример: пример

int F(int x)
{
    if (x >= 0)
    {
        goto x;
    }
    x = -x;
  x:
    return x;
}

является допустимым и использует имя x как в качестве параметра, так и в качестве метки.

конец примера

Выполнение помеченной инструкции соответствует точно выполнению инструкции после метки.

В дополнение к достижимости, предоставляемой обычным потоком управления, помеченная инструкция достижима, если на метку ссылается достижимая goto инструкция, если goto инструкция не находится внутри try блока или catch блока try_statement, который содержит finally блок, конечная точка которого недоступна, и помеченная инструкция находится за пределами 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 General

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

конечная заметка

Значение локальной переменной получается в выражении с помощью simple_name (§12.8.4). Локальная переменная должна быть определенно назначена (§9.4) в каждом расположении, где получается его значение. Каждая локальная переменная, представленная local_variable_declaration , изначально не назначена (§9.4.3). Если декларатор имеет выражение инициализации, введенная локальная переменная классифицируется как назначенная в конце декларатора (§9.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-safe-context (§9.7.2) локальной переменной ref является ref-safe-context ссылочного variable_reference, используемого при его инициализации. Контекст, безопасный для ссылок, нессылочных локальных переменных — это блок объявления.

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
    ;

Операция, неявно типизирующая локальную переменную, объявляет единственную локальную переменную, идентификатор. Выражение или variable_reference должно иметь тип времени компиляции. T Первая альтернатива объявляет переменную с начальным значением expression; ее тип — T?, если T является ссылочным типом, не допускающим значения NULL, в противном случае ее тип — T. Вторая альтернатива объявляет переменную ref с начальным значением refvariable_reference; ее тип — ref T?, когда 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

конец примера

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 должна иметь тип type и соответствовать тем же требованиям, что и для присвоения ссылки (§12.21.3).

Если ref_kindref readonly, то идентификаторы, которые объявляются, являются ссылками на переменные, которые рассматриваются как доступные только для чтения. В противном случае, если ref_kind равен ref, объявляемые идентификаторы являются ссылками на переменные, которые должны быть изменяемы.

Это ошибка во время компиляции для объявления локальной переменной ref или переменной ref struct типа, в методе, объявленном с , или внутри итератора (async).

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_declarators, каждый из которых представляет новую константу. Constant_declarator состоит из идентификатора, который называет константу, за ним следует маркер "=", а затем выражения константы (§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 и 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;
        }
    }
}

конечный пример

Если иное не указано ниже, семантика всех элементов грамматики совпадает с method_declaration (§15.6.1), считывается в контексте локальной функции вместо метода.

Идентификатор объявления_локальной_функции должен быть уникальным в пределах заявленной области блока, включая все вложенные пространства объявлений локальных переменных. Одним из последствий этого является то, что перегруженные 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. Это верно, является ли доступ явным (как в this.x) или неявным (как в x, где x является членом экземпляра структуры). Это правило запрещает только такой доступ и не влияет на то, приводит ли поиск элементов к члену структуры.

Это ошибка во время компиляции для текста локальной функции, содержащей goto инструкцию, break инструкцию или continue оператор, целевой объект которого находится за пределами тела локальной функции.

Примечание. Приведенные выше правила и thisgoto зеркальное отображение правил для анонимных функций в §12.19.3. конечная заметка

Локальная функция может вызываться из лексической позиции до её объявления. Однако это ошибка времени компиляции для функции, объявленной лексически до того, как будет объявлена переменная, используемая в локальной функции (§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;
    }
}

Другими словами, расположение объявления локальной функции не влияет на доступность каких-либо инструкций в содержащей функции. завершающий пример

Если тип аргумента для локальной функции имеет значение dynamic, вызываемая функция должна быть разрешена во время компиляции, а не во время выполнения.

Локальная функция не должна использоваться в дереве выражений.

Статическую локальную функцию

  • Могут ссылаться на статические члены, параметры типа, определения констант и статические локальные функции в охватывающей области.
  • Не следует ссылаться ни на неявные ссылки this или base, ни на элементы экземпляра this, а также на локальные переменные, параметры или нестатические локальные функции из охватывающего контекста. Однако все это разрешено в 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, которые просто вычисляют значение (которое будет отменено), не допускаются в качестве инструкций. конечная заметка

Выполнение expression_statement приводит к вычислению содержащегося выражения, после чего управление передается в конечную точку expression_statement. Конечная точка expression_statement доступна, если expression_statement доступна.

13.8 Операторы выбора

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

завершение примера

Оператор if выполняется следующим образом:

  • Вычисляется boolean_expression (§12.24).
  • Если логическое выражение дает true, элемент управления передается в первую внедренную инструкцию. Когда и если элемент управления достигает конечной точки этого оператора, элемент управления передается в конечную точку инструкции if .
  • Если логическое выражение дает false, и если часть else присутствует, управление передается во вторую встроенную инструкцию. Когда и если контроль достигает конечной точки этого оператора, управление передается в конечную точку оператора if.
  • Если логическое выражение возвращает false, и если часть else отсутствует, управление передается в конечную точку инструкции if.

Первый вложенный оператор внутри инструкции if достижим, если оператор if достижим и логическое выражение не имеет постоянного значения false.

Второй вложенный оператор оператора if, если он присутствует, достижим, если оператор if достижим, и логическое выражение не имеет константного значения true.

Конечная точка инструкции if может быть достижима, если конечная точка по крайней мере одной из вложенных инструкций может быть достигнута. Кроме того, конечная точка if оператора без else части доступна, если if оператор доступен и логическое выражение не имеет константного значения true.

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_expression), а затем switch_block. switch_block состоит из нуля или более switch_sectionов, заключённых в фигурные скобки. Каждый switch_section состоит из одного или нескольких switch_label, за которым следует statement_list (§13.3.2). Каждый switch_label содержит связанный шаблон (§11), по которому проверяется значение переключающего выражения. Если case_guard присутствует, его выражение должно быть неявно преобразовано в тип bool , и это выражение оценивается как дополнительное условие для рассмотрения дела.

Руководящий тип инструкции switch устанавливается выражением switch.

  • Если тип выражения переключателя - это sbyte, byte, short, ushort, int, uint, long, ulong, char, bool, string или enum_type, или если это обнуляемый тип, соответствующий одному из этих типов, то именно этот тип управляет switch оператором.
  • В противном случае, если существует ровно одно определенное пользователем неявное преобразование из типа выражения switch к одному из следующих возможных типов управления: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, или допускающий значение NULL тип, соответствующий одному из этих типов, то преобразованный тип является определяющим типом инструкции switch.
  • В противном случае управляющим типом инструкции switch является тип выражения 'switch'. Это ошибка, если такой тип отсутствует.

В инструкции switch может быть не более одной метки default.

Это ошибка, если шаблон любой метки коммутатора неприменимо (§11.2.1) к типу входного выражения.

Это ошибка, если шаблон любой метки оператора switch охвачен (§11.3) набором шаблонов более ранних меток оператора switch, которые не имеют условия или чье условие является константным выражением со значением 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.
}

завершающий пример

Оператор switch выполняется следующим образом:

  • Выражение коммутатора вычисляется и преобразуется в тип управления.
  • Управление передается в соответствии со значением преобразованного выражения переключателя.
    • Лексически первый шаблон в наборе case меток в той же switch инструкции, который соответствует значению выражения switch и для которого выражение guard отсутствует или оценивается как истинное, вызывает передачу управления в список инструкций после соответствующей case метки.
    • В противном случае, если default метка присутствует, управление передается в список инструкций после default метки.
    • В противном случае элемент управления передается в конечную точку инструкции switch .

Примечание. Порядок сопоставления шаблонов во время выполнения не определен. Компилятор может (но не требуется) сопоставлять шаблоны вне порядка и повторно использовать результаты уже сопоставленных шаблонов для вычисления результата сопоставления других шаблонов. Тем не менее, компилятору требуется определить лексически первый шаблон, соответствующий выражению, и для которого охранное условие либо отсутствует, либо имеет значение true. конечная заметка

Если конечная точка списка инструкций раздела коммутатора недоступна, возникает ошибка во время компиляции. Это называется правилом "нет провала".

Пример: пример

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    default:
        CaseOthers();
        break;
}

является допустимым, так как раздел переключателя не имеет доступную конечную точку. В отличие от C и C++, выполнение раздела коммутатора не допускается "переходить" к следующему разделу коммутатора, а пример

switch (i)
{
    case 0:
        CaseZero();
    case 1:
        CaseZeroOrOne();
    default:
        CaseAny();
}

приводит к ошибке во время компиляции. Если после выполнения одной секции switch следует выполнение другой секции, необходимо использовать явный goto case или goto default оператор:

switch (i)
{
    case 0:
        CaseZero();
        goto case 1;
    case 1:
        CaseZeroOrOne();
        goto default;
    default:
        CaseAny();
        break;
}

пример конца

В switch_section разрешено несколько меток.

Пример: пример

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    case 2:
    default:
        CaseTwo();
        break;
}

является допустимым. В примере не нарушается правило "без падения", так как метки case 2: и являются частью одной и default: той же switch_section.

пример конца

Примечание. Правило "без падения" предотвращает общий класс ошибок, возникающих в C и C++ при break случайном пропуске инструкций. Например, разделы приведенной switch выше инструкции можно отменить, не влияя на поведение инструкции:

switch (i)
{
    default:
        CaseAny();
        break;
    case 1:
        CaseZeroOrOne();
        goto default;
    case 0:
        CaseZero();
        goto case 1;
}

конечная заметка

Примечание. Список инструкций раздела переключателя обычно заканчивается оператором break, goto case или goto default, но любая конструкция, которая делает конечную точку списка инструкций недоступной, разрешается. Например, оператор while, контролируемый логическим выражением true, известно, что никогда не достигает конечной точки. Аналогичным образом, оператор throwreturn всегда передает контроль в другом месте и никогда не достигает конечной точки. Таким образом, следующий пример является правильным:

switch (i)
{
     case 0:
         while (true)
         {
             F();
         }
     case 1:
         throw new ArgumentException();
     case 2:
         return;
}

конечная заметка

Пример. Тип инструкции 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;
    }
}

концевой пример

Примечание. Как и операторы равенства строк (§12.12.8), switch оператор чувствителен к регистру и выполнит данный раздел коммутатора тогда и только тогда, когда строка выражения коммутатора в точности совпадает с case меткой-константой. конец примечания. Если управляющий тип инструкции является switch или допускающим значение NULL типом значенийstring, значение null допускается как case константа метки.

список_операторов в блоке_переключения может содержать операторы объявления (§13.6). Область локальной переменной или константы, объявленной в блоке коммутатора, является блоком коммутатора.

Метка переключателя доступна, если хотя бы одно из следующих условий истинно:

  • Выражение switch является константным значением и либо
    • Метка — это case, чей шаблон совпадал бы (§11.2.1) с этим значением, и охранник метки отсутствует или не является постоянным выражением со значением false; или
    • default это метка, и ни один раздел switch не содержит метку case, шаблон которой будет соответствовать данному значению, и чьё условие либо отсутствует, либо является константным выражением со значением true.
  • Выражение коммутатора не является константным значением и
    • Метка — это case без охранника или с охранником, значение которого не является константой false; или
    • это default метка и
      • набор шаблонов, отображаемых среди случаев оператора switch, которые не имеют условий или имеют условия, значение которых является константой true, не являются исчерпывающими (§11.4) для типа, которым управляет оператор switch; или
      • Тип, управляющий переключением, является nullable, и набор шаблонов, появляющихся среди случаев оператора switch, которые не имеют условий или содержат условия со значением константы true, не включает шаблон, который соответствовал бы значению null.
  • Метка переключателя ссылается на доступную инструкцию goto case или goto default.

Список инструкций заданного раздела переключателя достижим, если switch оператор достижим, а раздел переключателя содержит достижимую метку переключателя.

Конечная точка switch оператора достижима, если оператор switch достижим, и по крайней мере одно из следующих истинно:

  • Инструкция switch содержит доступную break инструкцию, которая завершает инструкцию switch .
  • Метка default отсутствует и либо
    • Выражение switch является неконстантным значением, и набор шаблонов, представленных в случаях оператора switch, не имеющих условий или имеющих условия со значением константы true, не является исчерпывающим (§11.4) для управляющего типа switch.
    • Выражение переключателя — это неконстантное значение типа, допускающего значение null, и ни один из шаблонов, появляющихся среди случаев оператора switch без условий или с условиями, истинность которых является постоянной, не соответствует значению null.
    • Выражение switch является константным значением, и ни одна case метка без условия или с условием, являющимся константой истина, не будет соответствовать этому значению.

Пример. В следующем коде показано краткое использование 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, пустой строкой или любой строкой, содержащей только пробелы. завершающий пример

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 доступен, если оператор while доступен и булевое выражение не имеет постоянного значения false.

Конечная точка 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) может использоваться для передачи управления в точку завершения встроенной инструкции, что позволяет выполнить еще одну итерацию оператора .

Вложенный оператор инструкции do достижим, если оператор do достижим.

Конечная точка do оператора может быть достигнута, если по крайней мере одно из следующих значений имеет значение true:

  • Инструкция do содержит доступную break инструкцию, которая завершает инструкцию do .
  • Конечная точка встроенного оператора достигается, и логическое выражение не имеет константного значения true.

13.9.4 Инструкция for

Оператор for вычисляет последовательность выражений инициализации, а затем, пока условие истинно, многократно выполняет встроенную инструкцию и вычисляет последовательность итерационных выражений.

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, если он присутствует, состоит из объявления локальной переменной (§13.6.2) или списка выражений (§13.7), разделенных запятыми. Область локальной переменной, объявленной for_initializer, — это for_initializer, for_condition, for_iterator и embedded_statement.

for_condition, если присутствует, должна быть boolean_expression (§12.24).

For_iterator, если присутствует, состоит из списка statement_expression(§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 отсутствует.
  • Оператор 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
    ;

Local_variable_type и идентификатор инструкции foreach объявляют переменную итерации инструкции. var Если идентификатор присваивается в качестве local_variable_type, а имя типа var не находится в области, переменная итерации, как говорят, является неявно типизированной переменной итерации, и его тип принимается как тип foreach элемента инструкции, как указано ниже.

Если foreach_statement содержит либо оба, либо ни одного из ref и readonly, то переменная итерации обозначает переменную, которая рассматривается как только для чтения. В противном случае, если foreach_statement содержит ref без readonlyэтого, переменная итерации обозначает переменную, которая должна быть записываема.

Переменная итерации соответствует локальной переменной с областью действия, которая распространяется на встроенный оператор. Во время выполнения инструкции foreach переменная итерации представляет элемент коллекции, для которого выполняется итерация. Если переменная итерации обозначает переменную только для чтения, ошибка во время компиляции возникает, если внедренная инструкция пытается изменить ее (через назначение или ++-- операторы) или передать ее в качестве ссылочного или выходного параметра.

Во следующем, для краткости, IEnumerable, IEnumerator, IEnumerable<T> и IEnumerator<T> ссылаются на соответствующие типы в пространствах имен System.Collections и System.Collections.Generic.

Обработка инструкции foreach во время компиляции сначала определяет тип коллекции, тип перечислителя и тип итерации выражения. Это определение изложено следующим образом:

  • Если тип выражения является типом X массива Тип коллекции — это IEnumerable интерфейс, тип перечислителя — это IEnumerator интерфейс, а тип итерации — это тип элемента массива типа X.
  • Если тип X выражения есть, то существует неявное преобразование из выражения в dynamic интерфейс (§10.2.10).IEnumerable Тип коллекции — интерфейс IEnumerable , а тип перечислителя — IEnumerator интерфейс. var Если идентификатор указан в качестве local_variable_type, то тип итерации имеет значение dynamic, в противном случае — object.
  • В противном случае определите, имеет ли тип X соответствующий GetEnumerator метод:
    • Выполните поиск элементов для типа X с идентификатором GetEnumerator и без аргументов типа. Если поиск элемента не создает совпадение или создает неоднозначность или создает совпадение, которое не является группой методов, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется сделать предупреждение, если поиск элемента создает что-либо, кроме группы методов или отсутствия совпадения.
    • Выполните разрешение перегрузки с помощью результирующей группы методов и пустого списка аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется выдавать предупреждение, если разрешение перегрузки создает что-либо, кроме однозначного метода общедоступного экземпляра или нет применимых методов.
    • Если возвращаемый тип EGetEnumerator метода не является классом, структурой или типом интерфейса, создается ошибка, и дальнейшие действия не выполняются.
    • Выполняется поиск членов на E с идентификатором Current и без аргументов типа. Если поиск члена не дает совпадения, результат будет ошибкой, или если результатом является что-либо, кроме общедоступного свойства экземпляра, которое разрешает чтение, возникает ошибка, и дальнейшие шаги не предпринимаются.
    • Выполняется поиск членов E с идентификатором MoveNext и без аргументов типа. Если поиск элемента не дает совпадений, возникает ошибка или результатом является что-либо, кроме группы методов, создается ошибка, и никакие дальнейшие действия не выполняются.
    • Разрешение перегрузки выполняется в группе методов с пустым списком аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, или его тип возвращаемого значения отсутствует bool, возникает ошибка и дальнейшие действия не выполняются.
    • Тип коллекции — Xэто тип Eперечислителя, а тип итерации — это тип Current свойства. Свойство Current может включать ref модификатор, в этом случае возвращаемое выражение является variable_reference (§9.5), которое при необходимости доступно только для чтения.
  • В противном случае проверьте наличие перечисленного интерфейса:
    • Если среди всех типовTᵢ, для которых есть неявное преобразование от X к IEnumerable<Tᵢ>, существует уникальный тип T такой, что T не является dynamic и для всех остальных Tᵢ существует неявное преобразование от IEnumerable<T> к IEnumerable<Tᵢ>, то тип коллекции — интерфейс IEnumerable<T>, тип перечислителя — интерфейс IEnumerator<T>, а тип итерации — T.
    • В противном случае, если существует несколько таких типов T, возникает ошибка и дальнейшие действия не выполняются.
    • В противном случае, если есть неявное преобразование из X в интерфейс System.Collections.IEnumerable, то тип коллекции — это интерфейс, тип перечислителя — интерфейс System.Collections.IEnumerator, а тип итерации — интерфейс object.
    • В противном случае возникает ошибка, и дальнейшие действия не выполняются.

Приведенные выше шаги, если успешно, однозначно создают тип коллекции, тип Cперечислителя и тип ETитерации, 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 доступна только для чтения во встроенном выражении. Если не существует явного преобразования (§10.3) из T (типа итерации) в V (local_variable_type в foreach инструкции), создается ошибка и дальнейшие действия не выполняются.

Если переменная итерации является эталонной переменной (§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(§12.21.3). Если отсутствует идентичное преобразование (§10.2.2) из T (типа итерации) в V (local_variable_type в инструкции foreach), производится ошибка, и дальнейшие действия не предпринимаются.

Оператор foreach формы foreach (ref readonly V v in x) «embedded_statement» имеет аналогичную эквивалентную форму, но ссылочная переменная v находится ref readonly в внедренной инструкции, поэтому не может быть переназначен или переназначен.

Примечание. Если x имеет значение null, выбрасывается System.NullReferenceException во время выполнения. конечная заметка

Разрешается использовать разные способы реализации заданного foreach_statement, например, по соображениям производительности, если поведение соответствует описанному выше развертыванию.

Размещение v внутри while цикла значимо для захвата (§12.19.6.2) любой анонимной функцией, встречающейся в embedded_statement.

Пример:

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, которое будет напечатано. (Обратите внимание, что более ранние версии C# объявляли v вне цикла while.)

завершающий пример

Тело 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 это тип значения или параметр типа, созданный в тип значения, преобразование 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

заключительный пример

Пример. В следующем примере

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers)
{
    Console.WriteLine(n);
}

Тип n выводится как тип int, который является типом итерации numbers.

заключительный пример

13.10 Операторы перехода

13.10.1 Общие

Инструкции перехода безусловно передают управление.

jump_statement
    : break_statement
    | continue_statement
    | goto_statement
    | return_statement
    | throw_statement
    ;

Место, куда оператор перехода передает управление, называется целью оператора перехода.

Когда оператор перехода происходит внутри блока, и цель этого оператора находится за пределами этого блока, говорится, что оператор выходит из блока. Хотя оператор перехода может передавать элемент управления из блока, он никогда не может передавать управление в блок.

Выполнение инструкций перехода сложно благодаря присутствию промежуточных try инструкций. В отсутствие таких try заявлений оператор прыжка безоговорочно передает контроль от оператора перехода к целевому объекту. В присутствии таких промежуточных try инструкций выполнение является более сложным. Если оператор перехода выходит из одного или нескольких 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 Блоки, связанные с двумя try операторами, выполняются перед передачей элемента управления в целевой объект инструкции jump. Результат создается следующим образом:

Before break
Innermost finally block
Outermost finally block
After break

конечный пример

13.10.2 Оператор перерыва

Оператор break выходит из ближайшего включающего оператора switch, оператора while, оператора do, оператора for или оператора foreach.

break_statement
    : 'break' ';'
    ;

Цель оператора break — это конечная точка ближайшего заключенного оператора switch, while, do, for или foreach. break Если инструкция не заключена в switch, while, do, for или foreach, возникает ошибка во время компиляции.

Если несколько 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

Оператор continue запускает новую итерацию ближайшего охватывающего оператора while, do, for или foreach.

continue_statement
    : 'continue' ';'
    ;

Цель оператора continue — это конечная точка внедренного оператора ближайшего окружающего while, do, for или foreach оператора. continue Если инструкция не заключена в whileоператор , doforили foreach оператор, возникает ошибка во время компиляции.

Если несколько while, do, for или foreach операторов вложены друг в друга, continue оператор применяется только к самому внутреннему оператору. Для передачи управления через несколько уровней вложенности, следует использовать оператор (§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 Оператор используется для передачи элемента управления из вложенной области.

конечная заметка

Цель инструкции goto case — это список инструкций в немедленном заключенном switch операторе (§13.8.3), который содержит case метку с константным шаблоном заданного значения константы и без защиты. goto case Если оператор не заключён в switch, если ближайший заключающий switch оператор не содержит такого case, или если constant_expression не может быть неявно преобразовано (§10.2) в управляющий тип ближайшего заключающего switch оператора, возникает ошибка времени компиляции.

Цель инструкции goto default — это список инструкций в непосредственно окружающем операторе switch (§13.8.3), который содержит метку default. goto default Если инструкция не заключена в switch инструкцию или если ближайшая заключающая switch инструкция не содержит default метку, возникает ошибка компиляции.

Оператор goto не может выйти из блока finally (§13.11). goto При возникновении инструкции finally в goto блоке целевой объект инструкции должен находиться в одном finally блоке или в противном случае возникает ошибка во время компиляции.

Оператор goto выполняется следующим образом:

  • goto Если оператор выходит из одного или нескольких try блоков с связанными finally блоками, элемент управления изначально передается finally в блок самой try внутренней инструкции. Когда и если элемент управления достигает конечной finally точки блока, элемент управления передается finally в блок следующей заключающей инструкции try . Этот процесс повторяется до тех пор, пока finally блоки всех промежуточных try инструкций не будут выполнены.
  • Элемент управления передается в целевой объект инструкции goto .

goto Поскольку оператор безоговорочно передает контроль в другом месте, конечный пункт goto оператора никогда недоступен.

13.10.5 Инструкция return

Оператор return возвращает управление текущему вызывающему объекту функции, где присутствует оператор return, при необходимости может вернуть значение или variable_reference (§9.5).

return_statement
    : 'return' ';'
    | 'return' expression ';'
    | 'return' 'ref' variable_reference ';'
    ;

Оператор возврата без выражения называется возвратом без значения; оператор, содержащий выражение, называется возвратом по ссылке; и оператор, содержащий только выражение, называется возвратом по значению.

Во время компиляции возникает ошибка, если использовать возврат без значения в методе, объявленном как возвращающий по значению или возврат по ссылке (§15.6.1).

Ошибка компиляции возникает при использовании возвращаемого по ссылке метода, объявленного как не возвращающий значение или возвращающий по значению.

Это ошибка времени компиляции — использовать возврат по значению из метода, объявленного как не возвращающего значение или возвращающего по ссылке.

Это ошибка времени компиляции для использования возвращения по ссылке, если выражение не является ссылкой_на_переменную или является ссылкой на переменную, чей безопасный_контекст_ссылки не является вызывающим контекстом (§9.7.2).

Это ошибка времени компиляции — использование возврата по ссылке из метода, объявленного с method_modifierasync.

Как сообщается, член функции вычисляет значение , если это метод с методом возвращаемого по значению (§15.6.11), методом доступа по значению свойства или индексатора или определяемым пользователем оператором. Члены функции, которые не возвращают значение, не вычисляют значение и являются методами с эффективным типом возврата void, задают методы доступа свойств и индексаторов, добавляют и удаляют методы доступа событий, конструкторы экземпляров, статические конструкторы и деструкторы. Члены функции, возвращаемые по ссылке, не вычисляют значение.

Для возвращения по значению должно существовать неявное преобразование (§10.2) от типа выражения к эффективному возвращаемому типу (§15.6.11) содержащего члена функции. Для возврата по ссылке должно существовать преобразование идентичности (§10.2.2) между типом выражения и эффективным типом возвращаемого члена функции.

returnоператоры также можно использовать в тексте анонимных выражений функций (§12.19) и участвовать в определении того, какие преобразования существуют для этих функций (§10.7.1).

Это ошибка времени компиляции, если оператор return появляется в блоке finally (§13.11).

Оператор return выполняется следующим образом:

  • Для возврата по значению выражение вычисляется, и его значение преобразуется в эффективному типу возвращаемого значения функции путем неявного преобразования. Результат преобразования становится результатом, созданным функцией. Для возврата по ссылке выражение вычисляется, и результат должен быть классифицирован как переменная. Если в методе readonly используется возврат по ссылке, то результирующая переменная будет доступна только для чтения.
  • return Если инструкция заключена в один или несколько trycatch блоков с связанными finally блоками, управление изначально передается finally блоку самой внутренней try инструкции. Когда и если управление достигает конечной точки блока 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 никогда недоступна.

При возникновении исключения элемент управления передается первому catch предложению в заключающей try инструкции, которая может обрабатывать исключение. Процесс, который происходит с точки исключения, вызываемого до точки передачи элемента управления в подходящий обработчик исключений, называется распространением исключений. Распространение исключения состоит из многократного вычисления следующих шагов до тех пор, пока catch не будет найдено предложение, соответствующее исключению. В этом описании точка выброса изначально является расположением, в котором выбрасывается исключение. Это поведение указано в (§21.4).

  • В текущем элементе функции проверяется каждая try инструкция, заключающая точку броска. Для каждой инструкции, начиная с самой внутренней S инструкции tryи заканчивая самой внешней try инструкцией, вычисляются следующие действия:

    • try Если блок S заключает точку броска и S имеет одну или несколько catch клауз, catch клаузы проверяются в порядке появления, чтобы найти подходящий обработчик исключения. Первое catch предложение, указывающее тип T исключения (или параметр типа, обозначающий тип Tисключения во время выполнения), таким образом, что тип E времени выполнения наследуется от T соответствия. Если предложение содержит фильтр исключений, объект исключения назначается переменной исключения, а фильтр исключений вычисляется. Если catch предложение содержит фильтр исключений, то это catch предложение считается совпадением, если фильтр исключений оценивается какtrue. Общее catch предложение (§13.11) считается совпадением для любого типа исключения. catch Если находится соответствующее предложение, распространение исключений завершается путем передачи элемента управления в блок этого catch предложения.
    • В противном случае, если try блок или catch блок S охватывает точку выброса и у S есть finally блок, управление передается в finally блок. Если блок finally создает другое исключение, обработка текущего исключения завершается. В противном случае, когда элемент управления достигает конечной точки блока, обработка текущего finally исключения продолжается.
  • Если обработчик исключений не был расположен в текущем вызове функции, вызов функции завершается, и происходит одно из следующих действий:

    • Если текущая функция не является асинхронной, описанные выше шаги повторяются для вызывающего функции с точкой вызова, соответствующей инструкции, из которой был вызван член функции.

    • Если текущая функция является асинхронной и возвращаемой задачей, исключение записывается в задаче возврата, которая помещается в состояние сбоя или отмены, как описано в разделе 15.15.3.

    • Если текущая функция является асинхронной и возвращает объект типа void, контекст синхронизации текущего потока уведомляется, как описано в разделе 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_filter и catch блока переменная исключения представляет исключение, которое в настоящее время обрабатывается. В целях проверки определенного назначения переменная исключения считается определенно назначенной в всей области.

Если catch предложение не содержит имя переменной исключения, доступ к объекту исключения в фильтре и catch блоке невозможно.

Предложение catch, не указывающее тип исключения или имя переменной исключения, называется общим catch предложением. Оператор try может иметь только одно общее catch условие, и, если оно присутствует, это должно быть последнее catch условие.

Примечание. Некоторые языки программирования могут поддерживать исключения, которые не представляются как объект, производный от System.Exception, хотя такие исключения никогда не могут быть созданы кодом C#. Общий catch оператор может использоваться для перехвата таких исключений. Таким образом, генеральное catch правило семантически отличается от того, которое задает тип System.Exception, тем, что первое может также обрабатывать исключения, возникающие в других языках. конечная заметка

Чтобы найти обработчик исключения, catch предложения рассматриваются в лексическом порядке. catch Если в предложении указан тип, но не фильтр исключений, это будет ошибкой во время компиляции, если более поздняя catch инструкция try указывает тип, который совпадает с этим типом или является его производным.

Примечание. Без этого ограничения можно было бы написать недоступные catch предложения. конечная заметка

В блоке 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

конец примера

Это ошибка времени компиляции, если оператор break, continue или goto пытается перенести управление из блока finally. Когда оператор break, continue или goto возникает в блоке finally, целевой объект оператора должен находиться в том же блоке finally, иначе возникает ошибка компиляции.

Это ошибка во время компиляции для инструкции, return возникающей в блоке finally .

Когда выполнение достигает инструкции try , элемент управления передается в try блок. Если элемент управления достигает конечной try точки блока без исключения, элемент управления передается finally в блок, если он существует. Если блок finally не существует, управление передается в конечную точку оператора try.

Если исключение было распространено, предложения catch, если они есть, последовательно проверяются в лексическом порядке, чтобы найти первое совпадение для исключения. Поиск подходящего предложения catch продолжается по всем вложенным блокам, как описано в разделе 13.10.6. Предложение catch считается подходящим, если тип исключения соответствует любому exception_specifier, а любой exception_filter истинен. Предложение catch без exception_specifier соответствует любому типу исключения. Тип исключения соответствует exception_specifier, если exception_specifier указывает тип исключения или базовый тип типа исключения. Если предложение содержит фильтр исключений, объект исключения назначается переменной исключения, а фильтр исключений вычисляется.

Если исключение было распространено и catch найдено соответствующее предложение, элемент управления передается в первый блок сопоставления catch . Если поток управления достигает конца блока catch без распространения исключений, он передается в блок finally, если он существует. Если блок finally не существует, управление передается к конечной точке оператора try. Если исключение было распространено из блока catch, управление передается блоку finally, если он существует. Исключение распространяется на следующую заключиющую инструкцию try .

Если исключение было распространено, и предложение сопоставления catch не найдено, контроль передается в finally блок, если он существует. Исключение распространяется на следующую заключиющую инструкцию try .

Операторы блока finally всегда выполняются, когда управление выходит из оператора try. Это верно, происходит ли передача элемента управления в результате нормального выполнения, в результате выполнения breakинструкции , continuegotoили 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

заключительный пример

Блок try оператора try достижим, если оператор try достижим.

Блок catch инструкции try достижим, если инструкция try достижима.

Блок finally оператора try доступен, если оператор try доступен.

Конечная точка try оператора может быть достигнута, если оба из следующих значений имеют значение true:

  • Конечная точка try блока может быть достигнута или конечная точка по крайней мере одного catch блока может быть достигнута.
  • Если блок finally присутствует, конечная точка блока finally становится достижима.

13.12 Проверенные и непроверенные инструкции

Инструкции checked и unchecked используются для управления проверкой контекста переполнения для арифметических операций и преобразований целочисленного типа .

checked_statement
    : 'checked' block
    ;

unchecked_statement
    : 'unchecked' block
    ;

Оператор checked приводит к оценке всех выражений в блоке в проверяемом контексте, а оператор unchecked приводит к оценке всех выражений в блоке в непроверяемом контексте.

Операторы checked и unchecked точно эквивалентны операторам checked и unchecked (§12.8.20), за исключением того, что они действуют на блоках вместо выражений.

13.13 Инструкция блокировки

Оператор lock получает блокировку взаимного исключения для заданного объекта, выполняет инструкцию, а затем освобождает блокировку.

lock_statement
    : 'lock' '(' expression ')' embedded_statement
    ;

Выражение должно указывать значение типа, известного lock. Неявное преобразование бокса (§10.2.9) никогда не выполняется для выраженияlock инструкции, и, следовательно, в случае, если выражение обозначает значение типа 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 преобразуется в три части: приобретение, использование и удаление. Использование ресурса неявно заключено в try инструкцию, включающую finally предложение. Это 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 не должно привести к возникновению бокса.

В противном случае, когда 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, чтобы убедиться в правильном закрытии базового файла после операций записи или чтения.

конец примера

13.15 Оператор доходности

Оператор yield используется в блоке итератора (§13.3), чтобы получить значение объекту перечислителя (§15.14.5) или перечислению объекта (§15.14.6) итератора или сигнализировать о завершении итерации.

yield_statement
    : 'yield' 'return' expression ';'
    | 'yield' 'break' ';'
    ;

yield — это контекстное ключевое слово (§6.4.4), имеющее особое значение только при использовании непосредственно перед ключевым словом return или break.

Есть несколько ограничений на то, где может появляться инструкция yield, как описано в следующем описании.

  • Это ошибка компиляции, если инструкция yield (любой формы) появляется вне method_body, operator_body или accessor_body.
  • Это ошибка компиляции, когда yield инструкция (любой формы) появляется внутри анонимной функции.
  • Это ошибка компиляции, если yield инструкция (любой формы) появляется в части finally инструкции try.
  • Это ошибка в момент компиляции для yield return оператора, встречающегося в любом месте try оператора, содержащего любые catch_clauses.

Пример. В следующем примере показаны некоторые допустимые и недопустимые использование инструкций 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
}

конец примера

Неявное преобразование (§10.2) должно существовать от типа выражения в инструкцииyield return к типу yield (§15.14.4) итератора.

Оператор yield return выполняется следующим образом:

  • Выражение, заданное в инструкции, вычисляется, неявно преобразуется в тип доходности и назначается Current свойству объекта перечислителя.
  • Выполнение блока итератора приостановлено. yield return Если оператор находится в одном или нескольких try блоках, связанные finally блоки не выполняются на данный момент.
  • Метод MoveNext объекта перечислителя возвращает true вызывающему, указывая, что объект перечислителя успешно продвинулся к следующему элементу.

Следующий вызов метода объекта перечислителя MoveNext возобновляет выполнение итерационного блока с места, где он был последний раз приостановлен.

Оператор yield break выполняется следующим образом:

  • Если оператор yield break заключен в один или несколько try блоков со связанными finally блоками, управление изначально передается в finally блок самой внутренней try инструкции. Когда и если управление достигает конца блока finally, управление передаётся в блок finally следующей заключающей инструкции try. Процесс повторяется до тех пор, пока блоки всех вложенных инструкций finallytry не будут выполнены.
  • Управление возвращается вызывающему объекту итератора. Это MoveNext метод или Dispose метод объекта перечислителя.

yield break Поскольку оператор безоговорочно передает управление в другое место, конечная точка yield break оператора никогда не достижима.