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
語句需要一個嵌入語句而非分支中的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
由於語句不允許 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
語句是允許的。 - 反覆運算器區塊包含不安全內容是編譯時錯誤(§23.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)。
注意:對於 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 局部變數的 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
。 第二個替代方法會宣告具有初始值為 ref
variable_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
;
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 類型,並符合與 ref 指派 相同的需求(§12.21.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.23)。
類型和constant_expression的本機常數宣告應遵循與常數成員宣告 (§15.4) 相同的規則。
本機常數的值是在一個表達式中透過使用 simple_name 來取得(§12.8.4)。
本機常數的範圍是宣告所在的區塊。 在constant_declarator結尾之前的文字位置中參照本機常數是錯誤的。
宣告多個常數的本機常數宣告相當於具有相同類型的單一常數的多個宣告。
13.6.4 本機函式宣告
local_function_declaration會宣告本機函式。
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
文法注意事項:當識別 local_function_body 時,如果 null_conditional_invocation_expression 和 運算式 替代方案都適用,則應該選擇前者。 (§15.6.1)
範例:區域函式有兩個常見的使用案例:迭代器方法和異步方法。 在反覆運算器方法中,只有在呼叫列舉傳回序列的程序代碼時,才會觀察到任何例外狀況。 在異步方法中,只有在等候傳回的工作時,才會觀察到任何例外狀況。 下列範例示範如何使用本機函式來分隔參數驗證與反覆運算器實作:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }
結束範例
除非另有指定,否則所有文法元素的語意與在本機函式替代方法的情境中解讀的method_declaration(§15.6.1)相同。
local_function_declaration的標識碼在其宣告的區塊範圍中應是唯一的,包括任何封入局部變數宣告空格。 其中一個結果是不允許多載 local_function_declaration。
local_function_declaration可能包含一個async
(§15.14)修飾詞和一個unsafe
(§23.1)修飾詞。 如果宣告包含 async
修飾詞,則傳回型別應該是 void
或 «TaskType»
類型 ({15.14.1)。 如果宣告包含 static
修飾詞,則函式是 靜態區域函式,否則為 非靜態區域函式。 這是type_parameter_list或parameter_list包含屬性的編譯時間錯誤。 如果local函式是在不安全的內容中宣告的({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 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.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 標籤的語句清單來執行。
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
switch_statement包含 關鍵詞 switch
,後面接著括弧表達式(稱為 switch 運算式),後面接著switch_block。
switch_block由零個或多個switch_section組成,並以大括號括起來。 每個 switch_section 都包含一或多個 switch_label,後面接著語句列表(§13.3.2)。 每個包含的case
都有一個相關聯的模式(§11),用來測試 switch 表達式的值。 如果 case_guard 存在,則其表達式應當隱式可轉換為類型 bool
,而且該表達式會作為額外條件來評估是否滿足該案例。
語句的是由 switch 運算式所建立。
- 如果 switch 表達式的類型為
sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、bool
、string
或 enum_type,或者如果它是對應至其中一個型別的可為 Null 實值型別,則為語句的switch
控制類型。 - 否則,如果從 switch 表達式的類型到下列其中一個可能的控管類型,恰好存在一個使用者定義的隱含轉換:
sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、或string
,或對應於這些類型之一的可為 Null 實值型別,那麼轉換後的類型即是switch
語句的控管類型。 - 否則,語句的
switch
控管型別是 switch 表達式的類型。 如果不存在這類類型,就會發生錯誤。
語句中最多可以有一個default
switch
標籤。
如果任何 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
語句的執行方式如下:
- switch 表達式會被評估並轉換成支配類型。
- 控制會根據轉換的 switch 表達式值進行轉移:
- 符合 switch 運算式值的相同
case
語句中標籤集合switch
中的詞法上排列第一的模式,且 guard 運算式不存在或評估為 true 時,控制流程會轉移到匹配的case
標籤後的語句清單。 - 否則,如果標籤
default
存在,控件會傳送至標籤之後的default
語句清單。 - 否則,控制權會傳送至 語句的
switch
結束點。
- 符合 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.12.8),
switch
語句會區分大小寫,而且只有在 switch 表達式字串完全符合case
卷標常數時,才會執行指定的 switch 區段。 end note 當語句的switch
控管型別為string
或可為 Null 的實值型別時,允許該值null
做為case
標籤常數。
switch_block的statement_list可能包含宣告語句({13.6)。 在 switch 區塊中宣告的局部變數或常數範圍是 switch 區塊。
如果下列條件中至少有一個為真,那麼切換標籤是可達成的:
- switch 表達式是一個常數值,或者是其中一個
- 標籤是
case
,其模式 會比對 該值(§11.2.1),而標籤的守衛則不存在或不是值為 false 的常數表達式,或 - 它是一個
default
標籤,而且沒有 switch 區段包含模式符合該值的 case 標籤,並且其守衛缺失或者是值為 true 的常數表達式。
- 標籤是
- switch 運算式不是常數值,而且也不是
- 標籤是
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
標籤存在,或是缺少其他條件。- switch 表達式是非常量值,switch 語句中出現的模式集合沒有 guards 或者具有值為常數 true 的 guards,對於 switch 控制的類型而言並不 詳盡 (§11.4)。
- switch 運算式是具有可為 Null 的類型的非常數值,而 switch 語句的案例中,沒有未設條件或設條件且其值為常數 true 的模式符合值
null
。 - switch 運算式是常數值,沒有 guard 條件或 guard 條件為常數 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.24)。
- 如果布爾表達式產生
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.24)。 如果布爾表達式產生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.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
成立,語句的內嵌語句就可連線:
- 語句
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
編譯時間處理會先決定表達式的集合類型、列舉值類型和反覆項目類型。 語句的 foreach
處理詳述於 •13.9.5.2 中,而的處理程序 await foreach
詳述於 .13.9.5.3 中。
注意:如果 expression 具有 值
null
,System.NullReferenceException
則會在運行時間擲回 。 結尾註釋
允許實作以不同的方式實作指定的 foreach_statement ;例如,基於效能考慮,只要行為與上述擴充一致即可。
13.9.5.2 同步 foreach
語句的foreach
編譯時間處理會先決定表達式的集合類型、列舉值類型和反覆項目類型。 此判斷的步驟如下所示:
- 如果
X
的類型是陣列類型,則會有一個從 X 到IEnumerable
介面的隱含參考轉換(因為System.Array
實作了這個介面)。 集合類型是IEnumerable
介面,列舉值類型是IEnumerator
介面,而迭代類型是陣列類型的元素類型X
。 - 如果
X
的類型是dynamic
,則會從 表示式 隱含轉換成IEnumerable
介面(§10.2.10)。 集合類型是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
,則會產生錯誤,而且不會採取任何進一步的步驟。 - 否則,如果從
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
在內嵌語句中是唯讀的。 如果沒有從 (迭代類型) 到 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-reassigned (\12.21.3)。 如果沒有從 (迭代類型) 到 T
(V
語句中的本地變量類型)的身分轉換 (§10.2.2),則會產生錯誤,並且不會採取任何進一步的步驟。
foreach
語句的形式foreach (ref readonly V v in x) «embedded_statement»
有類似的等價形式,但參照變數v
在內嵌語句ref readonly
中,因此無法重新指派或以引用方式指定。
迴圈內的位置v
對於在while
中發生的任何匿名函式如何擷取它很重要。12.19.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 await foreach
語句的foreach
編譯時間處理會先決定表達式的集合類型、列舉值類型和反覆項目類型。 語句的 foreach
處理詳述於 •13.9.5.2 中,而的處理程序 await foreach
詳述於 .13.9.5.3 中。
此判斷的步驟如下所示:
- 判斷類型
X
是否有適當的GetAsyncEnumerator
方法:- 使用標識碼
X
且沒有類型自變數,對類型GetAsyncEnumerator
執行成員查閱。 如果搜尋成員未能產生匹配結果、產生不明確的匹配或產生非方法群組的匹配,應檢查可列舉的介面,如下所述。 如果成員查找產生除方法群組外的任何結果,或沒有找到匹配,建議發出警告。 - 使用產生的方法群組和空的自變數清單來執行多載解析。 如果多載解析沒有產生任何適用的方法、產生模棱兩可的方法,或產生單一最佳方法,但該方法為靜態或不公用,請檢查可列舉的介面,如下所示。 如果多載解析不是產生明確的公用實例方法或無可應用的方法,建議發出警告。
- 如果
E
方法的傳回型別GetAsyncEnumerator
不是類別、結構或介面類型,則會產生錯誤,而且不會採取任何進一步的步驟。 - 成員查找會在具有識別符
E
且沒有類型引數的Current
上執行。 如果成員查詢未找到匹配項目,結果將是錯誤;若結果是除可讀取的公用實例屬性以外的任何項目,亦將產生錯誤,且不會採取任何進一步的步驟。 - 成員查找會在具有識別符
E
且沒有類型引數的MoveNextAsync
上執行。 如果成員查閱不會產生任何相符專案,則結果為錯誤,或結果為方法群組以外的任何專案,則會產生錯誤,而且不會採取任何進一步的步驟。 - 多載解析是在具有空自變數清單的方法群組上執行。 如果多載解析沒有產生任何適用的方法、造成模稜兩可情況,或產生單一最佳方法,但該方法是靜態或非公用方法,或其傳回類型無法被等待(§12.9.8.2),其中await_expression 被分類為
bool
(§12.9.8.3),則會產生錯誤,而且不會採取任何進一步的步驟。 - 集合類型是
X
,列舉值類型是E
,而反覆項目類型是Current
屬性的類型。
- 使用標識碼
- 否則,請檢查非同步可列舉介面:
- 如果在所有可以從
Tᵢ
隱含轉換到X
的型別中,有一個唯一的類型IAsyncEnumerable<Tᵢ>
如此,T
不是T
,而對於其他所有dynamic
型別,都存在一個從Tᵢ
隱含轉換到IAsyncEnumerable<T>
,那麼集合類型是介面IAsyncEnumerable<Tᵢ>
,列舉器類型是介面IAsyncEnumerable<T>
,而迭代類型是IAsyncEnumerator<T>
。 - 否則,如果有多個這類類型
T
,則會產生錯誤,而且不會採取任何進一步的步驟。 - 否則,會產生錯誤,而且不會採取任何進一步的步驟。
- 如果在所有可以從
如果成功,上述步驟會明確產生集合類型 C
、列舉值類型 E
和反覆運算類型 T
。
await foreach
表單的語句
await foreach (V v in x) «embedded_statement»
然後相當於:
{
E e = ((C)(x)).GetAsyncEnumerator();
try
{
while (await e.MoveNextAsync())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
無法從表達式e
、內嵌語句或程式中的任何其他程式碼中看到或存取變數x
。 變數 v
在內嵌語句中是唯讀的。 如果沒有從 (迭代類型) 到 T
(語句中的V
local_variable_type) 的明確轉換 (await foreach
),則會產生錯誤,而且不會採取任何進一步的步驟。
異步列舉器可能會選擇性地公開一個DisposeAsync
方法,這個方法可以無參數調用,並且傳回一個可被await
的對象,且其GetResult()
返回void
。
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
{
await enumerator.DisposeAsync(); // omitted, along with the try/finally,
// if the enumerator doesn't expose DisposeAsync
}
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
、 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
將開始最近的封閉while
、do
、for
或foreach
語句的新的迭代。
continue_statement
: 'continue' ';'
;
語句 continue
的目標是最接近封入 while
、do
、for
或 foreach
語句之內嵌語句的結束點。
continue
如果語句不是以 while
、 do
for
或 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.19),並參與判斷這些函式存在哪些轉換(~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
子句為止。 在此描述中,拋出點 一開始是例外被拋出的地方。 這個行為是在 (~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
結束點時,會繼續處理目前的例外狀況。
- 在
如果例外狀況處理程式不在目前的函式調用中,則會終止函式調用,併發生下列其中一項:
如果例外狀況處理終止目前線程中的所有函式成員調用,表示線程沒有例外狀況的處理程式,則線程本身會終止。 此類終止的影響取決於具體實作。
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。 如果 子句包含例外狀況篩選條件,則會將例外狀況物件指派給例外狀況變數,並評估例外狀況篩選條件。
如果例外狀況已被傳播且找到相符的 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 語句
using
語句會取得一或多個資源、執行 語句,然後處置資源。
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
資源是一個實作介面的類別或結構,其中介面為System.IDisposable
(針對異步數據流則為IAsyncDisposable
),內含一個名為Dispose
的無參數方法(適用於異步數據流則為DisposeAsync
)。 使用資源的程式代碼可以呼叫 Dispose
,以指出不再需要資源。
如果 resource_acquisition 的形式是 local_variable_declaration,則 local_variable_declaration 的類型應為 dynamic
或可以隱含轉換成 System.IDisposable
的類型(針對異步數據流使用 IAsyncDisposable
)。 如果 resource_acquisition 的形式是 表達式 ,則此表達式應隱含轉換成 System.IDisposable
(IAsyncDisposable
異步數據流)。
在resource_acquisition中宣告的局部變數是唯讀的,且必須包含一個初始值。 如果內嵌語句嘗試修改這些局部變數(透過指派或 ++
和 --
運算元)、取得它們的位址,或將它們當做參考或輸出參數傳遞,就會發生編譯時期錯誤。
using
語句會轉譯成三個部分:取得、使用和處置。 資源的使用方式會自動包含在包含try
子句的finally
語句中。 這個 finally
條款會處置資源。 如果取得了null
資源,則不會呼叫Dispose
,針對異步資料流則為DisposeAsync
,而且也不會拋出例外狀況。 如果資源的類型是 dynamic
,它會在取得過程中通過隱含動態轉換(§10.2.10)動態轉換為 IDisposable
(如果是異步流則轉換為 IAsyncDisposable
),以確保在使用和處置之前轉換成功。
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
(IAsyncDisposable
針對異步數據流)本身會當做 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 語句
語句 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
語句的終點。