Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
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 (§24.2) и fixed_statement (§24.7) доступны только в небезопасном коде (§24).
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.25), которые управляют поведением операторов, но возможные значения неконстантных выражений не учитываются. Другими словами, в целях анализа потока управления неконстантное выражение заданного типа считается любым возможным значением этого типа.
В примере
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.15). Некоторые дополнительные ограничения применяются к блокам итератора:
- Это ошибка компиляции для оператора
return, появляющегося в блоке итератора (но операторыyield returnразрешены). - Это ошибка во время компиляции для блока итератора, содержащего небезопасный контекст (§24.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
;
Explicitly_typed_local_variable_declaration вводит одну или несколько локальных переменных с указанным типом.
Если local_variable_initializer присутствует , то его тип должен соответствовать правилам простого назначения (§12.23.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 должна иметь тип типа и соответствовать тем же требованиям, что и для назначения ссылок (§12.23.3).
Если ref_kindref readonly, то идентификаторы, которые объявляются, являются ссылками на переменные, которые рассматриваются как доступные только для чтения. В противном случае, если ref_kind равен ref, объявляемые идентификаторы являются ссылками на переменные, которые должны быть изменяемы.
Это ошибка времени компиляции, если объявить локальную переменную ref или переменную типа ref struct, в методе, объявленном с модификатором method_modifierasync, или внутри итератора (§15.15).
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.25), который дает значение константы.
Тип и 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.14) и один unsafe модификатор (§24.1). Если объявление включает async модификатор, возвращаемый тип должен быть void или «TaskType» тип (§15.14.1). Если объявление включает static модификатор, функция является статической локальной функцией; в противном случае это нестатичная локальная функция. Это ошибка во время компиляции для type_parameter_list или parameter_list для хранения атрибутов. Если локальная функция объявлена в небезопасном контексте (§24.2), локальная функция может включать небезопасный код, даже если объявление локальной функции не включает unsafe модификатор.
Локальная функция объявляется в области видимости блока. Нестатичная локальная функция может записывать переменные из включающей области, а статическую локальную функцию не должна (поэтому у нее нет доступа к закрытым локальным, параметрам, не статическим локальным функциям или this). Это ошибка во время компиляции, если записанная переменная считывается текстом нестатитической локальной функции, но не определенно назначается перед каждым вызовом функции. Компилятор должен определить, какие переменные определенно назначаются при возврате (§9.4.4.33).
Если тип структуры является типом this структуры, это ошибка во время компиляции для текста локальной функции для доступа this. Это верно, является ли доступ явным (как в this.x) или неявным (как в x, где x является членом экземпляра структуры). Это правило запрещает только такой доступ и не влияет на то, приводит ли поиск элементов к члену структуры.
Это ошибка во время компиляции для текста локальной функции, содержащей goto инструкцию, break инструкцию или continue оператор, целевой объект которого находится за пределами тела локальной функции.
Примечание. Приведенные выше правила и
thisgotoзеркальное отображение правил для анонимных функций в §12.21.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.26).
- Если логическое выражение дает
true, элемент управления передается в первую внедренную инструкцию. Когда и если элемент управления достигает конечной точки этого оператора, элемент управления передается в конечную точку инструкцииif. - Если логическое выражение дает
false, и если частьelseприсутствует, управление передается во вторую встроенную инструкцию. Когда и если элемент управления достигает конечной точки этого оператора, элемент управления передается в конечную точку инструкцииif. - Если логическое выражение возвращает
false, и если частьelseотсутствует, управление передается в конечную точку инструкцииif.
Первый вложенный оператор внутри инструкции if достижим, если оператор if достижим и логическое выражение не имеет постоянного значения false.
Второй вложенный оператор оператора if, если он присутствует, достижим, если оператор if достижим, и логическое выражение не имеет константного значения true.
Конечная точка инструкции if может быть достижима, если конечная точка по крайней мере одной из вложенных инструкций может быть достигнута. Кроме того, конечная точка if оператора без else части доступна, если if оператор доступен и логическое выражение не имеет константного значения true.
13.8.3 Инструкция switch
Оператор switch выбирает для выполнения список инструкций, имеющий связанную метку коммутатора, соответствующую значению selector_expression коммутатора.
switch_statement
: 'switch' selector_expression switch_block
;
selector_expression
: '(' expression ')'
| tuple_expression
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' null_coalescing_expression
;
Switch_statement состоит из ключевого словаswitch, за которым следует tuple_expression или круглые скобки (каждое из которых называется selector_expression), а затем switch_block.
switch_block состоит из нуля или более switch_sectionов, заключённых в фигурные скобки. Каждый switch_section состоит из одного или нескольких switch_label, за которым следует statement_list (§13.3.2). Каждый switch_label , содержащий case связанный шаблон (§11), по которому проверяется значение selector_expression коммутатора. Если case_guard присутствует, его выражение должно быть неявно преобразовано в тип bool , и это выражение оценивается как дополнительное условие для рассмотрения дела.
Примечание. Для удобства круглые скобки в switch_statement могут быть опущены, если selector_expression является tuple_expression. Например,
switch ((a, b)) …может быть записано какswitch (a, b) …. конечная заметка
Руководящий тип инструкции switch устанавливается selector_expression коммутатора.
- Если тип selector_expression коммутатора имеет
sbyteтип ,shortushortintcharulonguintbyteboolstringlongили enum_type, или если это тип значения NULL, соответствующий одному из этих типов, то это руководящий тип инструкции.switch - В противном случае, если одно определяемое пользователем неявное преобразование существует из типа selector_expression коммутатора к одному из следующих возможных типов управления:
sbyte,ushortlongstringshortbyteuintulongintcharили, допускающих значение NULL, соответствующее одному из этих типов, то преобразованный тип является руководящим типом инструкции.switch - В противном случае тип инструкции
switchявляется типом selector_expression коммутатора. Это ошибка, если такой тип отсутствует.
В инструкции default может быть не более одной метки switch.
Это ошибка, если шаблон любой метки коммутатора неприменимо (§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 выполняется следующим образом:
- Selector_expression коммутатора вычисляется и преобразуется в тип управления.
- Элемент управления передается в соответствии со значением преобразованного коммутатора selector_expression:
- Лексически первый шаблон в наборе
caseметок в той жеswitchинструкции, которая соответствует значению selector_expression коммутатора, и для которого выражение охранника либо отсутствует, либо оценивается как истинное, приводит к передаче элемента управления в список инструкций после соответствующей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.14.8),
switchоператор учитывает регистр и будет выполнять указанный раздел коммутатора, только если строка коммутатора selector_expression точно соответствуетcaseконстанте метки. конечная заметка
Если управляющий тип инструкции switch имеет string значение null или имеет значение NULL, это значение null допускается как case константа метки.
список_операторов в блоке_переключения может содержать операторы объявления (§13.6). Область локальной переменной или константы, объявленной в блоке коммутатора, является блоком коммутатора.
Метка переключателя доступна, если хотя бы одно из следующих условий истинно:
-
Selector_expression коммутатора является константным значением и либо
- Метка — это
case, чей шаблон совпадал бы (§11.2.1) с этим значением, и охранник метки отсутствует или не является постоянным выражением со значением false; или -
defaultэто метка, и ни один раздел switch не содержит метку case, шаблон которой будет соответствовать данному значению, и чьё условие либо отсутствует, либо является константным выражением со значением true.
- Метка — это
-
Selector_expression коммутатора не является константным значением и либо
- Метка — это
caseбез охранника или с охранником, значение которого не является константой false; или - это
defaultметка и- набор шаблонов, отображаемых среди случаев оператора switch, которые не имеют условий или имеют условия, значение которых является константой true, не являются исчерпывающими (§11.4) для типа, которым управляет оператор switch; или
- Тип, управляющий переключением, является nullable, и набор шаблонов, появляющихся среди случаев оператора switch, которые не имеют условий или содержат условия со значением константы true, не включает шаблон, который соответствовал бы значению
null.
- Метка — это
- Метка переключателя ссылается на доступную инструкцию
goto caseилиgoto default.
Список инструкций заданного раздела переключателя достижим, если switch оператор достижим, а раздел переключателя содержит достижимую метку переключателя.
Конечная точка switch оператора достижима, если оператор switch достижим, и по крайней мере одно из следующих истинно:
- Инструкция
switchсодержит доступнуюbreakинструкцию, которая завершает инструкциюswitch. - Метка
defaultотсутствует и либо- Selector_expression коммутатора является неконстантным значением, и набор шаблонов, отображаемых среди случаев инструкции switch, которая не имеет охранников или имеют охранников, значение которого является константой true, не является исчерпывающим (§11.4) для управляющего типа коммутатора.
-
Selector_expression коммутатора — это неконстантное значение типа, допускающего значение NULL, и ни один шаблон, отображаемый среди случаев оператора switch, у которого нет охранников или есть охранники, значение которого является константой true, будет соответствовать значению
null. -
Selector_expression коммутатора является константным значением и без
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.26).
- Если логическое выражение дает
true, управление передается во вложенное выражение. Когда и если элемент управления достигает конечной точки внедренного оператора (возможно, из выполненияcontinueинструкции), элемент управления передается в начало инструкцииwhile. - Если логическое выражение дает
false, управление передается в конец инструкцииwhile.
Вложенный оператор в операторе while, оператор break (§13.10.2) может использоваться для передачи управления к конечной точке оператору (завершая таким образом итерацию вложенного оператора), а оператор while (§13.10.3) может использоваться для передачи управления к конечной точке вложенной инструкции (тем самым выполняя другую итерацию continue оператора).
Вложенный оператор 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.26). Если логическое выражение даетtrue, ход выполнения передается к началу инструкцииdo. В противном случае элемент управления передается в конечную точку инструкцииdo.
Вложенный оператор в операторе do, оператор break (§13.10.2) может использоваться для передачи управления к конечной точке оператору (завершая таким образом итерацию вложенного оператора), а оператор do (§13.10.3) может использоваться для передачи управления к конечной точке вложенной инструкции (тем самым выполняя другую итерацию 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.26).
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
13.9.5.1 Общие
Инструкция foreach перечисляет элементы коллекции, выполняя внедренную инструкцию для каждого элемента коллекции.
foreach_statement
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
'in' expression ')' embedded_statement
;
Local_variable_type и идентификатор инструкции foreach объявляют переменную итерации инструкции.
var Если идентификатор присваивается в качестве local_variable_type, а имя типа var не находится в области, переменная итерации, как говорят, является неявно типизированной переменной итерации, и его тип принимается как тип foreach элемента инструкции, как указано ниже.
Это ошибка времени компиляции, если и await, и ref_kind присутствуют в foreach statement.
Если foreach_statement содержит либо оба, либо ни одного из ref и readonly, то переменная итерации обозначает переменную, которая рассматривается как только для чтения. В противном случае, если foreach_statement содержит ref без readonlyэтого, переменная итерации обозначает переменную, которая должна быть записываема.
Переменная итерации соответствует локальной переменной с областью действия, которая распространяется на встроенный оператор. Во время выполнения инструкции foreach переменная итерации представляет элемент коллекции, для которого выполняется итерация. Если переменная итерации обозначает переменную только для чтения, ошибка во время компиляции возникает, если внедренная инструкция пытается изменить ее (через назначение или ++-- операторы) или передать ее в качестве ссылочного или выходного параметра.
Обработка инструкции foreach во время компиляции сначала определяет тип коллекции (C), тип перечислителя (E) и тип итерации (Tref Tилиref readonly T) выражения.
Определение аналогично синхронным и асинхронным версиям. Различные интерфейсы с различными методами и возвращаемыми типами отличают синхронные и асинхронные версии. Общий процесс продолжается следующим образом. Имена в "" и """ являются заполнителями для фактических имен синхронных и асинхронных итераторов. Типы, разрешенные для GetEnumerator, MoveNext, IEnumerable»<T, IEnumerator»>T<>, и любые другие различия подробно описаны в §13.9.5.2 для синхронной инструкции и в foreach для асинхронной foreach инструкции.
- Определите, имеет ли тип
Xвыражения соответствующий метод GetEnumerator:- Выполните поиск элементов для типа
Xс идентификатором GetEnumerator и без аргументов типа. Если поиск элемента не создает совпадение или создает неоднозначность или создает совпадение, которое не является группой методов, проверьте наличие перечисленного интерфейса, как описано на шаге 2. Рекомендуется сделать предупреждение, если поиск элемента создает что-либо, кроме группы методов или отсутствия совпадения. - Выполните разрешение перегрузки с помощью результирующей группы методов и пустого списка аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется выдавать предупреждение, если разрешение перегрузки создает что-либо, кроме однозначного метода общедоступного экземпляра или нет применимых методов.
- Если возвращаемый тип
Eметода GetEnumerator не является классом, структурой или типом интерфейса, создайте ошибку и не выполните дальнейшие действия. - Выполните поиск
Eэлементов с идентификаторомCurrentи без аргументов типа. Если поиск элемента не соответствует, результатом является ошибка, или результатом является что-либо, кроме свойства общедоступного экземпляра, которое разрешает чтение, создает ошибку и не выполняет дальнейшие действия. - Выполните поиск
Eэлементов с идентификатором MoveNext и без аргументов типа. Если подстановка элемента не соответствует, результатом является ошибка, или результатом является что-либо, кроме группы методов, создайте ошибку и не выполните дальнейшие действия. - Выполните разрешение перегрузки в группе методов с пустым списком аргументов. Если разрешение перегрузки приводит к тому, что методы не применяются; неоднозначность; или один лучший метод, но этот метод является статическим, или не общедоступным, или его тип возврата не является допустимым типом возвращаемого значения; затем создайте ошибку и не выполните дальнейшие действия.
- Тип коллекции —
Xэто типEперечислителя, а тип итерации — это типCurrentсвойства.
- Выполните поиск элементов для типа
- В противном случае проверьте наличие перечисленного интерфейса:
- Если среди всех типов
Tᵢ, для которых существует неявное преобразование изX"IEnumerable"<Ti>, существует уникальный типT,Tкоторый неdynamicявляется, и для всех остальныхTᵢсуществует неявное преобразование из IEnumerable"T< в IEnumerable"><Ti>, то тип коллекции является интерфейсом "IEnumerable"T, тип перечислителя является интерфейсом "IEnumerator<">T<>, итерация имеет типT. - В противном случае, если существует несколько таких типов
T, создайте ошибку и не выполните дальнейшие действия.
- Если среди всех типов
Примечание. Если выражение имеет значение
null,System.NullReferenceExceptionвыбрасывается во время выполнения. конечная заметка
Реализация разрешена для реализации заданного foreach_statement по-разному; Например, по соображениям производительности, если поведение соответствует расширениям, описанным в разделе 13.9.5.2 и §13.9.5.3.
13.9.5.2 Синхронный foreach
Синхронный foreach не включает ключевое await слово перед ключевым словом foreach . Определение типа коллекции, типа перечисления и типа итерации продолжается, как описано в разделе §13.9.5.1, где:
- Метод GetEnumerator
GetEnumerator. - MoveNext — это
MoveNextметод с типом возвращаемогоboolзначения. - "IEnumerable"<T> — это
System.Collections.Generic.IEnumerable<T>интерфейс. - "IEnumerator"<T> — это
System.Collections.Generic.IEnumerator<T>интерфейс.
Кроме того, следующие изменения вносятся в шаги в §13.9.5.1:
Перед процессом, описанным в разделе "13.9.5.1", необходимо выполнить следующие действия:
- Если тип
Xявляется типом массива, то существует неявное преобразование ссылок изXIEnumerable<T>интерфейса, гдеTявляется тип элемента массиваX(§17.2.3). - Если тип
Xвыражения есть, то существует неявное преобразование из выражения вdynamicинтерфейс (§10.2.10).IEnumerableТип коллекции — интерфейсIEnumerable, а тип перечислителя —IEnumeratorинтерфейс.varЕсли идентификатор указан в качестве local_variable_type, то тип итерации имеет значениеdynamic, в противном случае —object.
Если процесс в §13.9.5.1 завершается без создания одного типа коллекции, типа перечислителя и типа итерации, выполняются следующие действия:
- Если есть неявное преобразование из
XSystem.Collections.IEnumerableинтерфейса, то тип коллекции — это интерфейс, тип перечислителя — интерфейсSystem.Collections.IEnumerator, а тип итерации — этоobjectинтерфейс. - В противном случае возникает ошибка, и дальнейшие действия не выполняются.
Оператор 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.23.3). Если отсутствует идентичное преобразование (§10.2.2) из (типа итерации) в T (V в инструкции ), производится ошибка, и дальнейшие действия не предпринимаются.
Оператор foreach формы foreach (ref readonly V v in x) «embedded_statement» имеет аналогичную эквивалентную форму, но ссылочная переменная v находится ref readonly в внедренной инструкции, поэтому не может быть переназначен или переназначен.
Размещение v внутри while цикла важно для отслеживания (§12.21.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.9.5.3 await foreach
Асинхронный элемент foreach использует await foreach синтаксис. Определение типа коллекции, типа перечисления и типа итерации продолжается, как описано в разделе §13.9.5.1, где:
- GetEnumerator — это
GetEnumeratorAsyncметод, имеющий ожидаемый тип возвращаемого значения (§12.9.9.2). - MoveNext — это
MoveNextAsyncметод, имеющий ожидаемый тип возвращаемого значения (§12.9.9.2), где await_expression классифицируется как типbool(§12.9.9.3). - "IEnumerable"<T> — это
System.Collections.Generic.IAsyncEnumerable<T>интерфейс. - "IEnumerator"<T> — это
System.Collections.Generic.IAsyncEnumerator<T>интерфейс.
Это ошибка для типа оператора, который должен быть ссылочной переменной (await foreach).
Оператор await foreach в форме
await foreach (T item in enumerable) «embedded_statement»
имеет семантический эквивалент:
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
// dispose of enumerator as described later in this clause.
}
В случае, когда выражение enumerable представляет выражение вызова метода, и один из параметров помечается с EnumeratorCancellationAttribute помощью метода (§23.5.8), CancellationToken который передается методу GetAsyncEnumerator . Для других методов библиотеки может потребоваться CancellationToken передаться GetAsyncEnumeratorв . Если эти методы являются частью выражения enumerable, маркеры должны объединяться в один маркер, как будто и CreateLinkedTokenSource его Token свойство.
Тело finally блока построено на следующих шагах:
Если
Eимеется доступныйDisposeAsync()метод, в котором тип возвращаемого значения ожидается (§12.9.9.2),finallyпредложение расширяется до семантического эквивалента:finally { await e.DisposeAsync(); }В противном случае, если есть неявное преобразование из
ESystem.IAsyncDisposableинтерфейса иEявляется типом ненулевого значения,finallyпредложение будет развернуто до семантического эквивалента:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }за исключением того, что если
Eэто тип значения или параметр типа, созданный в тип значения, преобразованиеeSystem.IAsyncDisposableне должно привести к возникновению упаковки.В противном случае, если
Eявляется типом и имеет доступныйref structDispose()метод,finallyпредложение расширяется до семантического эквивалента:finally { e.Dispose(); }В противном случае, если
E— закрытый тип, условиеfinallyрасширяется до пустого блока.finally {}В противном случае, выражение
finallyразвёртывается в следующее:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
Локальная переменная d не видна или недоступна для любого пользовательского кода. В частности, он не конфликтует с любой другой переменной, область которой включает finally блок.
Примечание. Не
await foreachобязательно удалятьeсинхронно, если недоступен асинхронный механизм удаления. конечная заметка
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 оператор применяется только к самому внутреннему оператору. Для передачи управления на нескольких уровнях 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 (§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 ';'
;
Оператор возврата без выражения называется возвратом без значения; оператор, содержащий выражение, называется ; и оператор, содержащий только ref, называется возвратом по значению.
Во время компиляции возникает ошибка, если использовать возврат без значения в методе, объявленном как возвращающий по значению или возврат по ссылке (§15.6.1).
Ошибка компиляции возникает при использовании возвращаемого по ссылке метода, объявленного как не возвращающий значение или возвращающий по значению.
Это ошибка времени компиляции — использовать возврат по значению из метода, объявленного как не возвращающего значение или возвращающего по ссылке.
Это ошибка времени компиляции для использования возвращения по ссылке, если выражение не является ссылкой_на_переменную или является ссылкой на переменную, чей безопасный_контекст_ссылки не является вызывающим контекстом (§9.7.2).
Это ошибка времени компиляции — использование возврата по ссылке из метода, объявленного с method_modifierasync.
Как сообщается, член функции вычисляет значение , если это метод с методом возвращаемого по значению (§15.6.11), методом доступа по значению свойства или индексатора или определяемым пользователем оператором. Члены функции, которые не возвращают значение, не вычисляют значение и являются методами с эффективным типом возврата void, задают методы доступа свойств и индексаторов, добавляют и удаляют методы доступа событий, конструкторы экземпляров, статические конструкторы и деструкторы. Члены функции, возвращаемые по ссылке, не вычисляют значение.
Для возвращения по значению должно существовать неявное преобразование (§10.2) от типа выражения к эффективному возвращаемому типу (§15.6.11) содержащего члена функции. Для возврата по ссылке должно существовать преобразование идентичности (§10.2.2) между типом выражения и эффективным типом возвращаемого члена функции.
return операторы также можно использовать в тексте анонимных выражений функций (§12.21) и участвовать в определении того, какие преобразования существуют для этих функций (§10.7.1).
Это ошибка времени компиляции, если оператор return появляется в блоке finally (§13.11).
Оператор return выполняется следующим образом:
- Для возврата по значению выражение вычисляется, и его значение преобразуется в эффективному типу возвращаемого значения функции путем неявного преобразования. Результат преобразования становится результатом, созданным функцией. Для возврата по ссылке выражение вычисляется, и результат должен быть классифицирован как переменная. Если в методе
readonlyиспользуется возврат по ссылке, то результирующая переменная будет доступна только для чтения. -
returnЕсли инструкция заключена в один или несколькоtrycatchблоков с связаннымиfinallyблоками, управление изначально передаетсяfinallyблоку самой внутреннейtryинструкции. Когда и если элемент управления достигает конечнойfinallyточки блока, элемент управления передаетсяfinallyв блок следующей заключающей инструкцииtry. Этот процесс повторяется до тех пор,finallyпока блоки всех обрамляющихtryинструкций не будут выполнены. - Если содержащая функция не является асинхронной, управление возвращается вызывающему коду вместе со значением результата, если такое имеется.
- Если содержащая функция является асинхронной, управление возвращается текущему вызывающему, а результирующее значение, если таковое имеется, записывается в задачу возврата, как описано в разделе (§15.14.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 не будет найдено предложение, соответствующее исключению. В этом описании точка выброса изначально является расположением, в котором выбрасывается исключение. Это поведение указано в (§22.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.14.3.
Если текущая функция является асинхронной и возвращает значение типа
void, контекст синхронизации текущего потока уведомляется, как описано в разделе 15.14.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 указывает тип исключения или базовый тип типа исключения. Если предложение содержит фильтр исключений, объект исключения назначается переменной исключения, а фильтр исключений вычисляется. Если оценка boolean_expression для exception_filter вызывает исключение, это исключение перехватывается, а фильтр исключений оценивается.false
Если исключение было распространено и 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
13.14.1 Общие
Оператор using получает один или несколько ресурсов, выполняет инструкцию, а затем удаляет ресурс.
using_statement
: 'await'? 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: non_ref_local_variable_declaration
| expression
;
non_ref_local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
;
Тип ресурса — это класс или структура, не относящуюся к ссылкам, реализующей либо оба или интерфейсы System.IDisposableSystem.IAsyncDisposable, которая включает один метод без параметров с именем Dispose и(или) DisposeAsyncили структуру ссылок, которая включает метод с Dispose тем же сигнатурой, что и объявленнаяSystem.IDisposable. Код, использующий ресурс, может вызывать Dispose или DisposeAsync указывать, что ресурс больше не нужен.
Если форма resource_acquisitionlocal_variable_declaration , тип local_variable_declaration должен быть либо dynamic типом ресурса. Если форма resource_acquisition является выражением , то это выражение должно иметь тип ресурса. Если await он присутствует, тип ресурса должен реализовываться System.IAsyncDisposable. Тип ref struct ресурса для using инструкции с модификатором не может быть типом await ресурса.
Локальные переменные, объявленные в resource_acquisition , доступны только для чтения, и должны включать инициализатор. Ошибка во время компиляции возникает, если внедренная инструкция пытается изменить эти локальные переменные (с помощью назначения или ++-- операторов), принять адрес или передать их в качестве ссылочных или выходных параметров.
Выражение using преобразуется в три части: приобретение, использование и удаление. Использование ресурса неявно заключено в try инструкцию, включающую finally предложение. Это finally предложение удаляет ресурс. Если выражение приобретения оценивается null, то вызов ( Dispose или DisposeAsync) не выполняется, и исключение не возникает. Если ресурс имеет тип dynamic , он динамически преобразуется с помощью неявного динамического преобразования (§10.2.10) в IDisposable (или IAsyncDisposable) во время приобретения, чтобы убедиться, что преобразование успешно выполнено до использования и удаления.
Оператор using, имеющий форму
using (ResourceType resource = «expression» ) «statement»
соответствует одной из трех возможных формул. Для ресурсов структуры класса и неref, если ResourceType это ненулевой тип значения или параметр типа с ограничением типа значения (§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();
}
}
}
Для ресурсов структуры ссылок единственным семантически эквивалентным формулировкам является
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
В любой формулировке переменная доступна только для чтения в внедренной инструкции resource , и d переменная недоступна и невидима для внедренной инструкции.
Выражение using в форме:
using («expression») «statement»
имеет те же возможные формулировки.
Если 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, чтобы убедиться в правильном закрытии базового файла после операций записи или чтения.конечный пример
Когда ResourceType является ссылочным типом, реализующим IAsyncDisposable. Другие формулировки для await using выполнения аналогичных подстановок синхронного метода с асинхронным DisposeDisposeAsync методом. Оператор await using в форме
await using (ResourceType resource = «expression» ) «statement»
семантически эквивалентен приведенным ниже IAsyncDisposable формулировкам вместо , IDisposableDisposeAsync а не DisposeTaskвозвращается DisposeAsyncawait:
await using (ResourceType resource = «expression» ) «statement»
семантически эквивалентен
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Примечание. Все инструкции перехода (§13.10) в embedded_statement должны соответствовать расширенной форме инструкции
using. конечная заметка
13.14.2 Использование объявления
Синтаксический вариант инструкции using — это объявление using.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Объявление using имеет ту же семантику, что и можно переписать, как и соответствующую форму приобретения ресурсов инструкции using (§13.14.1), как показано ниже.
using «local_variable_type» «local_variable_declarators»
// statements
семантически эквивалентен
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
и
await using «local_variable_type» «local_variable_declarators»
// statements
семантически эквивалентен
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
Время существования переменных, объявленных в non_ref_local_variable_declaration , расширяется до конца области, в которой они объявлены. Эти переменные затем удаляются в обратном порядке, в котором они объявляются.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Объявление об использовании не должно отображаться непосредственно внутри switch_label, но вместо этого может находиться в блоке внутри switch_label.
13.15 Оператор доходности
Оператор yield используется в блоке итератора (§13.3) для передачи значения объекту перечислителя (§15.15.5) или перечисляемому объекту (§15.15.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 к типу возвращаемого значения (§15.15.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 оператора никогда недоступен.
ECMA C# draft specification