Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
13.1 Общие
C# предоставляет различные инструкции.
Примечание. Большинство этих инструкций будут знакомы разработчикам, которые запрограммировали в C и C++. конечная заметка
statement
: labeled_statement
| declaration_statement
| embedded_statement
;
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| unsafe_statement // unsafe code support
| fixed_statement // unsafe code support
;
unsafe_statement (§23.2) и fixed_statement (§23.7) доступны только в небезопасном коде (§23).
Embedded_statement нетерминал используется для инструкций, которые отображаются в других инструкциях. Использование embedded_statement вместо инструкции исключает использование операторов объявления и помеченных инструкций в этих контекстах.
Пример: код
void F(bool b) { if (b) int i = 44; }
приводит к ошибке во время компиляции, так как
if
для оператора требуется embedded_statement , а не оператор для ветвиif
. Если этот код разрешен, то переменнаяi
будет объявлена, но ее нельзя использовать. Обратите внимание, что, поместивi
объявление в блок, пример действителен.конечный пример
13.2 Конечные точки и доступность
Каждая инструкция имеет конечную точку. Интуитивно конечная точка утверждения — это местоположение, которое сразу же следует за заявлением. Правила выполнения составных операторов (операторы, содержащие вложенные операторы) указывают действие, которое выполняется, когда управление достигает конечной точки вложенного оператора.
Пример. Когда элемент управления достигает конечной точки инструкции в блоке, элемент управления передается в следующую инструкцию в блоке. конец примера
Если заявление может быть достигнуто путем выполнения, заявление, как утверждается , доступно. И наоборот, если нет возможности, что заявление будет выполнено, говорится, что это заявление является недоступным.
Пример. В следующем коде
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }
Второй вызов Console.WriteLine недоступен, так как невозможно выполнить инструкцию.
заключительный пример
Предупреждение сообщается, если недоступен оператор, отличный от throw_statement, блок или empty_statement. В частности, не является ошибкой, если инструкция недостижима.
Примечание. Чтобы определить, доступен ли конкретный оператор или конечная точка, компилятор выполняет анализ потока в соответствии с правилами доступности, определенными для каждой инструкции. Анализ потока учитывает значения константных выражений (§12.23), которые управляют поведением операторов, но возможные значения неконстантных выражений не учитываются. Другими словами, в целях анализа потока управления неконстантное выражение заданного типа считается любым возможным значением этого типа.
В примере
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }
Логическое выражение
if
является константным выражением, так как оба операнда оператора==
являются константами. Так как константное выражение вычисляется во время компиляции, создавая значениеfalse
,Console.WriteLine
вызов считается недоступным. Однако, еслиi
изменяется на локальную переменнуюvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }
Console.WriteLine
Вызов считается доступным, хотя в действительности он никогда не будет выполнен.конечная заметка
Блокировка члена функции или анонимной функции всегда считается доступной. Последовательно оценивая правила доступности каждого оператора в блоке, можно определить доступность любого данного оператора.
Пример. В следующем коде
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }
Доступность второго
Console.WriteLine
определяется следующим образом:
- Первый
Console.WriteLine
оператор выражения доступен, так как блокF
метода доступен (§13.3).- Конечная точка первого
Console.WriteLine
оператора выражения является достижимой, так как этот оператор может быть достигнут (§13.7 и §13.3).- Оператор
if
доступен, так как конечная точка первогоConsole.WriteLine
оператора выражения достигается (§13.7 и §13.3).Console.WriteLine
Второй оператор выражения доступен, так как логическое выражение инструкцииif
не имеет константного значенияfalse
.конец примера
Существуют две ситуации, в которых возникает ошибка компиляции из-за достижимой конечной точки инструкции:
switch
Так как оператор не разрешает переход к следующему разделу коммутатора, это ошибка во время компиляции для конечной точки списка инструкций раздела коммутатора, доступного к нему. Если эта ошибка возникает, это обычно указывает на отсутствие оператораbreak
.Это ошибка во время компиляции для конечной точки блока элемента функции или анонимной функции, которая вычисляет значение, доступное для достижения. Если эта ошибка возникает, обычно это означает, что
return
инструкция отсутствует (§13.10.5).
13.3 Блоки
13.3.1 Общие
Блок block разрешает использовать множественные операторы в контекстах, где разрешён только один оператор.
block
: '{' statement_list? '}'
;
Блок состоит из необязательного списка инструкций (statement_list) (§13.3.2), заключенного в фигурные скобки. Если список заявлений опущен, блок считается пустым.
Блок может содержать инструкции декларации (§13.6). Область локальной переменной или константы, объявленной в блоке, является блоком.
Блок выполняется следующим образом:
- Если блок пуст, элемент управления передается в конечную точку блока.
- Если блок не пуст, элемент управления передается в список инструкций. Когда и если элемент управления достигает конечной точки списка инструкций, элемент управления передается в конечную точку блока.
Список инструкций блока достижим, если сам блок достижим.
Конечная точка блока может быть достигнута, если блок пуст или если конечный пункт списка инструкций доступен.
Блок, содержащий одну или несколько yield
инструкций (§13.15), называется блоком итератора. Блоки итератора используются для реализации элементов функции в качестве итераторов (§15.14). Некоторые дополнительные ограничения применяются к блокам итератора:
- Это ошибка компиляции для оператора
return
, появляющегося в блоке итератора (но операторыyield return
разрешены). - Это ошибка во время компиляции для блока итератора, содержащего небезопасный контекст (§23.2). Блок итератора всегда определяет безопасный контекст, даже если его объявление вложено в небезопасный контекст.
13.3.2 Список выражений
Список операторов состоит из одного или нескольких операторов, написанных последовательно. Списки операторов встречаются в blockах (§13.3) и в switch_blockах (§13.8.3).
statement_list
: statement+
;
Список инструкций выполняется путем передачи элемента управления в первую инструкцию. Когда и если элемент управления достигает конечной точки инструкции, элемент управления передается в следующую инструкцию. Когда и если элемент управления достигает конечной точки последней инструкции, элемент управления передается в конечную точку списка инструкций.
Утверждение в списке утверждений доступно, если хотя бы одно из следующих условий истинно:
- Выражение является первым выражением, и сам список выражений доступен.
- Конечная точка предыдущего утверждения достижима.
- Оператор является помеченной строкой, и на метку ссылается доступный
goto
оператор.
Конечная точка списка инструкций может быть достигнута, если конечный пункт последней инструкции в списке доступен.
13.4 Пустой оператор
empty_statement ничего не делает.
empty_statement
: ';'
;
Пустой оператор выполнения используется, когда отсутствуют операции в контексте, в котором требуется оператор выполнения.
Выполнение пустой инструкции просто передает контроль в конечную точку инструкции. Таким образом, конечная точка пустого оператора доступна, если доступна сама пустая инструкция.
Пример: пустой оператор можно использовать при написании инструкции
while
с пустым текстом:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }
Кроме того, пустую инструкцию можно использовать для объявления метки непосредственно перед закрытием блока
}
:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }
завершающий пример
Операторы с метками 13.5
Labeled_statement позволяет оператору префиксировать метку. Операторы с метками разрешены в блоках, но не допускаются как внедренные инструкции.
labeled_statement
: identifier ':' statement
;
Оператор с метками объявляет метку с именем, заданным идентификатором. Область метки — это весь блок, в котором объявлена метка, включая все вложенные блоки. Это ошибка времени компиляции, если две метки с одинаковым именем имеют перекрывающиеся области видимости.
Метка может ссылаться на goto
инструкции (§13.10.4) в пределах области метки.
Примечание. Это означает, что
goto
операторы могут передавать управление внутри блоков и вне блоков, но никогда не в блоки. конечная заметка
Метки имеют собственное пространство объявления и не вмешиваются в другие идентификаторы.
Пример: пример
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }
является допустимым и использует имя x как в качестве параметра, так и в качестве метки.
конец примера
Выполнение помеченной инструкции соответствует точно выполнению инструкции после метки.
В дополнение к достижимости, предоставляемой обычным потоком управления, помеченная инструкция достижима, если на метку ссылается достижимая goto
инструкция, если goto
инструкция не находится внутри try
блока или catch
блока try_statement, который содержит finally
блок, конечная точка которого недоступна, и помеченная инструкция находится за пределами try_statement.
Утверждения объявления 13.6
13.6.1 Общие
Declaration_statement объявляет одну или несколько локальных переменных, одну или несколько локальных констант или локальную функцию. Операторы объявления разрешены в блоках и блоках коммутаторов, но не допускаются как внедренные инструкции.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Локальная переменная объявляется с помощью local_variable_declaration (§13.6.2). Локальная константа объявляется с помощью local_constant_declaration (§13.6.3). Локальная функция объявляется с помощью local_function_declaration (§13.6.4).
Объявленные имена вводятся в ближайшее охватывающее пространство объявления (§7.3).
Объявления локальных переменных 13.6.2
13.6.2.1 General
Local_variable_declaration объявляет одну или несколько локальных переменных.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
Неявно типизированные объявления содержат контекстное ключевое слово (§6.4.4), var
что приводит к синтактической неоднозначности между тремя категориями, разрешаемой следующим образом:
- Если в области нет именованного
var
типа, а входные данные соответствуют implicitly_typed_local_variable_declaration выбрано; - В противном случае, если тип с именем
var
находится в области, implicitly_typed_local_variable_declaration не считается возможным совпадением.
В local_variable_declaration каждая переменная представлена декларатором, который является одним из implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator или ref_local_variable_declarator для неявно типизированных, явно типизированных и ссылочных локальных переменных соответственно. Декларатор определяет имя (идентификатор) и начальное значение, если таковые имеются, введенной переменной.
Если в объявлении есть несколько деклараторов, они обрабатываются, включая любые выражения инициализации, в порядке слева направо (§9.4.4.5).
Примечание. Для local_variable_declaration, который не является for_initializer (§13.9.4) или resource_acquisition (§13.14), этот порядок слева направо эквивалентен каждому декларатору, находящемуся в отдельном local_variable_declaration. Например:
void F() { int x = 1, y, z = x * 2; }
эквивалентно
void F() { int x = 1; int y; int z = x * 2; }
конечная заметка
Значение локальной переменной получается в выражении с помощью simple_name (§12.8.4). Локальная переменная должна быть определенно назначена (§9.4) в каждом расположении, где получается его значение. Каждая локальная переменная, представленная local_variable_declaration , изначально не назначена (§9.4.3). Если декларатор имеет выражение инициализации, введенная локальная переменная классифицируется как назначенная в конце декларатора (§9.4.4.5).
Область локальной переменной, введенной local_variable_declaration , определена следующим образом (§7.7):
- Если объявление происходит как for_initializer, тогда область — это for_initializer, for_condition, for_iterator и embedded_statement (§13.9.4);
- Если объявление является resource_acquisition, область охватывает самый внешний блок семантически эквивалентного расширения using_statement (§13.14);
- В противном случае область видимости — это блок, в котором происходит объявление.
Это ошибка обращаться к локальной переменной по имени в тексте перед объявлением или в любом выражении инициализации в её деклараторе. В области действия локальной переменной является ошибкой во время компиляции объявление другой локальной переменной, локальной функции или константы с тем же именем.
ref-safe-context (§9.7.2) локальной переменной ref является ref-safe-context ссылочного variable_reference, используемого при его инициализации. Контекст, безопасный для ссылок, нессылочных локальных переменных — это блок объявления.
13.6.2.2 Неявно типизированные объявления локальных переменных
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Операция, неявно типизирующая локальную переменную, объявляет единственную локальную переменную, идентификатор. Выражение или variable_reference должно иметь тип времени компиляции. T
Первая альтернатива объявляет переменную с начальным значением expression; ее тип — T?
, если T
является ссылочным типом, не допускающим значения NULL, в противном случае ее тип — T
. Вторая альтернатива объявляет переменную ref с начальным значением ref
variable_reference; ее тип — ref T?
, когда T
является ненулевым ссылочным типом, в противном случае его тип — ref T
. (ref_kind описано в разделе §15.6.1.)
Пример:
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;
Неявно типизированные объявления локальных переменных выше точно эквивалентны следующим явно типизированным объявлениям:
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;
Ниже приведены неправильные неявно типизированные объявления локальных переменных:
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itself
конец примера
13.6.2.3 Явно типизированные объявления локальных переменных
explicitly_typed_local_variable_declaration
: type explicitly_typed_local_variable_declarators
;
explicitly_typed_local_variable_declarators
: explicitly_typed_local_variable_declarator
(',' explicitly_typed_local_variable_declarator)*
;
explicitly_typed_local_variable_declarator
: identifier ('=' local_variable_initializer)?
;
local_variable_initializer
: expression
| array_initializer
;
Explicity_typed_local_variable_declaration вводит одну или несколько локальных переменных с указанным типом.
Если local_variable_initializer присутствует, то его тип должен соответствовать правилам простого назначения (§12.21.2) или инициализации массива (§17.7), а его значение назначается в качестве начального значения переменной.
13.6.2.4 Явно типизированные объявления локальных переменных типа ref
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
Инициализируемая variable_reference должна иметь тип type и соответствовать тем же требованиям, что и для присвоения ссылки (§12.21.3).
Если ref_kindref readonly
, то идентификаторы, которые объявляются, являются ссылками на переменные, которые рассматриваются как доступные только для чтения. В противном случае, если ref_kind равен ref
, объявляемые идентификаторы являются ссылками на переменные, которые должны быть изменяемы.
Это ошибка во время компиляции для объявления локальной переменной ref или переменной ref struct
типа, в методе, объявленном с , или внутри итератора (async
).
13.6.3 Локальные объявления констант
Local_constant_declaration объявляет одну или несколько локальных констант.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
Тип local_constant_declaration указывает тип констант, введенных объявлением. За типом следует список constant_declarators, каждый из которых представляет новую константу.
Constant_declarator состоит из идентификатора, который называет константу, за ним следует маркер "=
", а затем выражения константы (§12.23), которое определяет значение константы.
Тип и constant_expression локального объявления констант должны соответствовать тем же правилам, что и объявление константного члена (§15.4).
Значение локальной константы получается в выражении с помощью simple_name (§12.8.4).
Область локальной константы — это блок, в котором происходит объявление. Это ошибка, которая ссылается на локальную константу в текстовой позиции, которая предшествует концу его constant_declarator.
Локальное объявление констант, объявляющее несколько констант, эквивалентно нескольким объявлениям отдельных констант с одинаковым типом.
13.6.4 Объявления локальных функций
Local_function_declaration объявляет локальную функцию.
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
Примечание к грамматике: При распознавании local_function_body, если применимы оба варианта: null_conditional_invocation_expression и expression, то следует выбрать первый. (§15.6.1)
Пример. Существует два распространенных варианта использования локальных функций: методы итератора и асинхронные методы. В случае методов итератора исключения наблюдаются только при вызове кода, перечисляющего возвращенную последовательность. В асинхронных методах все исключения наблюдаются только при ожидании возвращаемой задачи. В следующем примере показано отделение проверки параметров от реализации итератора с использованием локальной функции:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }
конечный пример
Если иное не указано ниже, семантика всех элементов грамматики совпадает с method_declaration (§15.6.1), считывается в контексте локальной функции вместо метода.
Идентификатор объявления_локальной_функции должен быть уникальным в пределах заявленной области блока, включая все вложенные пространства объявлений локальных переменных. Одним из последствий этого является то, что перегруженные local_function_declarationне допускаются.
Local_function_declaration может включать модификатор async
(§15.15) и один unsafe
модификатор (§23.1). Если объявление включает async
модификатор, то возвращаемый тип должен быть void
или «TaskType»
тип (§15.15.1). Если объявление включает static
модификатор, функция является статической локальной функцией; в противном случае это нестатичная локальная функция. Это ошибка во время компиляции для type_parameter_list или parameter_list для хранения атрибутов. Если локальная функция объявлена в небезопасном контексте (§23.2), локальная функция может включать небезопасный код, даже если объявление локальной функции не включает unsafe
модификатор.
Локальная функция объявляется в области видимости блока. Нестатичная локальная функция может записывать переменные из включающей области, а статическую локальную функцию не должна (поэтому у нее нет доступа к закрытым локальным, параметрам, не статическим локальным функциям или this
). Это ошибка во время компиляции, если записанная переменная считывается текстом нестатитической локальной функции, но не определенно назначается перед каждым вызовом функции. Компилятор должен определить, какие переменные определенно назначаются при возврате (§9.4.4.33).
Если тип структуры является типом this
структуры, это ошибка во время компиляции для текста локальной функции для доступа this
. Это верно, является ли доступ явным (как в this.x
) или неявным (как в x
, где x
является членом экземпляра структуры). Это правило запрещает только такой доступ и не влияет на то, приводит ли поиск элементов к члену структуры.
Это ошибка во время компиляции для текста локальной функции, содержащей goto
инструкцию, break
инструкцию или continue
оператор, целевой объект которого находится за пределами тела локальной функции.
Примечание. Приведенные выше правила и
this
goto
зеркальное отображение правил для анонимных функций в §12.19.3. конечная заметка
Локальная функция может вызываться из лексической позиции до её объявления. Однако это ошибка времени компиляции для функции, объявленной лексически до того, как будет объявлена переменная, используемая в локальной функции (§7.7).
Ошибка компиляции возникает, если локальная функция объявляет параметр, параметр типа или локальную переменную с тем же именем, что и объявленный в любом окружающем пространстве объявления локальных переменных.
Локальные тела функций всегда достижимы. Конечная точка объявления локальной функции становится доступной, если можно достичь начальной точки объявления локальной функции.
Пример: В следующем примере тело
L
доступно, даже если начальная точкаL
недоступна. Так как начальная точкаL
недоступна, оператор, следующий за конечной точкойL
, недоступен:class C { int M() { L(); return 1; // Beginning of L is not reachable int L() { // The body of L is reachable return 2; } // Not reachable, because beginning point of L is not reachable return 3; } }
Другими словами, расположение объявления локальной функции не влияет на доступность каких-либо инструкций в содержащей функции. завершающий пример
Если тип аргумента для локальной функции имеет значение dynamic
, вызываемая функция должна быть разрешена во время компиляции, а не во время выполнения.
Локальная функция не должна использоваться в дереве выражений.
Статическую локальную функцию
- Могут ссылаться на статические члены, параметры типа, определения констант и статические локальные функции в охватывающей области.
- Не следует ссылаться ни на неявные ссылки
this
илиbase
, ни на элементы экземпляраthis
, а также на локальные переменные, параметры или нестатические локальные функции из охватывающего контекста. Однако все это разрешено вnameof()
выражении.
13.7 Операторы выражения
Expression_statement вычисляет заданное выражение. Значение, вычисленное выражением, если таковой имеется, удаляется.
expression_statement
: statement_expression ';'
;
statement_expression
: null_conditional_invocation_expression
| invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
;
Не все выражения разрешены в виде инструкций.
Примечание. В частности, выражения, такие как
x + y
иx == 1
, которые просто вычисляют значение (которое будет отменено), не допускаются в качестве инструкций. конечная заметка
Выполнение expression_statement приводит к вычислению содержащегося выражения, после чего управление передается в конечную точку expression_statement. Конечная точка expression_statement доступна, если expression_statement доступна.
13.8 Операторы выбора
13.8.1 Общие
Операторы выбора выбирают одну из возможных инструкций для выполнения на основе значения некоторого выражения.
selection_statement
: if_statement
| switch_statement
;
13.8.2 Оператор if
Оператор if
выбирает выражение для выполнения на основе значения булевского выражения.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Часть else
связана с предыдущим по лексической близости if
, который разрешен синтаксисом.
Пример: таким образом,
if
высказывание формыif (x) if (y) F(); else G();
эквивалентно
if (x) { if (y) { F(); } else { G(); } }
завершение примера
Оператор if
выполняется следующим образом:
- Вычисляется boolean_expression (§12.24).
- Если логическое выражение дает
true
, элемент управления передается в первую внедренную инструкцию. Когда и если элемент управления достигает конечной точки этого оператора, элемент управления передается в конечную точку инструкцииif
. - Если логическое выражение дает
false
, и если частьelse
присутствует, управление передается во вторую встроенную инструкцию. Когда и если контроль достигает конечной точки этого оператора, управление передается в конечную точку оператораif
. - Если логическое выражение возвращает
false
, и если частьelse
отсутствует, управление передается в конечную точку инструкцииif
.
Первый вложенный оператор внутри инструкции if
достижим, если оператор if
достижим и логическое выражение не имеет постоянного значения false
.
Второй вложенный оператор оператора if
, если он присутствует, достижим, если оператор if
достижим, и логическое выражение не имеет константного значения true
.
Конечная точка инструкции if
может быть достижима, если конечная точка по крайней мере одной из вложенных инструкций может быть достигнута. Кроме того, конечная точка if
оператора без else
части доступна, если if
оператор доступен и логическое выражение не имеет константного значения true
.
13.8.3 Инструкция switch
Оператор switch
выбирает для выполнения список инструкций, имеющий связанную метку коммутатора, соответствующую значению выражения switch.
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
switch_statement состоит из ключевого слова switch
, за которым следует выражение в скобках (называемое switch_expression), а затем switch_block.
switch_block состоит из нуля или более switch_sectionов, заключённых в фигурные скобки. Каждый switch_section состоит из одного или нескольких switch_label, за которым следует statement_list (§13.3.2). Каждый switch_label содержит связанный шаблон (§11), по которому проверяется значение переключающего выражения. Если case_guard присутствует, его выражение должно быть неявно преобразовано в тип bool
, и это выражение оценивается как дополнительное условие для рассмотрения дела.
Руководящий тип инструкции switch
устанавливается выражением switch.
- Если тип выражения переключателя - это
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,bool
,string
или enum_type, или если это обнуляемый тип, соответствующий одному из этих типов, то именно этот тип управляетswitch
оператором. - В противном случае, если существует ровно одно определенное пользователем неявное преобразование из типа выражения switch к одному из следующих возможных типов управления:
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,string
, или допускающий значение NULL тип, соответствующий одному из этих типов, то преобразованный тип является определяющим типом инструкцииswitch
. - В противном случае управляющим типом инструкции
switch
является тип выражения 'switch'. Это ошибка, если такой тип отсутствует.
В инструкции switch
может быть не более одной метки default
.
Это ошибка, если шаблон любой метки коммутатора неприменимо (§11.2.1) к типу входного выражения.
Это ошибка, если шаблон любой метки оператора switch охвачен (§11.3) набором шаблонов более ранних меток оператора switch, которые не имеют условия или чье условие является константным выражением со значением true.
Пример:
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }
завершающий пример
Оператор switch
выполняется следующим образом:
- Выражение коммутатора вычисляется и преобразуется в тип управления.
- Управление передается в соответствии со значением преобразованного выражения переключателя.
- Лексически первый шаблон в наборе
case
меток в той жеswitch
инструкции, который соответствует значению выражения switch и для которого выражение guard отсутствует или оценивается как истинное, вызывает передачу управления в список инструкций после соответствующейcase
метки. - В противном случае, если
default
метка присутствует, управление передается в список инструкций послеdefault
метки. - В противном случае элемент управления передается в конечную точку инструкции
switch
.
- Лексически первый шаблон в наборе
Примечание. Порядок сопоставления шаблонов во время выполнения не определен. Компилятор может (но не требуется) сопоставлять шаблоны вне порядка и повторно использовать результаты уже сопоставленных шаблонов для вычисления результата сопоставления других шаблонов. Тем не менее, компилятору требуется определить лексически первый шаблон, соответствующий выражению, и для которого охранное условие либо отсутствует, либо имеет значение
true
. конечная заметка
Если конечная точка списка инструкций раздела коммутатора недоступна, возникает ошибка во время компиляции. Это называется правилом "нет провала".
Пример: пример
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }
является допустимым, так как раздел переключателя не имеет доступную конечную точку. В отличие от C и C++, выполнение раздела коммутатора не допускается "переходить" к следующему разделу коммутатора, а пример
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }
приводит к ошибке во время компиляции. Если после выполнения одной секции switch следует выполнение другой секции, необходимо использовать явный
goto case
илиgoto default
оператор:switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }
пример конца
В switch_section разрешено несколько меток.
Пример: пример
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }
является допустимым. В примере не нарушается правило "без падения", так как метки
case 2:
и являются частью одной иdefault:
той же switch_section.пример конца
Примечание. Правило "без падения" предотвращает общий класс ошибок, возникающих в C и C++ при
break
случайном пропуске инструкций. Например, разделы приведеннойswitch
выше инструкции можно отменить, не влияя на поведение инструкции:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }
конечная заметка
Примечание. Список инструкций раздела переключателя обычно заканчивается оператором
break
,goto case
илиgoto default
, но любая конструкция, которая делает конечную точку списка инструкций недоступной, разрешается. Например, операторwhile
, контролируемый логическим выражениемtrue
, известно, что никогда не достигает конечной точки. Аналогичным образом, операторthrow
return
всегда передает контроль в другом месте и никогда не достигает конечной точки. Таким образом, следующий пример является правильным:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }
конечная заметка
Пример. Тип инструкции
switch
может быть типомstring
. Например:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }
концевой пример
Примечание. Как и операторы равенства строк (§12.12.8),
switch
оператор чувствителен к регистру и выполнит данный раздел коммутатора тогда и только тогда, когда строка выражения коммутатора в точности совпадает сcase
меткой-константой. конец примечания. Если управляющий тип инструкции являетсяswitch
или допускающим значение NULL типом значенийstring
, значениеnull
допускается какcase
константа метки.
список_операторов в блоке_переключения может содержать операторы объявления (§13.6). Область локальной переменной или константы, объявленной в блоке коммутатора, является блоком коммутатора.
Метка переключателя доступна, если хотя бы одно из следующих условий истинно:
- Выражение switch является константным значением и либо
- Метка — это
case
, чей шаблон совпадал бы (§11.2.1) с этим значением, и охранник метки отсутствует или не является постоянным выражением со значением false; или -
default
это метка, и ни один раздел switch не содержит метку case, шаблон которой будет соответствовать данному значению, и чьё условие либо отсутствует, либо является константным выражением со значением true.
- Метка — это
- Выражение коммутатора не является константным значением и
- Метка — это
case
без охранника или с охранником, значение которого не является константой false; или - это
default
метка и- набор шаблонов, отображаемых среди случаев оператора switch, которые не имеют условий или имеют условия, значение которых является константой true, не являются исчерпывающими (§11.4) для типа, которым управляет оператор switch; или
- Тип, управляющий переключением, является nullable, и набор шаблонов, появляющихся среди случаев оператора switch, которые не имеют условий или содержат условия со значением константы true, не включает шаблон, который соответствовал бы значению
null
.
- Метка — это
- Метка переключателя ссылается на доступную инструкцию
goto case
илиgoto default
.
Список инструкций заданного раздела переключателя достижим, если switch
оператор достижим, а раздел переключателя содержит достижимую метку переключателя.
Конечная точка switch
оператора достижима, если оператор switch достижим, и по крайней мере одно из следующих истинно:
- Инструкция
switch
содержит доступнуюbreak
инструкцию, которая завершает инструкциюswitch
. - Метка
default
отсутствует и либо- Выражение switch является неконстантным значением, и набор шаблонов, представленных в случаях оператора switch, не имеющих условий или имеющих условия со значением константы true, не является исчерпывающим (§11.4) для управляющего типа switch.
- Выражение переключателя — это неконстантное значение типа, допускающего значение null, и ни один из шаблонов, появляющихся среди случаев оператора switch без условий или с условиями, истинность которых является постоянной, не соответствует значению
null
. - Выражение switch является константным значением, и ни одна
case
метка без условия или с условием, являющимся константой истина, не будет соответствовать этому значению.
Пример. В следующем коде показано краткое использование
when
предложения:static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }
Переменная var совпадает с
null
, пустой строкой или любой строкой, содержащей только пробелы. завершающий пример
13.9 Операторы итерации
13.9.1 Общие
Операторы итерации многократно выполняют встроенную инструкцию.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 Оператор while
Оператор while
условно выполняет вложенный оператор ноль или более раз.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Оператор while
выполняется следующим образом:
- Вычисляется boolean_expression (§12.24).
- Если логическое выражение дает
true
, управление передается во вложенное выражение. Когда и если элемент управления достигает конечной точки внедренного оператора (возможно, из выполненияcontinue
инструкции), элемент управления передается в начало инструкцииwhile
. - Если логическое выражение дает
false
, управление передается в конец инструкцииwhile
.
Вложенный оператор в операторе while
, оператор break
(§13.10.2) может использоваться для передачи управления к конечной точке оператору while
(завершая таким образом итерацию вложенного оператора), а оператор continue
(§13.10.3) может использоваться для передачи управления к конечной точке вложенной инструкции (тем самым выполняя другую итерацию while
оператора).
Вложенный оператор while
доступен, если оператор while
доступен и булевое выражение не имеет постоянного значения false
.
Конечная точка while
оператора может быть достигнута, если по крайней мере одно из следующих значений имеет значение true:
- Инструкция
while
содержит доступнуюbreak
инструкцию, которая завершает инструкциюwhile
. - Оператор
while
достижимый, и логическое выражение не имеет константного значенияtrue
.
13.9.3 Инструкция do
Оператор do
условно выполняет вложенную инструкцию один или несколько раз.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Оператор do
выполняется следующим образом:
- Элемент управления передается в внедренную инструкцию.
- Когда и если управление достигает конечной точки внедренного оператора (возможно, вследствие выполнения
continue
инструкции), вычисляется boolean_expression (§12.24). Если логическое выражение даетtrue
, ход выполнения передается к началу инструкцииdo
. В противном случае управление переходит к конечной точке инструкцииdo
.
Внедренный оператор инструкций do
может включать break
оператор (§13.10.2 таким образом, заканчивая итерацию встроенной инструкции), а оператор do
(continue
) может использоваться для передачи управления в точку завершения встроенной инструкции, что позволяет выполнить еще одну итерацию оператора .
Вложенный оператор инструкции do
достижим, если оператор do
достижим.
Конечная точка do
оператора может быть достигнута, если по крайней мере одно из следующих значений имеет значение true:
- Инструкция
do
содержит доступнуюbreak
инструкцию, которая завершает инструкциюdo
. - Конечная точка встроенного оператора достигается, и логическое выражение не имеет константного значения
true
.
13.9.4 Инструкция for
Оператор for
вычисляет последовательность выражений инициализации, а затем, пока условие истинно, многократно выполняет встроенную инструкцию и вычисляет последовательность итерационных выражений.
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
For_initializer, если он присутствует, состоит из объявления локальной переменной (§13.6.2) или списка выражений (§13.7), разделенных запятыми. Область локальной переменной, объявленной for_initializer, — это for_initializer, for_condition, for_iterator и embedded_statement.
for_condition, если присутствует, должна быть boolean_expression (§12.24).
For_iterator, если присутствует, состоит из списка statement_expression(§13.7), разделенных запятыми.
Оператор for
выполняется следующим образом:
- Если for_initializer присутствует, инициализаторы переменных или выражения инструкций выполняются в том порядке, в котором они записываются. Этот шаг выполняется только один раз.
- Если for_condition присутствует, она вычисляется.
-
Если for_condition отсутствует или если оценка дает
true
, управление передается во вложенную инструкцию. Когда и если управление достигает конечной точки внедренного оператора (возможно, из выполненияcontinue
инструкции), выражения for_iterator, если таковые имеются, вычисляются последовательно, а затем выполняется ещё одна итерация, начиная с оценки for_condition на вышеупомянутом шаге. - Если for_condition присутствует и оценка дает результат
false
, то управление передается в конечную точку инструкцииfor
.
В встроенном операторе for
, оператор break
(§13.10.2) может использоваться для передачи управления в конечную точку for
оператора (тем самым заканчивая итерацию встроенного оператора), а оператор continue
(§13.10.3) может использоваться для передачи управления в конечную точку встроенного оператора (тем самым выполняя for_iterator и выполняя другую итерацию оператора for
, начиная с for_condition).
Внедренный оператор оператора for
доступен, если одно из следующих значений имеет значение true:
- Оператор
for
достижим, и условие for отсутствует. - Оператор
for
достижим, и for_condition присутствует и не имеет постоянного значенияfalse
.
Конечная точка for
оператора может быть достигнута, если по крайней мере одно из следующих значений имеет значение true:
- Инструкция
for
содержит доступнуюbreak
инструкцию, которая завершает инструкциюfor
. - Оператор
for
доступен, и for_condition присутствует и не имеет константного значенияtrue
.
13.9.5 Оператор foreach
Инструкция foreach
перечисляет элементы коллекции, выполняя внедренную инструкцию для каждого элемента коллекции.
foreach_statement
: 'foreach' '(' ref_kind? local_variable_type identifier 'in'
expression ')' embedded_statement
;
Local_variable_type и идентификатор инструкции foreach объявляют переменную итерации инструкции.
var
Если идентификатор присваивается в качестве local_variable_type, а имя типа var
не находится в области, переменная итерации, как говорят, является неявно типизированной переменной итерации, и его тип принимается как тип foreach
элемента инструкции, как указано ниже.
Если foreach_statement содержит либо оба, либо ни одного из ref
и readonly
, то переменная итерации обозначает переменную, которая рассматривается как только для чтения. В противном случае, если foreach_statement содержит ref
без readonly
этого, переменная итерации обозначает переменную, которая должна быть записываема.
Переменная итерации соответствует локальной переменной с областью действия, которая распространяется на встроенный оператор. Во время выполнения инструкции foreach
переменная итерации представляет элемент коллекции, для которого выполняется итерация. Если переменная итерации обозначает переменную только для чтения, ошибка во время компиляции возникает, если внедренная инструкция пытается изменить ее (через назначение или ++
--
операторы) или передать ее в качестве ссылочного или выходного параметра.
Во следующем, для краткости, IEnumerable
, IEnumerator
, IEnumerable<T>
и IEnumerator<T>
ссылаются на соответствующие типы в пространствах имен System.Collections
и System.Collections.Generic
.
Обработка инструкции foreach
во время компиляции сначала определяет тип коллекции, тип перечислителя и тип итерации выражения. Это определение изложено следующим образом:
- Если тип выражения является типом
X
массива Тип коллекции — этоIEnumerable
интерфейс, тип перечислителя — этоIEnumerator
интерфейс, а тип итерации — это тип элемента массива типаX
. - Если тип
X
выражения есть, то существует неявное преобразование из выражения вdynamic
интерфейс (§10.2.10).IEnumerable
Тип коллекции — интерфейсIEnumerable
, а тип перечислителя —IEnumerator
интерфейс.var
Если идентификатор указан в качестве local_variable_type, то тип итерации имеет значениеdynamic
, в противном случае —object
. - В противном случае определите, имеет ли тип
X
соответствующийGetEnumerator
метод:- Выполните поиск элементов для типа
X
с идентификаторомGetEnumerator
и без аргументов типа. Если поиск элемента не создает совпадение или создает неоднозначность или создает совпадение, которое не является группой методов, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется сделать предупреждение, если поиск элемента создает что-либо, кроме группы методов или отсутствия совпадения. - Выполните разрешение перегрузки с помощью результирующей группы методов и пустого списка аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется выдавать предупреждение, если разрешение перегрузки создает что-либо, кроме однозначного метода общедоступного экземпляра или нет применимых методов.
- Если возвращаемый тип
E
GetEnumerator
метода не является классом, структурой или типом интерфейса, создается ошибка, и дальнейшие действия не выполняются. - Выполняется поиск членов на
E
с идентификаторомCurrent
и без аргументов типа. Если поиск члена не дает совпадения, результат будет ошибкой, или если результатом является что-либо, кроме общедоступного свойства экземпляра, которое разрешает чтение, возникает ошибка, и дальнейшие шаги не предпринимаются. - Выполняется поиск членов
E
с идентификаторомMoveNext
и без аргументов типа. Если поиск элемента не дает совпадений, возникает ошибка или результатом является что-либо, кроме группы методов, создается ошибка, и никакие дальнейшие действия не выполняются. - Разрешение перегрузки выполняется в группе методов с пустым списком аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, или его тип возвращаемого значения отсутствует
bool
, возникает ошибка и дальнейшие действия не выполняются. - Тип коллекции —
X
это типE
перечислителя, а тип итерации — это типCurrent
свойства. СвойствоCurrent
может включатьref
модификатор, в этом случае возвращаемое выражение является variable_reference (§9.5), которое при необходимости доступно только для чтения.
- Выполните поиск элементов для типа
- В противном случае проверьте наличие перечисленного интерфейса:
- Если среди всех типов
Tᵢ
, для которых есть неявное преобразование отX
кIEnumerable<Tᵢ>
, существует уникальный типT
такой, чтоT
не являетсяdynamic
и для всех остальныхTᵢ
существует неявное преобразование отIEnumerable<T>
кIEnumerable<Tᵢ>
, то тип коллекции — интерфейсIEnumerable<T>
, тип перечислителя — интерфейсIEnumerator<T>
, а тип итерации —T
. - В противном случае, если существует несколько таких типов
T
, возникает ошибка и дальнейшие действия не выполняются. - В противном случае, если есть неявное преобразование из
X
в интерфейсSystem.Collections.IEnumerable
, то тип коллекции — это интерфейс, тип перечислителя — интерфейсSystem.Collections.IEnumerator
, а тип итерации — интерфейсobject
. - В противном случае возникает ошибка, и дальнейшие действия не выполняются.
- Если среди всех типов
Приведенные выше шаги, если успешно, однозначно создают тип коллекции, тип C
перечислителя и тип E
T
итерации, ref T
или ref readonly T
. Оператор foreach
, имеющий форму
foreach (V v in x) «embedded_statement»
Это эквивалентно следующему:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Переменная e
не видна или недоступна для выражения x
или внедренной инструкции или любого другого исходного кода программы. Переменная v
доступна только для чтения во встроенном выражении. Если не существует явного преобразования (§10.3) из T
(типа итерации) в V
(local_variable_type в foreach
инструкции), создается ошибка и дальнейшие действия не выполняются.
Если переменная итерации является эталонной переменной (§9.7), foreach
оператор формы
foreach (ref V v in x) «embedded_statement»
Затем эквивалентен следующему:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
Переменная e
не видна или недоступна для выражения x
или внедренной инструкции или любого другого исходного кода программы. Эталонная переменная v
доступна для чтения и записи во вложенной инструкции, но v
не должна быть повторно назначена через ref(§12.21.3). Если отсутствует идентичное преобразование (§10.2.2) из T
(типа итерации) в V
(local_variable_type в инструкции foreach
), производится ошибка, и дальнейшие действия не предпринимаются.
Оператор foreach
формы foreach (ref readonly V v in x) «embedded_statement»
имеет аналогичную эквивалентную форму, но ссылочная переменная v
находится ref readonly
в внедренной инструкции, поэтому не может быть переназначен или переназначен.
Примечание. Если
x
имеет значениеnull
, выбрасываетсяSystem.NullReferenceException
во время выполнения. конечная заметка
Разрешается использовать разные способы реализации заданного foreach_statement, например, по соображениям производительности, если поведение соответствует описанному выше развертыванию.
Размещение v
внутри while
цикла значимо для захвата (§12.19.6.2) любой анонимной функцией, встречающейся в embedded_statement.
Пример:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();
Если
v
было объявлено в развернутой форме внеwhile
цикла, оно будет использоваться всеми итерациями, и его значение послеfor
цикла будет окончательным значением13
, которое выведет вызовf
. Вместо этого, так как каждая итерация имеет свою собственную переменнуюv
, та, которая захваченаf
в первой итерации, будет продолжать содержать значение7
, которое будет напечатано. (Обратите внимание, что более ранние версии C# объявлялиv
вне циклаwhile
.)завершающий пример
Тело finally
блока построено на следующих шагах:
Если есть неявное преобразование из
E
в интерфейсSystem.IDisposable
, тоЕсли
E
является типом значения, не допускающим значение NULL,finally
предложение расширяется до семантического эквивалента:finally { ((System.IDisposable)e).Dispose(); }
finally
В противном случае предложение развертывается до семантического эквивалента:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
за исключением того, что если
E
это тип значения или параметр типа, созданный в тип значения, преобразованиеe
System.IDisposable
не должно привести к возникновению упаковки.
В противном случае, если
E
— закрытый тип, условиеfinally
расширяется до пустого блока.finally {}
В противном случае, выражение
finally
развёртывается в следующее:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
Локальная переменная d
не видна или недоступна для любого пользовательского кода. В частности, он не конфликтует с любой другой переменной, область которой включает finally
блок.
Порядок, в котором foreach
проходит элементы массива, выглядит следующим образом: для элементов одномерных массивов проходят по возрастанию порядка индексов, начиная с индекса 0 и заканчивая индексом Length – 1
. Для многомерных массивов элементы пересекаются таким образом, чтобы индексы самого правого измерения были увеличены сначала, а затем следующее левое измерение и т. д. слева.
Пример. Следующий пример выводит каждое значение в двухмерном массиве в порядке элемента:
class Test { static void Main() { double[,] values = { {1.2, 2.3, 3.4, 4.5}, {5.6, 6.7, 7.8, 8.9} }; foreach (double elementValue in values) { Console.Write($"{elementValue} "); } Console.WriteLine(); } }
Результат создается следующим образом:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9
заключительный пример
Пример. В следующем примере
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }
Тип
n
выводится как типint
, который является типом итерацииnumbers
.заключительный пример
13.10 Операторы перехода
13.10.1 Общие
Инструкции перехода безусловно передают управление.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
Место, куда оператор перехода передает управление, называется целью оператора перехода.
Когда оператор перехода происходит внутри блока, и цель этого оператора находится за пределами этого блока, говорится, что оператор выходит из блока. Хотя оператор перехода может передавать элемент управления из блока, он никогда не может передавать управление в блок.
Выполнение инструкций перехода сложно благодаря присутствию промежуточных try
инструкций. В отсутствие таких try
заявлений оператор прыжка безоговорочно передает контроль от оператора перехода к целевому объекту. В присутствии таких промежуточных try
инструкций выполнение является более сложным. Если оператор перехода выходит из одного или нескольких try
блоков, связанных с finally
блоками, управление изначально передается в finally
блок самой внутренней try
инструкции. Когда и если элемент управления достигает конечной finally
точки блока, элемент управления передается finally
в блок следующей заключающей инструкции try
. Этот процесс повторяется до тех пор, пока finally
блоки всех промежуточных try
инструкций не будут выполнены.
Пример. В следующем коде
class Test { static void Main() { while (true) { try { try { Console.WriteLine("Before break"); break; } finally { Console.WriteLine("Innermost finally block"); } } finally { Console.WriteLine("Outermost finally block"); } } Console.WriteLine("After break"); } }
finally
Блоки, связанные с двумяtry
операторами, выполняются перед передачей элемента управления в целевой объект инструкции jump. Результат создается следующим образом:Before break Innermost finally block Outermost finally block After break
конечный пример
13.10.2 Оператор перерыва
Оператор break
выходит из ближайшего включающего оператора switch
, оператора while
, оператора do
, оператора 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
оператор , do
for
или foreach
оператор, возникает ошибка во время компиляции.
Если несколько while
, do
, for
или foreach
операторов вложены друг в друга, continue
оператор применяется только к самому внутреннему оператору. Для передачи управления через несколько уровней вложенности, следует использовать оператор (§13.10.4).
Оператор continue
не может выйти из finally
блока (§13.11).
continue
При возникновении инструкции в finally
блоке целевой объект инструкции continue
должен находиться в одном finally
блоке; в противном случае возникает ошибка во время компиляции.
Оператор continue
выполняется следующим образом:
-
continue
Если оператор выходит из одного или несколькихtry
блоков с привязаннымиfinally
блоками, управление изначально передается вfinally
блок самой внутреннейtry
инструкции. Когда и если управление достигает конечной точки блокаfinally
, оно передается в блокfinally
следующей внешней инструкцииtry
. Этот процесс повторяется до тех пор, покаfinally
блоки всех промежуточныхtry
инструкций не будут выполнены. - Элемент управления передается в целевой объект инструкции
continue
.
continue
Поскольку оператор безоговорочно передает контроль в другом месте, конечный пункт continue
оператора никогда недоступен.
13.10.4 Инструкция "goto"
Оператор goto
передает управление инструкции, помеченной меткой.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
Цель инструкции идентификатора goto
— это помеченная инструкция с заданной меткой. Если метка с заданным именем не существует в текущем члене функции или если goto
инструкция не находится в области метки, возникает ошибка во время компиляции.
Примечание. Это правило позволяет использовать инструкцию для передачи
goto
управления из вложенной области, но не в вложенную область. В примереclass Test { static void Main(string[] args) { string[,] table = { {"Red", "Blue", "Green"}, {"Monday", "Wednesday", "Friday"} }; foreach (string str in args) { int row, colm; for (row = 0; row <= 1; ++row) { for (colm = 0; colm <= 2; ++colm) { if (str == table[row,colm]) { goto done; } } } Console.WriteLine($"{str} not found"); continue; done: Console.WriteLine($"Found {str} at [{row}][{colm}]"); } } }
goto
Оператор используется для передачи элемента управления из вложенной области.конечная заметка
Цель инструкции goto case
— это список инструкций в немедленном заключенном switch
операторе (§13.8.3), который содержит case
метку с константным шаблоном заданного значения константы и без защиты.
goto case
Если оператор не заключён в switch
, если ближайший заключающий switch
оператор не содержит такого case
, или если constant_expression не может быть неявно преобразовано (§10.2) в управляющий тип ближайшего заключающего switch
оператора, возникает ошибка времени компиляции.
Цель инструкции goto default
— это список инструкций в непосредственно окружающем операторе switch
(§13.8.3), который содержит метку default
.
goto default
Если инструкция не заключена в switch
инструкцию или если ближайшая заключающая switch
инструкция не содержит default
метку, возникает ошибка компиляции.
Оператор goto
не может выйти из блока finally
(§13.11).
goto
При возникновении инструкции finally
в goto
блоке целевой объект инструкции должен находиться в одном finally
блоке или в противном случае возникает ошибка во время компиляции.
Оператор goto
выполняется следующим образом:
-
goto
Если оператор выходит из одного или несколькихtry
блоков с связаннымиfinally
блоками, элемент управления изначально передаетсяfinally
в блок самойtry
внутренней инструкции. Когда и если элемент управления достигает конечнойfinally
точки блока, элемент управления передаетсяfinally
в блок следующей заключающей инструкцииtry
. Этот процесс повторяется до тех пор, покаfinally
блоки всех промежуточныхtry
инструкций не будут выполнены. - Элемент управления передается в целевой объект инструкции
goto
.
goto
Поскольку оператор безоговорочно передает контроль в другом месте, конечный пункт goto
оператора никогда недоступен.
13.10.5 Инструкция return
Оператор return
возвращает управление текущему вызывающему объекту функции, где присутствует оператор return, при необходимости может вернуть значение или variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Оператор возврата без выражения называется возвратом без значения; оператор, содержащий выражение, называется возвратом по ссылке; и оператор, содержащий только выражение, называется возвратом по значению.
Во время компиляции возникает ошибка, если использовать возврат без значения в методе, объявленном как возвращающий по значению или возврат по ссылке (§15.6.1).
Ошибка компиляции возникает при использовании возвращаемого по ссылке метода, объявленного как не возвращающий значение или возвращающий по значению.
Это ошибка времени компиляции — использовать возврат по значению из метода, объявленного как не возвращающего значение или возвращающего по ссылке.
Это ошибка времени компиляции для использования возвращения по ссылке, если выражение не является ссылкой_на_переменную или является ссылкой на переменную, чей безопасный_контекст_ссылки не является вызывающим контекстом (§9.7.2).
Это ошибка времени компиляции — использование возврата по ссылке из метода, объявленного с method_modifierasync
.
Как сообщается, член функции вычисляет значение , если это метод с методом возвращаемого по значению (§15.6.11), методом доступа по значению свойства или индексатора или определяемым пользователем оператором. Члены функции, которые не возвращают значение, не вычисляют значение и являются методами с эффективным типом возврата void
, задают методы доступа свойств и индексаторов, добавляют и удаляют методы доступа событий, конструкторы экземпляров, статические конструкторы и деструкторы. Члены функции, возвращаемые по ссылке, не вычисляют значение.
Для возвращения по значению должно существовать неявное преобразование (§10.2) от типа выражения к эффективному возвращаемому типу (§15.6.11) содержащего члена функции. Для возврата по ссылке должно существовать преобразование идентичности (§10.2.2) между типом выражения и эффективным типом возвращаемого члена функции.
return
операторы также можно использовать в тексте анонимных выражений функций (§12.19) и участвовать в определении того, какие преобразования существуют для этих функций (§10.7.1).
Это ошибка времени компиляции, если оператор return
появляется в блоке finally
(§13.11).
Оператор return
выполняется следующим образом:
- Для возврата по значению выражение вычисляется, и его значение преобразуется в эффективному типу возвращаемого значения функции путем неявного преобразования. Результат преобразования становится результатом, созданным функцией. Для возврата по ссылке выражение вычисляется, и результат должен быть классифицирован как переменная. Если в методе
readonly
используется возврат по ссылке, то результирующая переменная будет доступна только для чтения. -
return
Если инструкция заключена в один или несколькоtry
catch
блоков с связаннымиfinally
блоками, управление изначально передаетсяfinally
блоку самой внутреннейtry
инструкции. Когда и если управление достигает конечной точки блокаfinally
, оно передается следующему заключающему блоку инструкцииtry
. Этот процесс повторяется до тех пор,finally
пока блоки всех обрамляющихtry
инструкций не будут выполнены. - Если содержащая функция не является асинхронной, управление возвращается вызывающему коду вместе со значением результата, если такое имеется.
- Если содержащая функция является асинхронной, управление возвращается текущему вызывающему, а результирующее значение, если таковое имеется, записывается в задаче возврата, как описано в разделе 15.15.3.
return
Поскольку оператор безоговорочно переносит контроль в другое место, конечная точка return
оператора никогда недостижима.
13.10.6 Оператор throw
Оператор throw
создает исключение.
throw_statement
: 'throw' expression? ';'
;
Оператор throw
с выражением создает исключение, созданное при оценке выражения. Выражение должно быть неявно преобразовано в System.Exception
, и результат оценки выражения преобразуется в System.Exception
перед выбросом. Если результат преобразования равен null
, вместо этого выбрасывается System.NullReferenceException
.
Оператор throw
без выражения может использоваться только в catch
блоке, в котором случае этот оператор повторно выбрасывает исключение, которое в настоящее время обрабатывается этим catch
блоком.
Поскольку оператор throw
безоговорочно передает управление в другое место, конечная точка оператора throw
никогда недоступна.
При возникновении исключения элемент управления передается первому catch
предложению в заключающей try
инструкции, которая может обрабатывать исключение. Процесс, который происходит с точки исключения, вызываемого до точки передачи элемента управления в подходящий обработчик исключений, называется распространением исключений. Распространение исключения состоит из многократного вычисления следующих шагов до тех пор, пока catch
не будет найдено предложение, соответствующее исключению. В этом описании точка выброса изначально является расположением, в котором выбрасывается исключение. Это поведение указано в (§21.4).
В текущем элементе функции проверяется каждая
try
инструкция, заключающая точку броска. Для каждой инструкции, начиная с самой внутреннейS
инструкцииtry
и заканчивая самой внешнейtry
инструкцией, вычисляются следующие действия:-
try
Если блокS
заключает точку броска иS
имеет одну или несколькоcatch
клауз,catch
клаузы проверяются в порядке появления, чтобы найти подходящий обработчик исключения. Первоеcatch
предложение, указывающее типT
исключения (или параметр типа, обозначающий типT
исключения во время выполнения), таким образом, что типE
времени выполнения наследуется отT
соответствия. Если предложение содержит фильтр исключений, объект исключения назначается переменной исключения, а фильтр исключений вычисляется. Еслиcatch
предложение содержит фильтр исключений, то этоcatch
предложение считается совпадением, если фильтр исключений оценивается какtrue
. Общееcatch
предложение (§13.11) считается совпадением для любого типа исключения.catch
Если находится соответствующее предложение, распространение исключений завершается путем передачи элемента управления в блок этогоcatch
предложения. - В противном случае, если
try
блок илиcatch
блокS
охватывает точку выброса и уS
естьfinally
блок, управление передается вfinally
блок. Если блокfinally
создает другое исключение, обработка текущего исключения завершается. В противном случае, когда элемент управления достигает конечной точки блока, обработка текущегоfinally
исключения продолжается.
-
Если обработчик исключений не был расположен в текущем вызове функции, вызов функции завершается, и происходит одно из следующих действий:
Если текущая функция не является асинхронной, описанные выше шаги повторяются для вызывающего функции с точкой вызова, соответствующей инструкции, из которой был вызван член функции.
Если текущая функция является асинхронной и возвращаемой задачей, исключение записывается в задаче возврата, которая помещается в состояние сбоя или отмены, как описано в разделе 15.15.3.
Если текущая функция является асинхронной и возвращает объект типа
void
, контекст синхронизации текущего потока уведомляется, как описано в разделе 15.15.4.
Если обработка исключений завершает вызов всех элементов функции в текущем потоке, указывая, что поток не имеет обработчика исключения, то сам поток завершается. Влияние такого прекращения определяется реализацией.
13.11 Инструкция try
Оператор try
предоставляет механизм для перехвата исключений, возникающих во время выполнения блока кода. Кроме того, инструкция try
предоставляет возможность указывать блок кода, который всегда выполняется, когда управление программой покидает инструкцию try
.
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
Try_statement состоит из ключевого слова, за которым следует try
, а затем ноль или более catch_clauses, а затем необязательный finally_clause. Должно быть не менее одного catch_clause или finally_clause.
В exception_specifierтип или его действующий базовый класс, если он является type_parameter, должен быть System.Exception
или тип, производный от него.
catch
Если class_type и идентификатор указаны в предложении, то объявляется переменная исключения с заданными именем и типом. Переменная исключения вводится в область объявления для specific_catch_clause (§7.3). Во время выполнения exception_filter и catch
блока переменная исключения представляет исключение, которое в настоящее время обрабатывается. В целях проверки определенного назначения переменная исключения считается определенно назначенной в всей области.
Если catch
предложение не содержит имя переменной исключения, доступ к объекту исключения в фильтре и catch
блоке невозможно.
Предложение catch
, не указывающее тип исключения или имя переменной исключения, называется общим catch
предложением. Оператор try
может иметь только одно общее catch
условие, и, если оно присутствует, это должно быть последнее catch
условие.
Примечание. Некоторые языки программирования могут поддерживать исключения, которые не представляются как объект, производный от
System.Exception
, хотя такие исключения никогда не могут быть созданы кодом C#. Общийcatch
оператор может использоваться для перехвата таких исключений. Таким образом, генеральноеcatch
правило семантически отличается от того, которое задает типSystem.Exception
, тем, что первое может также обрабатывать исключения, возникающие в других языках. конечная заметка
Чтобы найти обработчик исключения, catch
предложения рассматриваются в лексическом порядке.
catch
Если в предложении указан тип, но не фильтр исключений, это будет ошибкой во время компиляции, если более поздняя catch
инструкция try
указывает тип, который совпадает с этим типом или является его производным.
Примечание. Без этого ограничения можно было бы написать недоступные
catch
предложения. конечная заметка
В блоке catch
оператор throw
(§13.10.6) без выражения можно использовать для повторного выброса исключения, пойманного блоком catch
. Присвоения переменной исключения не изменяют исключение, которое повторно выбрасывается.
Пример. В следующем коде
class Test { static void F() { try { G(); } catch (Exception e) { Console.WriteLine("Exception in F: " + e.Message); e = new Exception("F"); throw; // re-throw } } static void G() => throw new Exception("G"); static void Main() { try { F(); } catch (Exception e) { Console.WriteLine("Exception in Main: " + e.Message); } } }
Метод
F
перехватывает исключение, записывает некоторые диагностические сведения в консоль, изменяет переменную исключения и повторно создает исключение. Исключение, которое создается повторно, является исходным исключением, поэтому выходные данные создаются:Exception in F: G Exception in Main: G
Если первый
catch
блок бросилe
вместо повторного выброса текущего исключения, выходные данные будут выглядеть следующим образом:Exception in F: G Exception in Main: F
конец примера
Это ошибка времени компиляции, если оператор break
, continue
или goto
пытается перенести управление из блока finally
. Когда оператор break
, continue
или goto
возникает в блоке finally
, целевой объект оператора должен находиться в том же блоке finally
, иначе возникает ошибка компиляции.
Это ошибка во время компиляции для инструкции, return
возникающей в блоке finally
.
Когда выполнение достигает инструкции try
, элемент управления передается в try
блок. Если элемент управления достигает конечной try
точки блока без исключения, элемент управления передается finally
в блок, если он существует. Если блок finally
не существует, управление передается в конечную точку оператора try
.
Если исключение было распространено, предложения catch
, если они есть, последовательно проверяются в лексическом порядке, чтобы найти первое совпадение для исключения. Поиск подходящего предложения catch
продолжается по всем вложенным блокам, как описано в разделе 13.10.6. Предложение catch
считается подходящим, если тип исключения соответствует любому exception_specifier, а любой exception_filter истинен. Предложение catch
без exception_specifier соответствует любому типу исключения. Тип исключения соответствует exception_specifier, если exception_specifier указывает тип исключения или базовый тип типа исключения. Если предложение содержит фильтр исключений, объект исключения назначается переменной исключения, а фильтр исключений вычисляется.
Если исключение было распространено и catch
найдено соответствующее предложение, элемент управления передается в первый блок сопоставления catch
. Если поток управления достигает конца блока catch
без распространения исключений, он передается в блок finally
, если он существует. Если блок finally
не существует, управление передается к конечной точке оператора try
. Если исключение было распространено из блока catch
, управление передается блоку finally
, если он существует. Исключение распространяется на следующую заключиющую инструкцию try
.
Если исключение было распространено, и предложение сопоставления catch
не найдено, контроль передается в finally
блок, если он существует. Исключение распространяется на следующую заключиющую инструкцию try
.
Операторы блока finally
всегда выполняются, когда управление выходит из оператора try
. Это верно, происходит ли передача элемента управления в результате нормального выполнения, в результате выполнения break
инструкции , continue
goto
или return
инструкции или в результате распространения исключения из инструкцииtry
. Если управление достигает конечной точки блока finally
без распространения исключения, оно передается в конечную точку оператора try
.
Если исключение возникает во время выполнения finally
блока и не перехватывается в том же finally
блоке, исключение передается следующей заключающей try
инструкции. Если другое исключение было в процессе распространения, это исключение будет потеряно. Процесс распространения исключения рассматривается далее в описании инструкции throw
(§13.10.6).
Пример. В следующем коде
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }
Метод
Method
вызывает исключение. Первое действие — проверить вложенныеcatch
предложения, выполняя все фильтры исключений.finally
Затем выполняется предложение вMethod
, прежде чем управление будет передано в соответствующее заключенное предложениеcatch
. Результирующий результат:Filter Finally Catch
заключительный пример
Блок try
оператора try
достижим, если оператор try
достижим.
Блок catch
инструкции try
достижим, если инструкция try
достижима.
Блок finally
оператора try
доступен, если оператор try
доступен.
Конечная точка try
оператора может быть достигнута, если оба из следующих значений имеют значение true:
- Конечная точка
try
блока может быть достигнута или конечная точка по крайней мере одногоcatch
блока может быть достигнута. - Если блок
finally
присутствует, конечная точка блокаfinally
становится достижима.
13.12 Проверенные и непроверенные инструкции
Инструкции checked
и unchecked
используются для управления проверкой контекста переполнения для арифметических операций и преобразований целочисленного типа .
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
Оператор checked
приводит к оценке всех выражений в блоке в проверяемом контексте, а оператор unchecked
приводит к оценке всех выражений в блоке в непроверяемом контексте.
Операторы checked
и unchecked
точно эквивалентны операторам checked
и unchecked
(§12.8.20), за исключением того, что они действуют на блоках вместо выражений.
13.13 Инструкция блокировки
Оператор lock
получает блокировку взаимного исключения для заданного объекта, выполняет инструкцию, а затем освобождает блокировку.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
Выражение должно указывать значение типа, известного lock
. Неявное преобразование бокса (§10.2.9) никогда не выполняется для выраженияlock
инструкции, и, следовательно, в случае, если выражение обозначает значение типа value_type, это является ошибкой компиляции.
Выражение lock
формы
lock (x)
…
где x
является выражением reference_type, точно эквивалентно следующим:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
за исключением того, что x
вычисляется только один раз.
Хотя блокировка взаимного исключения сохраняется, код, выполняемый в одном потоке выполнения, также может получить и освободить блокировку. Однако код, выполняемый в других потоках, блокируется от получения блокировки до тех пор, пока блокировка не будет освобождена.
13.14 Инструкция using
Оператор using
получает один или несколько ресурсов, выполняет инструкцию, а затем удаляет ресурс.
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
Ресурс — это класс или структура, реализующая System.IDisposable
интерфейс, который включает в себя один метод Dispose
без параметров. Код, использующий ресурс, может Dispose
вызвать, чтобы указать, что ресурс больше не нужен.
Если форма resource_acquisition — это local_variable_declaration, то тип local_variable_declaration должен быть либо dynamic
, либо типом, который можно неявно преобразовать в System.IDisposable
. Если форма resource_acquisition — это выражение, то это выражение должно быть неявно преобразовано в System.IDisposable
.
Локальные переменные, объявленные в resource_acquisition , доступны только для чтения, и должны включать инициализатор. Ошибка во время компиляции возникает, если внедренная инструкция пытается изменить эти локальные переменные (с помощью назначения или ++
--
операторов), принять адрес или передать их в качестве ссылочных или выходных параметров.
Выражение using
преобразуется в три части: приобретение, использование и удаление. Использование ресурса неявно заключено в try
инструкцию, включающую finally
предложение. Это finally
предложение удаляет ресурс. Если null
ресурс получен, то вызов Dispose
не выполняется, и исключение не возникает. Если ресурс имеет тип dynamic
, он динамически преобразуется через неявное динамическое преобразование (§10.2.10) в IDisposable
во время приобретения, чтобы убедиться в успешности преобразования перед использованием и удалением.
Конструкция using
формы
using (ResourceType resource = «expression» ) «statement»
соответствует одному из трех возможных расширений. Если ResourceType
имеет тип значения, не допускающий значение NULL, или параметр типа с ограничением типа значения (§15.2.5), расширение семантически эквивалентно
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
за исключением того, что приведение resource
System.IDisposable
не должно привести к возникновению бокса.
В противном случае, когда ResourceType
, dynamic
, происходит расширение.
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
В противном случае расширение будет...
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
В любом расширении resource
переменная доступна только для чтения в внедренной инструкции, и d
переменная недоступна и невидима для внедренной инструкции.
Реализация может реализовывать заданный using_statement по-разному, например по соображениям производительности, если поведение соответствует расширению, описанному выше.
Выражение using
в форме:
using («expression») «statement»
имеет те же три возможных расширения. В этом случае ResourceType
является неявным типом времени компиляции для выражения, если он у него есть. В противном случае сам интерфейс IDisposable
используется в качестве ResourceType
. Переменная resource
недоступна и невидима для внедренной инструкции.
Если resource_acquisition принимает форму local_variable_declaration, можно получить несколько ресурсов заданного типа. Оператор вида using
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
точно эквивалентен последовательности вложенных using
операторов:
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Пример. В приведенном ниже примере создается файл с именем log.txt и записывается две строки текста в файл. Затем в этом примере открывается тот же файл для чтения и копирования содержащихся строк текста в консоль.
class Test { static void Main() { using (TextWriter w = File.CreateText("log.txt")) { w.WriteLine("This is line one"); w.WriteLine("This is line two"); } using (TextReader r = File.OpenText("log.txt")) { string s; while ((s = r.ReadLine()) != null) { Console.WriteLine(s); } } } }
Так как классы
TextWriter
иTextReader
реализуют интерфейсIDisposable
, в примере можно использовать инструкцииusing
, чтобы убедиться в правильном закрытии базового файла после операций записи или чтения.конец примера
13.15 Оператор доходности
Оператор yield
используется в блоке итератора (§13.3), чтобы получить значение объекту перечислителя (§15.14.5) или перечислению объекта (§15.14.6) итератора или сигнализировать о завершении итерации.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield
— это контекстное ключевое слово (§6.4.4), имеющее особое значение только при использовании непосредственно перед ключевым словом return
или break
.
Есть несколько ограничений на то, где может появляться инструкция yield
, как описано в следующем описании.
- Это ошибка компиляции, если инструкция
yield
(любой формы) появляется вне method_body, operator_body или accessor_body. - Это ошибка компиляции, когда
yield
инструкция (любой формы) появляется внутри анонимной функции. - Это ошибка компиляции, если
yield
инструкция (любой формы) появляется в частиfinally
инструкцииtry
. - Это ошибка в момент компиляции для
yield return
оператора, встречающегося в любом местеtry
оператора, содержащего любые catch_clauses.
Пример. В следующем примере показаны некоторые допустимые и недопустимые использование инструкций
yield
.delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // Error, yield in finally yield break; // Error, yield in finally } try { yield return 3; // Error, yield return in try/catch yield break; // Ok } catch { yield return 4; // Error, yield return in try/catch yield break; // Ok } D d = delegate { yield return 5; // Error, yield in an anonymous function }; } int MyMethod() { yield return 1; // Error, wrong return type for an iterator block }
конец примера
Неявное преобразование (§10.2) должно существовать от типа выражения в инструкцииyield return
к типу yield (§15.14.4) итератора.
Оператор yield return
выполняется следующим образом:
- Выражение, заданное в инструкции, вычисляется, неявно преобразуется в тип доходности и назначается
Current
свойству объекта перечислителя. - Выполнение блока итератора приостановлено.
yield return
Если оператор находится в одном или несколькихtry
блоках, связанныеfinally
блоки не выполняются на данный момент. - Метод
MoveNext
объекта перечислителя возвращаетtrue
вызывающему, указывая, что объект перечислителя успешно продвинулся к следующему элементу.
Следующий вызов метода объекта перечислителя MoveNext
возобновляет выполнение итерационного блока с места, где он был последний раз приостановлен.
Оператор yield break
выполняется следующим образом:
- Если оператор
yield break
заключен в один или несколькоtry
блоков со связаннымиfinally
блоками, управление изначально передается вfinally
блок самой внутреннейtry
инструкции. Когда и если управление достигает конца блокаfinally
, управление передаётся в блокfinally
следующей заключающей инструкцииtry
. Процесс повторяется до тех пор, пока блоки всех вложенных инструкцийfinally
try
не будут выполнены. - Управление возвращается вызывающему объекту итератора. Это
MoveNext
метод илиDispose
метод объекта перечислителя.
yield break
Поскольку оператор безоговорочно передает управление в другое место, конечная точка yield break
оператора никогда не достижима.
ECMA C# draft specification