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


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
    : '{' statement_list? '}'
    ;

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

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

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

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

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

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

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

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

Список инструкций 13.3.2

Список инструкций состоит из одного или нескольких операторов, написанных в последовательности. Списки инструкций происходят в блоках(§13.3) и в switch_blocks (§13.8.3).

statement_list
    : statement+
    ;

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

Оператор в списке инструкций доступен, если по крайней мере одно из следующих значений имеет значение true:

  • Оператор является первым оператором, и сам список инструкций доступен.
  • Конечная точка предыдущей инструкции может быть достигнута.
  • Оператор — это помеченная инструкция, и метка ссылается на доступную 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. ref-safe-context для локальных переменных, отличных от ссылок, является блоком объявления.

13.6.2.2 Неявно типизированные объявления локальных переменных

implicitly_typed_local_variable_declaration
    : 'var' implicitly_typed_local_variable_declarator
    | ref_kind 'var' ref_local_variable_declarator
    ;

implicitly_typed_local_variable_declarator
    : identifier '=' expression
    ;

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

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

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

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

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 состоит из идентификатора, который называет константу, а затем маркером "=" и 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;
        }
    }
}

пример конца

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

Идентификатор local_function_declaration должен быть уникальным в объявленной области блока, включая все вложенные локальные пространства объявлений переменной. Одним из последствий этого является то, что перегруженные local_function_declarationне допускаются.

Local_function_declaration может включать модификатор async (§15.15) и один unsafe модификатор (§23.1). Если объявление включает async модификатор, то возвращаемый тип должен быть void или «TaskType» тип (§15.15.1). Если объявление включает static модификатор, функция является статической локальной функцией; в противном случае это нестатичная локальная функция. Это ошибка во время компиляции для type_parameter_list или parameter_list для хранения атрибутов. Если локальная функция объявлена в небезопасном контексте (§23.2), локальная функция может включать небезопасный код, даже если объявление локальной функции не включает unsafe модификатор.

Локальная функция объявляется в области блокировки. Нестатичная локальная функция может записывать переменные из включающей области, а статическую локальную функцию не должна (поэтому у нее нет доступа к закрытым локальным, параметрам, не статическим локальным функциям или this). Это ошибка во время компиляции, если записанная переменная считывается текстом нестатитической локальной функции, но не определенно назначается перед каждым вызовом функции. Компилятор должен определить, какие переменные определенно назначены при возврате (§9.4.4.33).

Если тип структуры является типом this структуры, это ошибка во время компиляции для текста локальной функции для доступа this. Это верно, является ли доступ явным (как в this.x) или неявным (как в x том месте, где x является элементом экземпляра структуры). Это правило запрещает только такой доступ и не влияет на то, приводит ли поиск элементов к члену структуры.

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

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

Операторы выражения 13.7

Expression_statement вычисляет заданное выражение. Значение, вычисленное выражением, если таковой имеется, удаляется.

expression_statement
    : statement_expression ';'
    ;

statement_expression
    : null_conditional_invocation_expression
    | invocation_expression
    | object_creation_expression
    | assignment
    | post_increment_expression
    | post_decrement_expression
    | pre_increment_expression
    | pre_decrement_expression
    | await_expression
    ;

Не все выражения разрешены в виде инструкций.

Примечание. В частности, выражения, такие как x + y и x == 1, которые просто вычисляют значение (которое будет отменено), не допускаются в качестве инструкций. конечная заметка

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

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

  • Если тип выражения переключателя имеет sbyteзначение , shortushortintlongcharboolstringulonguintbyteили enum_type, или если это тип значения NULL, соответствующий одному из этих типов, то это руководящий тип инструкции.switch
  • В противном случае, если одно определяемое пользователем неявное преобразование существует из типа выражения коммутатора к одному из следующих возможных типов управления: sbyte, ushortlongstringshortbyteuintulongintcharили, допускающее значение NULL, соответствующее одному из этих типов, то преобразованный тип является руководящим типом инструкции.switch
  • В противном случае тип инструкции switch является типом выражения switch. Это ошибка, если такой тип отсутствует.

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

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

Это ошибка, если шаблон любой метки коммутатора подмечен (§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 .

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

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

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

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

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 константе метки. конечная заметка, если руководящий тип инструкции является string или типом значенийswitch, допускаемым значением NULL, значение null допускается как case константа метки.

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

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

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

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

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

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

Операторы итерации 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 оператор (§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_expression(§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) для передачи управления в конечную точку инструкции (таким образом, заканчивая итерацию внедренной инструкции), а continue оператор (§13.10.3) может использоваться для передачи управления в конечную точку for внедренной инструкции (таким образом выполняя 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
    ;

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

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

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

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

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

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

Приведенные выше шаги, если успешно, однозначно создают тип коллекции, тип Cперечислителя и тип E Tитерации, ref Tили ref readonly T. Оператор foreach формы

foreach (V v in x) «embedded_statement»

Затем эквивалентен следующему:

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            V v = (V)(T)e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

Переменная e не видна или недоступна для выражения x или внедренной инструкции или любого другого исходного кода программы. Переменная v доступна только для чтения в внедренной инструкции. Если не существует явного преобразования (§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 не должна быть переназначаемой (§12.21.3). Если преобразование удостоверений (§10.2.2) не выполняется (тип итерации) V в T (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 это тип значения или параметр типа, созданный в тип значения, преобразование e System.IDisposable не должно привести к возникновению бокса.

  • В противном случае, если E тип запечатан, finally предложение расширяется до пустого блока:

    finally {}
    
  • finally В противном случае предложение развернуто в следующих случаях:

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

Локальная переменная d не видна или недоступна для любого пользовательского кода. В частности, он не конфликтует с любой другой переменной, область которой включает finally блок.

Порядок, в котором foreach проходит элементы массива, выглядит следующим образом: для элементов одномерных массивов проходят по возрастанию порядка индексов, начиная с индекса 0 и заканчивая индексом Length – 1. Для многомерных массивов элементы пересекаются таким образом, чтобы индексы самого правого измерения были увеличены сначала, а затем следующее левое измерение и т. д. слева.

Пример. Следующий пример выводит каждое значение в двухмерном массиве в порядке элемента:

class Test
{
    static void Main()
    {
        double[,] values =
        {
            {1.2, 2.3, 3.4, 4.5},
            {5.6, 6.7, 7.8, 8.9}
        };
        foreach (double elementValue in values)
        {
            Console.Write($"{elementValue} ");
        }
        Console.WriteLine();
    }
}

Выходные данные создаются следующим образом:

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9

пример конца

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

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или forforeach оператора.

break_statement
    : 'break' ';'
    ;

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

Если несколькоswitchwhile, , doили forforeach операторы вложены друг в друга, 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, doforили foreach оператора.

continue_statement
    : 'continue' ';'
    ;

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

Если несколько whiledoоператоров или forforeach операторов вложены друг в друга, continue оператор применяется только к самой внутренней инструкции. Для передачи управления на нескольких уровнях goto вложения необходимо использовать оператор (§13.10.4).

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

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

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

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

13.10.4 Инструкция goto

Оператор goto передает элемент управления инструкции, помеченной меткой.

goto_statement
    : 'goto' identifier ';'
    | 'goto' 'case' constant_expression ';'
    | 'goto' 'default' ';'
    ;

Цель инструкции идентификатора goto — это помеченная инструкция с заданной меткой. Если метка с заданным именем не существует в текущем члене функции или если goto инструкция не находится в области метки, возникает ошибка во время компиляции.

Примечание. Это правило позволяет использовать инструкцию для передачи goto управления из вложенной области, но не в вложенную область. В примере

class Test
{
    static void Main(string[] args)
    {
        string[,] table =
        {
            {"Red", "Blue", "Green"},
            {"Monday", "Wednesday", "Friday"}
        };
        foreach (string str in args)
        {
            int row, colm;
            for (row = 0; row <= 1; ++row)
            {
                for (colm = 0; colm <= 2; ++colm)
                {
                    if (str == table[row,colm])
                    {
                        goto done;
                    }
                }
            }
            Console.WriteLine($"{str} not found");
            continue;
          done:
            Console.WriteLine($"Found {str} at [{row}][{colm}]");
        }
    }
}

goto Оператор используется для передачи элемента управления из вложенной области.

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

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

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

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

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

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

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

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

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

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

Return_statement без выражения называется возвращаемым значением; один ref из содержащих выражение называется return-by-ref; и один, содержащий только выражение, называется возвращаемым по значению.

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

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

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

Это ошибка времени компиляции для использования возвращаемого по ссылке, если выражение не является variable_reference или является ссылкой на переменную, ref-safe-context не является вызывающим контекстом (§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 Если инструкция заключена в один или несколько try catch блоков с связанными finally блоками, элемент управления изначально передается finally блоку самой try внутренней инструкции. Когда и если элемент управления достигает конечной finally точки блока, элемент управления передается finally в блок следующей заключающей инструкции try . Этот процесс повторяется до тех пор, finally пока блоки всех вложенных try инструкций не будут выполнены.
  • Если содержащая функция не является асинхронной, элемент управления возвращается вызывающей функции, содержащей функцию вместе со значением результата, если таковой имеется.
  • Если содержащая функция является асинхронной, элемент управления возвращается текущему вызывающому объекту, а результирующий значение, если таковой имеется, записывается в задаче возврата, как описано в статье 15.15.3.

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

13.10.6 Оператор throw

Оператор throw создает исключение.

throw_statement
    : 'throw' expression? ';'
    ;

Оператор throw с выражением создает исключение, созданное при оценке выражения. Выражение должно быть неявно System.Exceptionпреобразовано в, и результат оценки выражения преобразуется в System.Exception значение перед созданием. Если результат преобразования является nullрезультатом преобразования, System.NullReferenceException создается вместо него.

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

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

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

  • В текущем элементе функции проверяется каждая try инструкция, заключающая точку броска. Для каждой инструкции, начиная с самой внутренней try инструкции Sи заканчивая самой внешней 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

пример конца

Это ошибка во время компиляции для оператора или goto оператора для breakcontinueпередачи элемента управления из finally блока. breakcontinueПри возникновении в блоке finally инструкции или goto инструкции целевой объект инструкции должен находиться в одном finally блоке или в противном случае возникает ошибка во время компиляции.

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

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

Если исключение было распространено, предложения, если таковые имеются, catch рассматриваются в лексическом порядке искать первое совпадение для исключения. Поиск предложения сопоставления catch продолжается со всеми вложенными блоками, как описано в разделе 13.10.6. Предложение catch — это совпадение, если тип исключения соответствует любому exception_specifier , а любой exception_filter имеет значение true. Предложение 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 unchecked операторы точно эквивалентны checked операторам (§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();
    }
}

за исключением того, что приведение resource System.IDisposable не должно привести к возникновению бокса.

В противном случае, когда ResourceType это dynamicрасширение

{
    ResourceType resource = «expression»;
    IDisposable d = resource;
    try
    {
        «statement»;
    }
    finally
    {
        if (d != null)
        {
            d.Dispose();
        }
    }
}

В противном случае расширение равно

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        IDisposable d = (IDisposable)resource;
        if (d != null)
        {
            d.Dispose();
        }
    }
}

В любом расширении resource переменная доступна только для чтения в внедренной инструкции, и d переменная недоступна и невидима для внедренной инструкции.

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

Оператор using формы:

using («expression») «statement»

имеет те же три возможных расширения. В этом случае ResourceType неявно тип времени компиляции выражения, если он имеет один. В противном случае сам интерфейс IDisposable используется в качестве ResourceType. Переменная resource недоступна и невидима для внедренной инструкции.

Если resource_acquisition принимает форму local_variable_declaration, можно получить несколько ресурсов заданного типа. Оператор using формы

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»

точно эквивалентен последовательности вложенных using операторов:

using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»

Пример. В приведенном ниже примере создается файл с именем log.txt и записывается две строки текста в файл. Затем в этом примере открывается тот же файл для чтения и копирования содержащихся строк текста в консоль.

class Test
{
    static void Main()
    {
        using (TextWriter w = File.CreateText("log.txt"))
        {
            w.WriteLine("This is line one");
            w.WriteLine("This is line two");
        }
        using (TextReader r = File.OpenText("log.txt"))
        {
            string s;
            while ((s = r.ReadLine()) != null)
            {
                Console.WriteLine(s);
            }
        }
    }
}

TextWriter TextReader Так как классы реализуют IDisposable интерфейс, пример может использовать using инструкции, чтобы убедиться, что базовый файл правильно закрыт после операций записи или чтения.

пример конца

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

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

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

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

Существует несколько ограничений, в которых 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 инструкции к типу доходности (§15.14.4) итератора.

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

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

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

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

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

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