13 個語句
13.1 一般
C# 提供各種語句。
注意:在 C 和 C++ 中程式設計的開發人員會熟悉這些語句中的大部分。 end note
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
區塊中,範例是有效的。end 範例
13.2 端點和可觸達性
每個語句都有一個結束點。 就直覺而言,語句的終點是緊接在 語句之後的位置。 複合語句的執行規則(包含內嵌語句的語句)指定當控件到達內嵌語句結束點時所採取的動作。
範例:當控件到達區塊中語句的結束點時,控件會傳送至 區塊中的下一個語句。 end 範例
如果可以透過執行來觸達語句,則表示語句是可連線的。 相反地,如果不可能執行語句,則表示語句無法連線。
範例:在下列程式代碼中
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }
Console.WriteLine 的第二個叫用無法連線,因為不可能執行 語句。
end 範例
如果無法連線到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
即使實際上,叫用會被視為可連線,但永遠不會執行。end note
函 式成員或匿名函式的區塊 一律視為可連線。 藉由連續評估區塊中每個語句的觸達性規則,即可判斷任何指定語句的觸達性。
範例:在下列程式代碼中
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
。end 範例
有兩種情況是可連線語句端點的編譯時間錯誤:
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.14)。 某些其他限制適用於反覆運算器區塊:
- 這是語句出現在反覆運算器區塊中的編譯時期錯誤
return
(但yield return
允許語句)。 - 這是反覆運算器區塊包含不安全內容 (~23.2) 的編譯時間錯誤。 反覆運算器區塊一律會定義安全的內容,即使其宣告巢狀於不安全的內容中也一樣。
13.3.2 語句清單
語句清單是由一或多個以順序撰寫的語句所組成。 語句清單會以 區塊s (~13.3) 和 switch_blocks (\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: ; }
end 範例
13.5 標記語句
labeled_statement允許以標籤為前置詞的語句。 區塊中允許加上標籤的語句,但不允許做為內嵌語句。
labeled_statement
: identifier ':' statement
;
加上標籤的語句會宣告具有標識碼所指定名稱的 標籤。 卷標的範圍是宣告標籤的整個區塊,包括任何巢狀區塊。 這是兩個具有相同名稱的標籤具有重疊範圍的編譯時間錯誤。
注意:這表示
goto
語句可以在區塊和區塊外傳輸控制權,但絕不會傳送到區塊中。 end note
標籤有自己的宣告空間,而且不會干擾其他識別碼。
範例:範例
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }
有效,並使用 x 名稱作為參數和標籤。
end 範例
標記語句的執行與標籤標之後的語句執行完全對應。
除了一般控制流程所提供的可觸達性之外,如果標籤是由可goto
連線語句參考標籤,則可觸達語句,除非goto
語句位於區塊 try_statementtry
finally
包含無法連線到其結束點的區塊,且加上卷標的語句位於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,宣告子會引進每個變數,而宣告子是隱含型別、明確型別和 ref 局部變數的其中一個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; }
end note
局部變數的值是在表達式 中使用 simple_name 取得 (~12.8.4)。 在取得其值的每個位置,都必須指定局部變數 (~9.4)。 local_variable_declaration引進的每個局部變數一開始都是未指派的(~9.4.3)。 如果宣告子具有初始化表達式,則引進的局部變數會分類為 在宣告子結尾指派 (~9.4.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
第二個替代方法會宣告具有初始值為 ref
variable_reference的 ref 變數;其類型是當 ref T?
為不可為 Null 的參考型別時,否則其類型為 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
end 範例
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)或數位初始化規則來適當,且其值會指派為變數的初始值。
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.21.3)。
如果 ref_kind 為 ref readonly
, 則所宣告的標識符會參考被視為只讀的變數。 否則,如果 ref_kind 為 ,ref
標識符會參考可寫入的變數。
在以 ref struct
宣告的方法內,或在反覆運算器中宣告 ref 局部變數或類型的變數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_declarator清單,每個清單都會導入新的常數。 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 和 表達式 替代方案都適用,則應選擇前者。 (~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; } } }
end 範例
除非另有指定,否則所有文法元素的語意與 method_declaration (\15.6.1) 相同,在本機函式的內容中讀取,而不是方法。
local_function_declaration的標識碼在其宣告的區塊範圍中應是唯一的,包括任何封入局部變數宣告空格。 其中一個結果是不允許多載 local_function_declaration。
local_function_declaration可能包含一個 (async
) 修飾詞和一個unsafe
修飾詞(~23.1) 修飾詞。 如果宣告包含 async
修飾詞,則傳回型別應為 void
或 «TaskType»
類型 ({15.15.1.1)。 如果宣告包含 static
修飾詞,則函式是靜態區域函式,否則為非靜態區域函式。 這是type_parameter_list或parameter_list包含屬性的編譯時間錯誤。 如果local函式是在不安全的內容中宣告的({23.2),則即使本機函式宣告不包含 unsafe
修飾詞,本機函式也可能包含不安全的程序代碼。
本機函式會在區塊範圍宣告。 非靜態局部函數可能會從封入範圍擷取變數,而靜態局部函數不得擷取變數(因此無法存取封入局部變數、參數、非靜態局部函數或 this
)。 如果非靜態局部函數的主體讀取擷取的變數,但在每次呼叫函式之前,都未明確指派擷取的變數,則這是編譯時期錯誤。 編譯程式應決定在傳回時明確指派哪些變數 (“\9.4.4.33)。
當的類型是 結構類型 this
時,這是本機函式主體存取 this
的編譯時期錯誤。 無論存取權是明確的(如 in this.x
)還是隱含的,都是如此(如同 , x
其中 x
是 結構的實例成員)。 此規則只會禁止這類存取,而且不會影響成員查閱是否會導致結構的成員。
本機函式主體 goto
包含語句、 break
語句或 continue
目標在區域函式主體外部的語句,這是編譯時期錯誤。
注意:下列規則適用於
this
,並goto
鏡像在 \12.19.3 中匿名函式的規則。 end note
本機函式可以在宣告之前從語匯點呼叫。 不過,在局部函式 ({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; } }
換句話說,本機函式宣告的位置不會影響包含函式中任何語句的觸達性。 end 範例
如果本機函式的自變數類型為 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
運算式只會計算值(將會捨棄),不允許做為 語句。 end note
執行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(); } }
end 範例
if
語句的執行方式如下:
- 評估 boolean_expression (~12.24)。
- 如果布爾表達式產生
true
,控件會傳送至第一個內嵌語句。 當控件到達該語句的結束點時,控件就會傳送至 語句的if
結束點。 - 如果布爾表達式產生
false
,如果else
元件存在,則會將控件傳送至第二個內嵌語句。 當控件到達該語句的結束點時,控件就會傳送至 語句的if
結束點。 - 如果布爾表達式產生
false
,而且else
元件不存在,則會將控件傳送至 語句的if
終點。
如果if
語句可觸達,而且布爾表達式沒有常數值 if
,則語句的第一個false
內嵌語句是可觸達的。
如果if
語句可連線,則語句的第二個if
內嵌語句如果存在,而且布爾表達式沒有常數值true
,則為可觸達。
如果至少可以觸達其中一個內嵌語句的結束點,語句的 if
結束點就可觸達。 此外,如果if
語句可連線,而且布爾表達式沒有常數值else
,則沒有部分的語句if
結束點true
。
13.8.3 Switch 語句
語句 switch
會選取執行語句清單,其具有對應至 switch 運算式值的相關聯參數標籤。
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
switch_statement包含 關鍵詞 switch
,後面接著括弧表達式(稱為 switch 運算式),後面接著switch_block。
switch_block包含零個或多個switch_section,以大括弧括住。 每個 switch_section 都包含一或多個 switch_label,後面接著一個 statement_list (~13.3.2)。 每個case
都有一個相關聯的模式(~11),其中會測試參數表達式的值。 如果 case_guard 存在,其表達式應隱含轉換成類型 bool
,而且該運算式會評估為符合案例的額外條件。
語句switch
是由 switch 運算式所建立。
- 如果 switch 表達式的類型為
sbyte
、byte
、short
、ushort
、int
uint
long
ulong
char
bool
、string
或 enum_type,或者如果它是對應至其中一種類型的可為 Null 實值型別,則為語句的switch
控管型別。 - 否則,如果剛好有一個使用者定義的隱含轉換,從參數表達式的類型到下列其中一個可能的控管類型:
sbyte
、、byte
short
ushort
int
、、uint
、、long
或ulong
char
string
對應至其中一個類型的可為 Null 實值型別,則轉換的類型就是語句的switch
控管型別。 - 否則,語句的
switch
控管型別是 switch 表達式的類型。 如果不存在這類類型,就會發生錯誤。
語句中最多可以有一個default
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. }
end 範例
switch
語句的執行方式如下:
- 參數表達式會評估並轉換成控管類型。
- 控制項會根據轉換的 switch 運算式值來傳送:
- 符合 switch 運算式值的相同
case
語句中標籤集合switch
中的語彙第一個模式,且 guard 運算式不存在或評估為 true,會導致控件在相符case
卷標之後傳送至語句清單。 - 否則,如果標籤
default
存在,控件會傳送至標籤之後的default
語句清單。 - 否則,控制權會傳送至 語句的
switch
結束點。
- 符合 switch 運算式值的相同
注意:未定義在運行時間比對模式的順序。 允許編譯程式(但不需要)依序比對模式,並重複使用已比對模式的結果,以計算其他模式比對的結果。 不過,編譯器必須判斷語法上第一個匹配表達式的模式,且保護子句不存在或評估為
true
。 end note
如果可連線到 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 區段時,應該使用明確的
goto case
或goto default
語句:switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }
end 範例
switch_section中允許多個標籤。
範例:範例
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }
有效。 此範例不會違反「沒有落入」規則,因為標籤
case 2:
和default:
是相同 switch_section的一部分。end 範例
注意:當意外省略語句時
break
,「無落入」規則會防止在 C 和 C++中發生的常見 Bug 類別。 例如,上述語句的switch
區段可以反轉,而不會影響 語句的行為:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }
end note
注意:參數區段的語句清單通常以 、
break
或goto case
語句結尾goto default
,但允許任何無法連線到語句清單結束點的建構。 例如,while
布爾表達式true
所控制的語句已知永遠不會到達其終點。 同樣地,throw
或return
語句一律會在別處傳輸控制權,而且永遠不會到達其終點。 因此,下列範例是有效的:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }
end note
範例:語句的
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; } }
end 範例
注意:如同字串相等運算符(~12.12.8),
switch
語句會區分大小寫,而且只有在 switch 表達式字串完全符合case
卷標常數時,才會執行指定的 switch 區段。 end note 當語句的switch
控管型別為string
或可為 Null 的實值型別時,允許值null
做為case
標籤常數。
switch_block的 statement_list 可能包含宣告語句 ({13.6)。 在 switch 區塊中宣告的局部變數或常數範圍是 switch 區塊。
如果至少下列其中一個為 true,則可連線切換標籤:
- switch 運算式是常數值,也是其中一個
- 卷標是
case
,其模式 會比對 該值(\11.2.1),而卷標的防護則不存在或不是值為 false 的常數表達式;或 - 它是標籤
default
,而且沒有 switch 區段包含大小寫標籤,其模式會符合該值,且其防護不存在或值為 true 的常數運算式。
- 卷標是
- switch 運算式不是常數值,也是
- 卷標是
case
沒有防護的 ,或具有值不是常數 false 的防護;或 - 它是標籤
default
和- 在參數控管型別沒有防護或具有值為常數 true 的 switch 語句案例中出現的模式集合,不是 完整的 參數控管型別 (~11.4) ; 或
- 參數控管類型是可為 Null 的型別,而且在 switch 語句中出現的模式集合沒有防護或具有 guards,其值為 true 的常數不包含符合值的
null
模式。
- 卷標是
- 參數標籤是由可
goto case
連線的 或goto default
語句所參考。
如果語句可連線, switch
且 switch 區段包含可連線的切換標籤,則可連線到指定 switch 區段的語句清單。
如果 switch 語句可觸達,而且至少有一個 switch
是 true,語句的終點是可連線的:
- 語句
switch
包含結束語句的break
可switch
連線語句。 - 沒有
default
標籤存在,也沒有- switch 表達式是非常數值,而且 switch 語句中出現的模式集合沒有防護或具有值為常數 true 的 guards,對於參數控管類型而言並不 詳盡 (\11.4)。
- switch 運算式是可為 Null 類型的非常數值,而且在 switch 語句中沒有 guards 或具有 guards 的模式,其值為 true 的常數不會符合 值
null
。 - switch 運算式是常數值,沒有
case
guard 或其 guard 為常數 true 的標籤會符合該值。
範例:下列程式代碼顯示 子句的簡潔用法
when
:static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }
var 大小寫會比對
null
、空字串或任何只包含空格符的字串。 end 範例
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
) 可用來將控制權傳送至內嵌語句的結束點(因此執行語句的另一個反覆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
) 可用來將控制權傳送至內嵌語句的結束點(因此執行語句的另一個反覆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_expressions (~13.7) 清單。 for_initializer所宣告的局部變數範圍是for_initializer、for_condition、for_iterator和embedded_statement。
for_condition,如果存在,應為boolean_expression(12.24)。
如果存在,則for_iterator是由以逗號分隔的statement_expressions (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
) 可用來將控制權傳送至內嵌語句的結束點(因此執行for_iterator並執行語句的另一個反覆專案for
, 從 for_condition 開始)。
如果下列其中一項 for
成立,語句的內嵌語句就可連線:
- 語句
for
是可連線的,而且不存在 任何for_condition 。 - 語句
for
是可連線的, 而且存在for_condition ,而且沒有常數值false
。
如果至少有下列其中一項 for
成立,語句的結束點就可連線:
- 語句
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
;
foreach 語句的local_variable_type和標識符會宣告 語句的反覆運算變數。
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
,則會從 XIEnumerable
隱含參考轉換成介面(因為System.Array
實作這個介面)。 集合類型是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
在內嵌語句中是唯讀的。 如果沒有從 (反復項目類型) 到 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
local_variable_type) 的身分識別轉換 (foreach
),則會產生錯誤,而且不會採取任何進一步的步驟。
foreach
表單foreach (ref readonly V v in x) «embedded_statement»
的語句具有類似的對等表單,但參考變數v
位於ref readonly
內嵌語句中,因此無法重新指派或重新指派。
注意:如果
x
具有 值null
,System.NullReferenceException
則會在運行時間擲回 。 end note
只要行為與上述擴充一致,實作即可以不同的方式實作指定的 foreach_statement ;例如,基於效能考慮。
在v
迴圈中的位置while
對於在embedded_statement中發生的任何匿名函式如何擷取它很重要。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
。 (請注意,在迴圈外部v
宣告while
的舊版 C# 。end 範例
區塊的 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
end 範例
範例:在下列範例中
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }
的型
n
別推斷為int
,為的numbers
反覆項目類型。end 範例
13.10 Jump 語句
13.10.1 一般
Jump 語句無條件傳輸控制件。
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
jump 語句傳輸控件的位置稱為 jump 語句的目標 。
當 jump 語句發生在區塊內,而該 Jump 語句的目標位於該區塊之外時,會說 jump 語句會 結束 區塊。 雖然 jump 語句可以將控制權從區塊移出,但它永遠無法將控制權傳送到區塊中。
跳躍語句的執行會因插入 try
語句的存在而複雜。 在沒有這類 try
語句的情況下,jump 語句會無條件地將控制權從 jump 語句轉移到其目標。 在這類介入 try
語句的存在下,執行會更加複雜。 如果 jump 語句結束一或多個 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
end 範例
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
語句可用來將控制權移出巢狀範圍。end note
語句的目標 goto case
為緊接 switch
在語句中的語句清單(~13.8.3),其中包含 case
具有指定常數值之常數模式且沒有防護的卷標。
goto case
如果語句不是以 switch
語句括住,如果最接近的封switch
入語句不包含這類 case
,或如果constant_expression無法隱含轉換成最接近封入語句的控管型別,switch
則會發生編譯時期錯誤。
語句的目標goto default
是緊接switch
在語句中包含標籤的語句清單(~13.8.3default
)。
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;一個包含表達式的表達式稱為 return-by-ref;而只包含ref
表達式的表達式則稱為傳回值。
從宣告為傳回值或傳回 by-ref 的方法使用傳回-no-value 的方法,這是編譯時期錯誤。。。
從宣告為 returns-no-value 或 returns-by-value 的方法使用傳回時錯誤。
從宣告為 returns-no-value 或 returns-by-ref 的方法使用傳回值是編譯時期錯誤。
如果表達式不是variable_reference,或是參考 ref 不是呼叫端內容(~9.7.2)的變數,則使用傳回時錯誤。
從以 method_modifierasync
的方法,使用傳回參考是編譯時期錯誤。
如果函式成員是具有傳回值方法(~15.6.11)、屬性或索引器或使用者定義運算符的傳回傳值 get 存取子,則表示函式成員會計算值。 傳回-no-value 的函式成員不會計算值,而且是具有有效傳回型 void
別的方法、屬性和索引器的 set 存取子、新增和移除事件存取子、實例建構函式、靜態建構函式和完成項。 傳回 by-ref 的函式成員不會計算值。
若為傳回值,則包含函式成員的有效傳回型別 (~15.6.11) 的隱含轉換 (~10.2) 應存在於表達式的類型。 針對傳回參考,識別轉換(~10.2.2)應該存在於表達式類型與包含函式成員的有效傳回型別之間。
return
語句也可以用於匿名函式表達式主體中(~12.19),並參與判斷這些函式存在哪些轉換(~10.7.1)。
語句出現在return
區塊中是編譯時期錯誤finally
(~13.11)。
return
語句的執行方式如下:
- 針對傳回值,會評估表達式,並透過隱含轉換將其值轉換成包含函式的有效傳回型別。 轉換的結果會成為函式所產生的結果值。 針對傳回的 ref, 會評估表達式 ,並將結果分類為變數。 如果封入方法的 return-by-ref 包含
readonly
,則產生的變數是只讀的。 -
return
如果語句是由一或多個try
具有catch
相關聯finally
區塊的區塊所括住,則控件一開始會傳送至finally
最try
內層語句的 區塊。 當控件到達區塊的finally
結束點時,控件會傳送至finally
下一個封入try
語句的 區塊。 此程式會重複執行,finally
直到執行所有封入try
語句的區塊為止。 - 如果包含函式不是異步函式,則會傳回控件給包含函式的呼叫端以及結果值,如果有的話。
- 如果包含的函式是異步函式,則會將控件傳回至目前的呼叫端,如果有任何,則會記錄在傳回工作中的結果值,如 (\15.15.3) 中所述。
return
因為語句無條件地將控制權傳送到別處,所以無法連線到語句的return
終點。
13.10.6 throw 語句
語句 throw
會擲回例外狀況。
throw_statement
: 'throw' expression? ';'
;
throw
表達式的語句會擲回評估表達式所產生的例外狀況。 運算式應該可以隱含轉換成 System.Exception
,而且評估表達式的結果會在擲回之前轉換成 System.Exception
。 如果轉換的結果為 null
, System.NullReferenceException
則會改為擲回 。
throw
沒有表達式的catch
語句只能在區塊中使用,在此情況下,該語句會重新擲回該區塊目前正在處理的catch
例外狀況。
throw
因為語句無條件地將控制權傳送到別處,所以無法連線到語句的throw
終點。
擲回例外狀況時,控件會傳送至可處理例外狀況之catch
封入語句中的第一try
個子句。 從擲回例外狀況點到將控件傳送至適當例外狀況處理程式的點所發生的程式稱為 例外狀況傳播。 例外狀況的傳播是由重複評估下列步驟所組成,直到 catch
找到符合例外狀況的 子句為止。 在此描述中 ,擲回點 一開始是擲回例外狀況的位置。 這個行為是在 (~21.4) 中指定。
在目前的函式成員中,會檢查封入擲回點的每個
try
語句。 針對每個語句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
子句不同,也就是說,前者也可能攔截其他語言的例外狀況。 end note
為了找出例外狀況的處理程式, catch
子句會依語匯順序進行檢查。
catch
如果 子句指定類型,但沒有例外狀況篩選,則為相同catch
語句的後續try
子句的編譯時期錯誤,以指定與該類型相同或衍生自的類型。
注意:如果沒有這項限制,就可以撰寫無法
catch
連線的子句。 end note
在 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
end 範例
這是 、 break
或 continue
語句的編譯時間錯誤goto
,可將控制權移出finally
區塊。
break
當、 continue
或 goto
語句發生在區塊中finally
時,語句的目標應位於相同的finally
區塊內,否則會發生編譯時期錯誤。
這是語句在區塊中return
發生的編譯時間錯誤finally
。
當執行到達 try
語句時,控制權會傳送至 try
區塊。 如果控件到達區塊的結束點 try
,而不會傳播例外狀況,控件就會在存在時傳送至 finally
區塊。
finally
如果沒有區塊存在,控件就會傳送至 語句的try
結束點。
如果已傳播例外狀況, catch
則會以語彙順序檢查子句,以尋找例外狀況的第一個相符專案。 搜尋比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
語句,則為 true。 如果控件到達區塊的結束點 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
end 範例
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
類別或結構,其中包含名為 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
語句來確保基礎檔案在寫入或讀取作業之後已正確關閉。end 範例
13.15 yield 語句
在yield
反覆運算器區塊 (~13.3) 中使用 語句,將值產生給反覆運算器之列舉值物件 (~15.14.5) 或可列舉的物件 (\15.14.6),或表示反覆運算器的結尾。
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield
是內容關鍵詞 (~6.4.4),只有在 或 return
關鍵詞之前break
立即使用時才具有特殊意義。
語句可以出現的位置有數個 yield
限制,如下所述。
- 語句出現在method_body、
yield
或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 }
end 範例
隱含轉換 (~10.2) 應該從 語句中的 yield return
表達式類型到迭代器的 yield 類型 (~15.14.4) 存在。
yield return
語句的執行方式如下:
- 會評估 語句中指定的表達式、隱含轉換成 yield 型別,並指派給
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
終點。