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語句需要一個嵌入語句而非分支中的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由於語句不允許 switch 區段「落入」至下一個 switch 區段,所以這是可連線之 switch 區段語句清單結束點的編譯時間錯誤。 如果發生此錯誤,通常表示break語句遺失。在編譯時期,若函式成員的區塊結束點或匿名函式的結束點(計算值)是可達的,則會出現錯誤。 如果發生此錯誤,通常表示
return語句遺失 (~13.10.5)。
13.3 個區塊
13.3.1 一般
區塊允許在允許單一語句的內容中寫入多個語句。
block
: '{' statement_list? '}'
;
區塊包含以大括弧括住的可選的statement_list(§13.3.2)。 如果省略語句清單,表示區塊是空的。
區塊可能包含宣告語句 ({13.6)。 區塊中宣告的局部變數或常數範圍是 區塊。
區塊的執行方式如下:
- 如果區塊是空的,控件就會傳送到區塊的終點。
- 如果區塊不是空的,控件就會傳送至語句清單。 當控件到達語句清單的結束點時,控件就會傳送至區塊的結束點。
如果區塊本身是可達的,則該區塊的語句列表也是可達的。
如果區塊是空的,或語句清單的端點可連線到,則區塊的終點是可連線的。
包含一或多個語句的yield(~13.15)稱為反覆運算器區塊。 Iterator 區塊可用來實作函式成員作為反覆運算器(~15.15)。 某些其他限制適用於反覆運算器區塊:
- 在反覆運算器區塊中出現
return語句時會導致編譯時期錯誤,但yield return語句是允許的。 - 反覆運算器區塊包含不安全內容的編譯階段錯誤 (§24.2) 。 迭代器區塊總是定義為安全環境,即使其宣告嵌套在不安全環境中也是如此。
13.3.2 語句清單
語句清單是由一或多個以順序撰寫的語句所組成。 語句清單發生在 區塊(§13.3)和 switch_block區塊(§13.8.3)。
statement_list
: statement+
;
敘述清單透過將控制權轉移至第一條敘述來執行。 當控件到達 語句的結束點時,控件會傳送至下一個語句。 當控件到達最後一個語句的結束點時,控件會傳送至語句清單的結束點。
如果至少有下列其中一項成立,語句清單中的語句就是可達到的:
- 語句是第一個語句,而且語句列表本身是可到達的。
- 能到達上述語句的結束點。
- 這個語句是標記的語句,並且該標籤由一個可達的
goto語句所參考。
如果清單中最後一個語句的端點可觸達,語句清單的結束點就可觸達。
13.4 空白語句
empty_statement不會執行任何動作。
empty_statement
: ';'
;
當在需要語句的情境中沒有需要執行的操作時,會使用空語句。
空語句的執行只會將控制權傳輸至 語句的結束點。 因此,如果可觸達空白語句,則可觸達空語句的結束點。
範例:使用 Null 主體撰寫
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語句可以在區塊和區塊外傳輸控制權,但絕不會傳送到區塊中。 結尾註釋
標籤有自己的宣告空間,且不會干擾其他識別符。
範例:範例
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }有效,並使用 x 名稱作為參數和標籤。
結束範例
標記語句的執行與標籤標之後的語句執行完全對應。
除了正常控制流程提供的可達性之外,如果可達語句引用了標籤,則該標籤是可觸達的,除非該goto語句在某個goto區塊或包含無法到達終點的區塊的try的catch內,而標記的語句在try_statement之外。
13.6 宣告語句
13.6.1 一般
declaration_statement會宣告一或多個局部變數、一或多個局部常數或局部函數。 區塊和 switch 區塊中允許宣告語句,但不允許做為內嵌語句。
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
局部變數是使用 local_variable_declaration 來宣告 (~13.6.2)。 本機常數是使用 local_constant_declaration 宣告的(第13.6.3節)。 本機函式是使用 local_function_declaration 來宣告(§13.6.4)。
宣告的名稱會引入到最接近的封閉宣告空間(§7.3)。
13.6.2 局部變數宣告
13.6.2.1 一般
local_variable_declaration會宣告一或多個局部變數。
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
隱含類型聲明中包括上下文關鍵字(§6.4.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(ref 變數)。 宣告子會定義引進變數的名稱(標識符)和初始值。如果有的話。
如果宣告中有多個宣告子,會按順序處理,包括任何初始化表達式,從左至右進行(§9.4.4.5)。
注意:對於不以for_initializer (§13.9.4) 或resource_acquisition (§13.14) 的形式出現的local_variable_declaration,此從左到右的順序相當於每個宣告者都在單獨的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 局部變數的 ref-safe-context (§9.7.2)是其初始化 variable_reference 的 ref-safe-context。 非 ref 局部變數的 ref-safe-context 是 declaration-block。
13.6.2.2 隱含類型局部變數宣告
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
implicitly_typed_local_variable_declaration引進單一局部變數識別碼。
運算式或variable_reference的編譯時間類型T為 。 第一個替代方法會宣告具有 表達式初始值的變數;其類型是 T? 當 T 為不可為 Null 的參考型別時,否則其類型為 T。 第二個替代方法會宣告具有初始值為 refvariable_reference 的 ref 變數;其類型是 ref T? 當 T 為不可為 Null 的參考型別時,否則其類型為 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_kind 為 ref readonly,所宣告的 標識符會參考被視為唯讀的變數。 否則,如果 ref_kind 為 ref,則所宣告的 標識符會參考可寫入的變數。
在以ref struct宣告的方法內,或在迭代器(async)中宣告 ref 局部變數或型別為的變數時,這是編譯時期錯誤。
13.6.3 本地常數宣告
local_constant_declaration會宣告一或多個本機常數。
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
local_constant_declaration的類型會指定宣告所引進之常數的類型。 此類型後面接著 constant_declarator清單,每一個都會引進新的常數。
constant_declarator 包含命名常數的識別碼、後面接著 “=” 記號,後面接著提供常數值的constant_expression (§12.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 和 運算式 替代方案都適用,則應該選擇前者。 (§15.6.1)
範例:區域函式有兩個常見的使用案例:迭代器方法和異步方法。 在反覆運算器方法中,只有在呼叫列舉傳回序列的程序代碼時,才會觀察到任何例外狀況。 在異步方法中,只有在等候傳回的工作時,才會觀察到任何例外狀況。 下列範例示範如何使用本機函式來分隔參數驗證與反覆運算器實作:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }結束範例
除非另有指定,否則所有文法元素的語意與在本機函式替代方法的情境中解讀的method_declaration(§15.6.1)相同。
local_function_declaration的標識碼在其宣告的區塊範圍中應是唯一的,包括任何封入局部變數宣告空格。 其中一個結果是不允許多載 local_function_declaration。
local_function_declaration可以包含一個 async (§15.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 語句,這將構成編譯時期錯誤。
注意:上述規則
this的 和goto反映了 §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 Selection 語法
13.8.1 一般
選取語句會根據某些表達式的值,選取數個可能執行的語句之一。
selection_statement
: if_statement
| switch_statement
;
13.8.2 if 語句
語句 if 會根據布爾表達式的值選取要執行的語句。
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
部分 else 與語法所允許的在詞彙上最近的前一個 if 聯繫在一起。
範例:因此,
if類型的語句if (x) if (y) F(); else G();等同於
if (x) { if (y) { F(); } else { G(); } }結束範例
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,後面接著語句列表(§13.3.2)。 每個包含case的switch_label都有一個關聯的模式 (§11),根據該模式測試交換機selector_expression的值。 如果 case_guard 存在,則其表達式應當隱式可轉換為類型 bool ,而且該表達式會作為額外條件來評估是否滿足該案例。
注意:為方便起見,當selector_expression為tuple_expression時,可以省略switch_statement中的括號。 例如,
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的型別。 如果不存在這類類型,就會發生錯誤。
語句中最多可以有一個defaultswitch標籤。
如果任何 switch 標籤的模式 不適用於 輸入運算式的類型(§11.2.1),則為錯誤。
如果某一個交換器標籤的模式被交換語句中先前標籤的模式集合所涵蓋(§11.3),而這些標籤沒有案例防護,或其案例防護是值為 true 的常量表達式,那麼這就是一個錯誤。
範例:
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }結束範例
switch語句的執行方式如下:
- 交換器的 selector_expression 會評估並轉換為控管類型。
- 控制權根據轉換後交換機的 selector_expression值進行轉移:
- 相同陳述式中標籤集中
case的詞彙第一個模式符合交換器selector_expression值,且防護運算式不存在或評估為 true,會導致控制權轉移至相符標籤case之後的陳述式清單。switch - 否則,如果標籤
default存在,控件會傳送至標籤之後的default語句清單。 - 否則,控制權會傳送至 語句的
switch結束點。
- 相同陳述式中標籤集中
注意:未定義在運行時間比對模式的順序。 允許編譯程式(但不需要)依序比對模式,並重複使用已比對模式的結果,以計算其他模式比對的結果。 不過,編譯器必須判斷語法上第一個匹配表達式的模式,且保護子句不存在或評估為
true。 結尾註釋
如果 switch 區段的語句清單的結束點是可到達的,就會發生編譯時錯誤。 這稱為「不允許掉落」規則。
範例:範例
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }有效,因為沒有 switch 區段有可達到的結束點。 不同於 C 和 C++,switch 區段的執行不允許自動「落入」下一個 switch 區段,而範例顯示..........
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }會產生編譯時錯誤。 當執行一個 switch 區段後需要接著執行另一個 switch 區段時,應使用明確的
goto 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++ 中出現的常見錯誤,即由於不小心省略語句而引發的問題。 例如,上述語句的
switch區段可以反轉,而不會影響 語句的行為:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }結尾註釋
注意:switch 區段的語句清單通常以
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.14.8) 一樣,陳述
switch式會區分大小寫,而且只有在參數的 selector_expression 字串完全符合case標籤常數時,才會執行指定的參數區段。 結尾註釋
當陳述式的switch控管類型是string或可為空值類型時,容許該值null作為標籤常數。case
switch_block的statement_list可能包含宣告語句({13.6)。 在 switch 區塊中宣告的局部變數或常數範圍是 switch 區塊。
如果下列條件中至少有一個為真,那麼切換標籤是可達成的:
- 開關的 selector_expression 是常數值,且
- 標籤是
case,其模式 會比對 該值(§11.2.1),而標籤的守衛則不存在或不是值為 false 的常數表達式,或 - 它是一個
default標籤,而且沒有 switch 區段包含模式符合該值的 case 標籤,並且其守衛缺失或者是值為 true 的常數表達式。
- 標籤是
- 開關的 selector_expression 不是常數值,且
- 標籤是
case無防護的,或防護的值不是常數 false。 - 它是
default標籤- 在參數控管型別沒有防護或具有值為 true 之 guard 的 switch 語句案例中出現的模式集合並不 詳盡 (\11.4) :或
- switch 控制的類型是一種可為 Null 的型別,而且在 switch 語句的各個 case 中,若不含保護條件或保護條件的值為常數 true,其模式集合不包含可以匹配值
null的模式。
- 標籤是
- 開關標籤是由可到達的
goto case或goto default語句引用。
如果給定 switch 區段中的 switch 語句是可達到的,且該 switch 區段包含一個可達到的切換標籤,則該區段的語句清單是可達到的。
如果 switch 語句是可達到的,並且至少有一個條件為 true,那麼該語句的終點就是可達到的:
- 語句
switch中包含一個可達的break語句,該語句會終結switch語句。 - 沒有
default標籤存在,或是缺少其他條件。- 參數的 selector_expression 是非常數值,而出現在沒有防護或具有值為常數 true 的防護的 switch 陳述式案例中出現的模式集,對於參數控管類型來說,並不是 詳盡的 (§11.4) 。
- switch 的selector_expression是可為 Null 類型的非常數值,而且沒有防護或具有值為常數 true 的防護的 switch 陳述式案例中出現的任何模式都不會符合值。
null - 交換器的 selector_expression 是常數值,沒有防護或其防護為常數 true 的標籤都不會
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語句的結束點(因此結束內嵌語句的反覆),而continue語句(§13.10.3)可用來將控制權傳送至內嵌語句的結束點(因此再次執行while語句的反覆)。
如果while語句可觸達,且布林表達式的值不是常數while,那麼false語句中的內嵌語句就可觸達。
如果至少有下列其中一項 while 成立,語句的結束點就可到達:
- 語句
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語句的結束點(因此結束內嵌語句的反覆),而continue語句(§13.10.3)可用來將控制權傳送至內嵌語句的結束點(因此再次執行do語句的反覆)。
如果do語句是可達的,那麼do語句的內嵌語句也是可達的。
如果至少有下列其中一項 do 成立,語句的結束點就可到達:
- 語句
do中包含一個可達的break語句,該語句會終結do語句。 - 可觸達內嵌語句的終點,而且布爾運算式沒有常數值
true。
13.9.4 for 語句
語句 for 會評估初始化表達式的序列,然後在條件為 true 時重複執行內嵌語句,並評估反覆項目表達式序列。
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
如果存在,for_initializer由local_variable_declaration(§13.6.2)或以逗號分隔的statement_expression清單(§13.7)組成。 for_initializer所宣告的局部變數範圍是for_initializer、for_condition、for_iterator和embedded_statement。
該 for_condition(如果存在)應為 boolean_expression (§12.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 成立,語句的內嵌語句就可連線:
- 語句
for是可達的,且沒有 for_condition。 - 該語句
for是可達到的,並且存在 for_condition,且其沒有常數值false。
如果至少有下列其中一項 for 成立,語句的結束點就可到達:
- 語句
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
;
foreach 語句的 local_variable_type 和 標識符 會宣告 語句的 反覆運算變數 。
var如果標識元指定為local_variable_type,且沒有具名var的類型在範圍內,則反覆運算變數會被視為隱含型別反覆運算變數,且其類型會採用為語句的foreach元素類型,如下所示。
這是一個編譯時錯誤,當 await 和 ref_kind 同時出現在 foreach statement 中時會發生。
如果foreach_statement同時包含ref和readonly或二者皆無,則迴圈變數表示被視為唯讀的變數。 否則,如果 foreach_statement 包含 ref 不含 readonly的 ,反覆運算變數代表可寫入的變數。
反覆運算變數會對應至局部變數,其範圍會延伸至內嵌語句。 在執行foreach語句時,迭代變數代表當前正在進行迭代的集合元素。 如果反覆運算變數表示只讀變數,則如果內嵌語句嘗試修改它,或透過指派或 ++ 和 -- 運算符,或將它傳遞為參考或輸出參數,就會發生編譯時期錯誤。
陳述式的 foreach 編譯階段處理會先決定運算式的 集合類型 (C)、列 舉值類型 (E) 及 反覆專案類型 (T或 ref Tref readonly T)。
同步及非同步版本的判定類似。 具有不同方法和傳回類型的不同介面區分同步和非同步版本。 一般流程如下進行。 '«' 和 '»' 內的名稱是同步和非同步迭代器實際名稱的預留位置。 「GetEnumerator」、「MoveNext」、「IEnumerable»<T、「IEnumerator»>T<」和任何其他差異所允許的類型,在同步>陳述式的 §13.9.5.2 和非同步foreach陳述式的 §13.9.5.3 中詳細foreach說明。
- 判斷
X類型是否有適當的 «GetEnumerator» 方法:- 對具有識別碼 «GetEnumerator» 且沒有類型引數的類型
X執行成員查閱。 如果成員查閱不會產生相符項目,或產生不明確性,或產生不是方法群組的相符項目,請檢查可列舉的介面,如步驟 2 中所述。 如果成員查找產生除方法群組外的任何結果,或沒有找到匹配,建議發出警告。 - 使用產生的方法群組和空的自變數清單來執行多載解析。 如果多載解析沒有產生任何適用的方法、產生模棱兩可的方法,或產生單一最佳方法,但該方法為靜態或不公用,請檢查可列舉的介面,如下所示。 如果多載解析不是產生明確的公用實例方法或無可應用的方法,建議發出警告。
- 如果 «GetEnumerator» 方法的傳回類型
E不是類別、結構或介面類型,則會產生錯誤,且不採取進一步的步驟。 - 使用識別碼
E執行成員查閱Current,且沒有類型引數。 如果成員查閱未產生相符專案,則結果是錯誤,或結果是允許讀取的公用實例屬性以外的任何專案,則會產生錯誤,且不採取進一步的步驟。 - 使用標識符 «MoveNext» 且沒有類型參數執行成員查找
E。 如果成員查閱不產生相符專案,結果是錯誤,或結果是方法群組以外的任何專案,則會產生錯誤,且不採取進一步的步驟。 - 在具有空引數清單的方法群組上執行多載解析。 如果過載解決導致:沒有適用的方法;模棱兩可;或單一最佳方法,但該方法是靜態的,或不是公用的,或其傳回類型不是允許的傳回類型;然後產生錯誤,不要採取進一步的步驟。
- 集合類型是
X,列舉值類型是E,而反覆項目類型是Current屬性的類型。
- 對具有識別碼 «GetEnumerator» 且沒有類型引數的類型
- 否則,請檢查可列舉介面:
- 如果在從 «IEnumerable»Ti 隱式轉換
Tᵢ的所有類型X中,有一個唯一的類型<,例如>不是T,並且對於所有其他類型T,都有從 «IEnumerable»dynamicTTᵢ到 «IEnumerable»<Ti> 的隱式轉換,則集合類型是介面 «IEnumerable»<T>,枚舉器類型是介面 «IEnumerator»<T>,<> 疊代類型為T。 - 否則,如果有多個此類類型
T,則會產生錯誤,並且不採取進一步的步驟。
- 如果在從 «IEnumerable»Ti 隱式轉換
注意:如果 expression 具有 值
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的類型是陣列類型,則會隱含參考轉換 fromXIEnumerable<T>介面,其中T是陣列X的元素類型 (§17.2.3) 。 - 如果
X的類型是dynamic,則會從 表示式 隱含轉換成IEnumerable介面(§10.2.10)。 集合類型是IEnumerable介面,而列舉值類型是IEnumerator介面。var如果將識別子指定為 local_variable_type 則反覆運算類型為dynamic,否則為object。
如果 §13.9.5.1 中的程式完成,但不產生單一集合類型、列舉值類型和反覆專案類型,則會採取下列步驟:
- 如果有隱含的轉換 從 到
X介面,System.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 在內嵌語句中是唯讀的。 如果沒有從 (迭代類型) 到 T (語句中的Vlocal_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.23.3)。 如果沒有從 (迭代類型) 到 T (V 語句中的本地變量類型)的身分轉換 (§10.2.2),則會產生錯誤,並且不會採取任何進一步的步驟。
foreach語句的形式foreach (ref readonly V v in x) «embedded_statement»有類似的等價形式,但參照變數v在內嵌語句ref readonly中,因此無法重新指派或以引用方式指定。
迴圈內部while的位置v對於embedded_statement中發生的任何匿名函式如何擷取迴圈 (§12.21.6.2) 很重要。
範例:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();如果在迴圈
v外部宣告了展開形式中的while,那麼它將在所有迭代中被共用,並且在迴圈for後的值將是最終值13,這是叫用f時將會列印的內容。 相反地,因為每個迭代都有自己的變數v,第一個迭代中由f擷取到的值會繼續保持為7,這就是會被列印的內容。 (請注意,舊版 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,則不會導致Boxing發生。
否則,如果
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 等待 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 方法。 其他函式庫方法可能需要將 a CancellationToken 傳遞給 GetAsyncEnumerator。 當這些方法是運算式 enumerable的一部分時,記號應該組合成單一記號,就像 by CreateLinkedTokenSource 及其 Token 屬性一樣。
finally 區塊的主體是根據下列步驟建構的:
如果具有可存取
E的方法,其中傳回類型是可等候的 (DisposeAsync()),則子finally句會展開為語意對等:finally { await e.DisposeAsync(); }否則,如果有隱含的轉換 從介面,
ESystem.IAsyncDisposable而且E是不可為空值的值類型,則子finally句會展開為語意對等:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }除非
E是實值型別,或具現化為實值型別的類型參數,則e轉換成System.IAsyncDisposable,則不會導致Boxing發生。否則,如果 是
E類型且具有可存取ref struct的方法,則Dispose()子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
;
jump 語句傳輸控件的位置稱為 jump 語句 的目標 。
當 jump 語句發生在區塊內,而該 Jump 語句的目標位於該區塊之外時,會說 jump 語句會 結束 區塊。 雖然 jump 語句可以將控制權從區塊移出,但它永遠無法將控制權傳送到區塊中。
跳躍語句的執行會因插入 try 語句的存在而複雜。 在沒有這類 try 語句的情況下,jump 語句會無條件地將控制權從 jump 語句轉移到其目標。 在這類介入 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語句的區塊會在控制權傳送至跳轉語句的目標之前執行。 產生的輸出如下所示:Before break Innermost finally block Outermost finally block After break結束範例
13.10.2 break 語句
語句 break 會結束最接近的 switch封入 、 while、 do、 for或 foreach 語句。
break_statement
: 'break' ';'
;
break語句的目標是最接近的包含switch、while、do、for或foreach語句的結束點。
break如果語句不是以 switch、 while、 dofor或 foreach 語句括住,則會發生編譯時期錯誤。
當多個switch、 while、 dofor或 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 將開始最近的封閉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語句清單,其中包含標籤(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 語句將控制權傳回給包含該返回語句的函式成員的當前呼叫者,並可選擇性地傳回值或 variable_reference (第9.5節)。
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
沒有表達式的return_statement稱為return-no-value;包含ref表達式的稱為return-by-ref;而只包含表達式的稱為return-by-value。
宣告為按值或按引用返回的方法中使用不返回值的方法會導致編譯時期錯誤。。。
將從被宣告為「returns-no-value」或「returns-by-value」的方法中使用「return-by-ref」是一個編譯時錯誤。
從宣告為 returns-no-value 或 returns-by-ref 的方法以傳回值方式回傳時會發生編譯時錯誤。
若 表達式 不是 變數參考,或是參考變數的 ref 安全內容不是呼叫者的內容(§9.7.2),則在編譯期間使用回傳參考會出錯。
從以 method_modifierasync 宣告的方法中使用以引用方式傳回,這會導致編譯時期的錯誤。
如果函式成員是以傳值方式傳回的函式(~15.6.11)、屬性或索引器的以傳值方式傳回的 get 存取子,或是使用者定義的運算符,則表示函式成員會計算值。 函式成員不返回值,它們不會計算值,這些成員包括具有有效回傳型別 void 的方法、屬性和索引子的 set 存取子、事件的新增和移除存取子、實例建構函式、靜態建構函式和完成項。 傳回 by-ref 的函式成員不會計算值。
若為傳回值類型,則從表達式的類型到包含函式成員的有效傳回型別(§15.6.11)應存在隱含轉換(§10.2)。 針對傳回參考,識別轉換(~10.2.2)應該存在於 表達式 類型與包含函式成員的有效傳回型別之間。
return 陳述式也可以在匿名函式的主體中使用 (§12.21) ,並參與判斷這些函式存在哪些轉換 (§10.7.1) 。
語句return出現在finally區塊中是一個編譯時期的錯誤(§13.11)。
return語句的執行方式如下:
- 針對傳回值, 會評估表達式 ,並透過隱含轉換將其值轉換成包含函式的有效傳回型別。 轉換的結果會成為函式所產生的結果值。 對於參考傳回,表達式 會被評估,並將結果分類為變數。 如果封入方法的 return-by-ref 包含
readonly,則產生的變數是只讀的。 -
return語句如果被一個或多個try或catch區塊連同相關的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區塊中使用,在此情況下,該語句會重新擲回該catch區塊目前正在處理的例外狀況。
因為throw語句無條件地將控制權轉移到其他地方,所以無法到達throw語句的終點。
當例外狀況被擲回時,控制權會轉移至能處理該例外的封閉語句中的第一個catch子句。 從擲回例外狀況點到將控件傳送至適當例外狀況處理程式的點所發生的程式稱為 例外狀況傳播。 例外狀況的傳播是透過反覆評估下列步驟進行的,直到找到一個與例外狀況相符的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區塊或catchS區塊括住擲回點,如果S具有finally區塊,則會將控件傳送至finally區塊。 如果finally區塊擲回另一個例外狀況,則會終止目前例外狀況的處理。 否則,當控件到達 區塊的finally結束點時,會繼續處理目前的例外狀況。
- 在
如果例外狀況處理程式不在目前的函式調用中,則會終止函式調用,併發生下列其中一項:
如果例外狀況處理終止目前線程中的所有函式成員調用,表示線程沒有例外狀況的處理程式,則線程本身會終止。 此類終止的影響取決於具體實作。
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子句會繼續在所有封入區塊中進行,如§13.10.6中所述。 如果例外狀況類型符合任何catch,且exception_filter為 true,則子句是匹配的。
catch沒有exception_specifier的 子句符合任何例外狀況類型。 當exception_specifier指定的是例外狀況類型或其基底類型時,例外狀況類型符合exception_specifier。 如果 子句包含例外狀況篩選條件,則會將例外狀況物件指派給例外狀況變數,並評估例外狀況篩選條件。 如果exception_filter的boolean_expression評估擲回例外狀況,則會攔截該例外狀況,而例外狀況篩選條件會評估為 false。
如果例外狀況已被傳播且找到相符的 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子句之前執行。 產生的輸出為:Filter Finally Catch結束範例
如果try語句是可到達的,則try語句的try區塊是可到達的。
catch區塊是可連線的,如果try程式語句是可連線的,則try語句區塊也是如此。
如果finally語句是可到達的,則try語句的try區塊是可到達的。
如果下列兩項 try 都成立,語句的結束點就可觸達:
- 區塊的
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 會取得指定物件的互斥鎖定、執行 語句,然後釋放鎖定。
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
語句的lock應表示已知為參考之型別的值。 針對語句的表達式,不會執行隱含 Boxing 轉換 (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
;
資源類型是實作 or System.IDisposable 介面之一System.IAsyncDisposable或兩個的類別或非 ref 結構,其中包含名為 Dispose and/or DisposeAsync的單一無參數方法,或包含名為 的方法的 ref 結構Dispose,其簽章與 宣告的System.IDisposable簽章相同。 使用資源的程式碼可以呼叫 Dispose 或 DisposeAsync 來指出不再需要資源。
如果 resource_acquisition 的形式 local_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 結構資源,當 是不可為 Null 的值類型或具有值類型條件約束的類型參數 (ResourceType) 時,公式在語意上相當於
{
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();
}
}
}
對於 ref 結構資源,唯一語義上對等的公式是
{
«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陳述式來確保在寫入或讀取作業之後,基礎檔案能夠適當關閉。結束範例
When ResourceType 是實作 IAsyncDisposable. 其他公式 執行 await using 從同步 Dispose 方法到非同步 DisposeAsync 方法的類似替換。
await using表單的語句
await using (ResourceType resource = «expression» ) «statement»
在語義上等同於下面 IAsyncDisposable 顯示的公式,用 代替 IDisposable, DisposeAsync 而不是 Dispose,並且傳回的 Task 是 DisposeAsyncawaited:
await using (ResourceType resource = «expression» ) «statement»
在語意上相當於
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
注意:embedded_statement中的任何跳轉陳述式 (§13.10) 都必須符合陳述式的
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
}
using 宣告不應直接出現在 switch_label內,而是可以出現在 switch_label內的區塊內。
13.15 yield 語句
語句 yield 會用於反覆運算器區塊 (~13.3) 中,以將值產生給反覆運算器之列舉值物件(\15.15.5) 或可列舉的物件(\15.15.6),或表示反覆運算器的結尾。
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield是內容關鍵詞 (~6.4.4),只有在 或 return 關鍵詞之前break立即使用時才具有特殊意義。
限制語句出現位置的條件有數個,如下所述。
- 出現在
yield、operator_body或accessor_body之外的語句將會導致編譯時期錯誤。 - 在匿名函式中出現
yield語句(無論哪種形式)都是編譯時期的錯誤。 - 在
yield語句的finally子句中出現try語句,會導致編譯時期錯誤。 - 在包含任何
yield return的try語句中,若語句出現在任何位置,將會引發編譯時錯誤。
範例:下列範例顯示語句的一些有效和無效用法
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語句的終點。