共用方式為


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.713.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_list13.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) 參考

注意:這表示 goto 語句可以在區塊和區塊外傳輸控制權,但絕不會傳送到區塊中。 end note

標籤有自己的宣告空間,而且不會干擾其他識別碼。

範例:範例

int F(int x)
{
    if (x >= 0)
    {
        goto x;
    }
    x = -x;
  x:
    return x;
}

有效,並使用 x 名稱作為參數和標籤。

end 範例

標記語句的執行與標籤標之後的語句執行完全對應。

除了一般控制流程所提供的可觸達性之外,如果標籤是由可goto連線語句參考標籤,則可觸達語句,除非goto語句位於區塊 try_statementtryfinally包含無法連線到其結束點的區塊,且加上卷標的語句位於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.4var 導致三個類別之間的語法模棱兩可,如下所示:

  • 如果範圍中沒有具名 var 的類型,且輸入符合 implicitly_typed_local_variable_declaration ,則會加以選擇;
  • 否則,如果名為 var 的類型位於範圍中, 則implicitly_typed_local_variable_declaration 不會被視為可能的相符專案。

local_variable_declaration,宣告子會引進每個變數,而宣告子是隱含型別、明確型別和 ref 局部變數的其中一個implicitly_typed_local_variable_declaratorexplicitly_typed_local_variable_declaratorref_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):

在宣告子前面的文字位置,或是在其宣告子內任何初始化表達式內,依名稱參照局部變數是錯誤的。 在局部變數的範圍內,宣告另一個具有相同名稱的局部變數、局部函數或常數是編譯時期錯誤。

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 的參考型別時,否則其類型為 Tref Tref_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_kindref 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_listparameter_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參考中參考basethis實例成員,或封入範圍中的局部變數、參數或非靜態局部函數。 不過,表達式中 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 + yx == 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 表達式的類型為 sbytebyteshortushortintuintlongulongcharboolstring或 enum_type,或者如果它是對應至其中一種類型的可為 Null 實值型別,則為語句的switch控管型別。
  • 否則,如果剛好有一個使用者定義的隱含轉換,從參數表達式的類型到下列其中一個可能的控管類型:sbyte、、byteshortushortint、、uint、、longulongcharstring對應至其中一個類型的可為 Null 實值型別,則轉換的類型就是語句的switch控管型別。
  • 否則,語句的 switch 控管型別是 switch 表達式的類型。 如果不存在這類類型,就會發生錯誤。

語句中最多可以有一個defaultswitch標籤。

如果任何參數標籤的模式不適用於輸入表達式的類型,則為錯誤。

如果任何交換器卷標的模式是由 (~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 結束點。

注意:未定義在運行時間比對模式的順序。 允許編譯程式(但不需要)依序比對模式,並重複使用已比對模式的結果,以計算其他模式比對的結果。 不過,編譯器必須判斷語法上第一個匹配表達式的模式,且保護子句不存在或評估為 trueend 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 casegoto 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

注意:參數區段的語句清單通常以 、 breakgoto case 語句結尾goto default,但允許任何無法連線到語句清單結束點的建構。 例如, while 布爾表達式 true 所控制的語句已知永遠不會到達其終點。 同樣地, throwreturn 語句一律會在別處傳輸控制權,而且永遠不會到達其終點。 因此,下列範例是有效的:

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包含結束語句的breakswitch連線語句。
  • 沒有 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包含結束語句的breakwhile連線語句。
  • 語句 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包含結束語句的breakdo連線語句。
  • 可觸達內嵌語句的終點,而且布爾運算式沒有常數值 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_initializerfor_conditionfor_iteratorembedded_statement

for_condition,如果存在,應為boolean_expression12.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包含結束語句的breakfor連線語句。
  • 語句 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同時包含 和 ,refreadonly反覆運算變數代表被視為只讀的變數。 否則,如果 foreach_statement 包含 refreadonly反覆運算變數代表可寫入的變數。

反覆運算變數會對應至局部變數,其範圍會延伸至內嵌語句。 在語句執行期間,反覆運算變數代表目前正在執行反覆專案的 foreach 集合專案。 如果反覆運算變數表示只讀變數,則如果內嵌語句嘗試修改它,或透過指派或 ++-- 運算符,或將它傳遞為參考或輸出參數,就會發生編譯時期錯誤。

在下列專案中,為了簡潔起見,IEnumerableIEnumeratorIEnumerable<T>IEnumerator<T> 會參考 命名空間System.CollectionsSystem.Collections.Generic中的對應型別。

語句的foreach編譯時間處理會先決定表達式的集合類型列舉值類型和反覆項目類型。 此判斷會繼續進行,如下所示:

  • 如果表達式的類型是數位類型X,則會從 X IEnumerable 隱含參考轉換成介面(因為System.Array實作這個介面)。 集合類型是 IEnumerable 介面,列舉值類型是 IEnumerator 介面,而反覆項目類型是數位類型的項目類型 X
  • 如果表示式的類型X為 ,則會從表示式dynamic介面 (~10.2.10)。IEnumerable 集合類型是 IEnumerable 介面,而列舉值類型是 IEnumerator 介面。 var如果識別元指定為 local_variable_type 則反覆運算類型為 ,否則為 dynamicobject
  • 否則,請判斷類型 X 是否有適當的 GetEnumerator 方法:
    • 在具有標識碼X且沒有類型自變數的類型GetEnumerator上執行成員查閱。 如果成員查閱不會產生相符專案,或產生模棱兩可,或產生不是方法群組的相符專案,請檢查可列舉的介面,如下所述。 如果成員查閱產生方法群組以外的任何專案,或沒有任何相符專案,建議發出警告。
    • 使用產生的方法群組和空的自變數清單來執行多載解析。 如果多載解析沒有產生任何適用的方法、產生模棱兩可的方法,或產生單一最佳方法,但該方法為靜態或不公用,請檢查可列舉的介面,如下所示。 如果多載解析產生任何不明確公用實例方法或不適用方法以外的任何專案,建議發出警告。
    • 如果方法的E傳回型GetEnumerator別不是類別、結構或介面類型,則會產生錯誤,而且不會採取任何進一步的步驟。
    • 成員查閱是以標識碼E執行,Current而且沒有類型自變數。 如果成員查閱不會產生任何相符專案,則結果為錯誤,或結果為允許讀取的公用實例屬性以外的任何專案,則會產生錯誤,而且不會採取任何進一步的步驟。
    • 成員查閱是以標識碼E執行,MoveNext而且沒有類型自變數。 如果成員查閱不會產生任何相符專案,則結果為錯誤,或結果為方法群組以外的任何專案,則會產生錯誤,而且不會採取任何進一步的步驟。
    • 多載解析是在具有空自變數清單的方法群組上執行。 如果多載解析沒有產生任何適用的方法、造成模棱兩可,或產生單一最佳方法,但該方法為靜態或不公用,或其傳回類型不是 bool,則會產生錯誤,而且不會採取任何進一步的步驟。
    • 集合類型為 X,列舉值類型為 E,而反覆項目類型是 屬性的類型 Current 。 屬性 Current 可能包含 ref 修飾詞,在此情況下,傳回的表達式是 選擇性只讀的variable_reference#9.5)。
  • 否則,請檢查可列舉介面:
    • 如果從 隱含轉換成 TᵢX的所有型別中,有一個唯一的類型IEnumerable<Tᵢ>TT例如 不是 dynamic ,而所有其他Tᵢ類型都有從 隱含轉換IEnumerable<T>IEnumerable<Tᵢ>,則集合類型為 介面、列舉值類型為 介面IEnumerable<T>IEnumerator<T>,而反覆項目類型為 T
    • 否則,如果有多個這類類型 T,則會產生錯誤,而且不會採取任何進一步的步驟。
    • 否則,如果從 隱含轉換 XSystem.Collections.IEnumerable 介面,則集合類型是這個介面,列舉值型別是 介面 System.Collections.IEnumerator,而反覆項目類型為 object
    • 否則,會產生錯誤,而且不會採取任何進一步的步驟。

如果成功,上述步驟會明確產生集合類型 C、列舉值型 E 別和反覆項目類型 Tref Tref readonly Tforeach表單的語句

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-reassigned (\12.21.3)。 如果沒有從 (反復項目類型) 到 T語句中的Vlocal_variable_type) 的身分識別轉換 (foreach),則會產生錯誤,而且不會採取任何進一步的步驟。

foreach表單foreach (ref readonly V v in x) «embedded_statement»的語句具有類似的對等表單,但參考變數v位於ref readonly內嵌語句中,因此無法重新指派或重新指派。

注意:如果 x 具有 值 nullSystem.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 主體是根據下列步驟建構的:

  • 如果從 隱含轉換成 ESystem.IDisposable 介面,則

    • 如果 E 是不可為 Null 的實值類型,則 finally 子句會擴充為語意對等的:

      finally
      {
          ((System.IDisposable)e).Dispose();
      }
      
    • 否則,子 finally 句會擴充為的語意對等專案:

      finally
      {
          System.IDisposable d = e as System.IDisposable;
          if (d != null)
          {
              d.Dispose();
          }
      }
      

      不同之處在於,如果 E 是實值型別,或具現化為實值型別的類型參數,則的轉換eSystem.IDisposable不會造成 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 區塊的區塊,控件一開始會傳送至 finallytry 內層語句的 區塊。 當控件到達區塊的 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封入 、 whiledoforforeach 語句。

break_statement
    : 'break' ';'
    ;

語句的目標break為最接近封switch入 、、whiledoforforeach 語句的結束點。 break如果語句不是以 switchwhiledoforforeach 語句括住,則會發生編譯時期錯誤。

當多個switchwhiledoforforeach 語句彼此巢狀時,break語句只會套用至最內部的語句。 若要跨多個巢狀層級傳輸控制權, goto 應使用語句 (~13.10.4)。

語句 break 無法結束 finally 區塊 (~13.11)。 break當語句發生在區塊內finally時,語句的目標break應位於相同的finally區塊內,否則會發生編譯時期錯誤。

break語句的執行方式如下:

  • break如果語句結束一或多個try具有相關聯finally區塊的區塊,則控件一開始會傳送至finallytry內層語句的 區塊。 當控件到達區塊的 finally 結束點時,控件會傳送至 finally 下一個封入 try 語句的 區塊。 此程式會重複執行, finally 直到執行所有介入 try 語句的區塊為止。
  • 控件會傳送至 語句的目標 break

break因為語句無條件地將控制權傳送到別處,所以無法連線到語句的break終點。

13.10.3 繼續語句

語句 continue 會啟動最近封 while入 、 doforforeach 語句的新反覆專案。

continue_statement
    : 'continue' ';'
    ;

語句的目標 continue 為最接近封入 whiledoforforeach 語句之內嵌語句的結束點。 continue如果語句不是以 whiledoforforeach 語句括住,則會發生編譯時期錯誤。

當多個 whiledoforforeach 語句彼此巢狀時, continue 語句只會套用至最內部的語句。 若要跨多個巢狀層級傳輸控制權, goto 應使用語句 (~13.10.4)。

語句 continue 無法結束 finally 區塊 (~13.11)。 continue當語句發生在區塊內finally時,語句的目標continue應位於相同的finally區塊內,否則會發生編譯時期錯誤。

continue語句的執行方式如下:

  • continue如果語句結束一或多個try具有相關聯finally區塊的區塊,則控件一開始會傳送至finallytry內層語句的 區塊。 當控件到達區塊的 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區塊的區塊,則控件一開始會傳送至finallytry內層語句的 區塊。 當控件到達區塊的 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區塊的區塊所括住,則控件一開始會傳送至finallytry內層語句的 區塊。 當控件到達區塊的 finally 結束點時,控件會傳送至 finally 下一個封入 try 語句的 區塊。 此程式會重複執行, finally 直到執行所有封入 try 語句的區塊為止。
  • 如果包含函式不是異步函式,則會傳回控件給包含函式的呼叫端以及結果值,如果有的話。
  • 如果包含的函式是異步函式,則會將控件傳回至目前的呼叫端,如果有任何,則會記錄在傳回工作中的結果值,如 (\15.15.3) 中所述。

return因為語句無條件地將控制權傳送到別處,所以無法連線到語句的return終點。

13.10.6 throw 語句

語句 throw 會擲回例外狀況。

throw_statement
    : 'throw' expression? ';'
    ;

throw表達式的語句會擲回評估表達式所產生的例外狀況。 運算式應該可以隱含轉換成 System.Exception,而且評估表達式的結果會在擲回之前轉換成 System.Exception 。 如果轉換的結果為 nullSystem.NullReferenceException 則會改為擲回 。

throw沒有表達式的catch語句只能在區塊中使用,在此情況下,該語句會重新擲回該區塊目前正在處理的catch例外狀況。

throw因為語句無條件地將控制權傳送到別處,所以無法連線到語句的throw終點。

擲回例外狀況時,控件會傳送至可處理例外狀況之catch封入語句中的第一try個子句。 從擲回例外狀況點到將控件傳送至適當例外狀況處理程式的點所發生的程式稱為 例外狀況傳播。 例外狀況的傳播是由重複評估下列步驟所組成,直到 catch 找到符合例外狀況的 子句為止。 在此描述中 ,擲回點 一開始是擲回例外狀況的位置。 這個行為是在 (~21.4) 中指定。

  • 在目前的函式成員中,會檢查封入擲回點的每個 try 語句。 針對每個語句 S,從最 try 內部語句開始,並以最 try 外層語句結尾,會評估下列步驟:

    • 如果 的 tryS 區塊括住擲回點,如果 S 具有一或多個 catch 子句, catch 則會檢查 子句,以便找出適合例外狀況的處理程式。 第一catch個子句,指定例外狀況類型T(或運行時間表示例外狀況類型的類型參數),讓衍生自 TE運行時間類型T視為相符專案。 如果 子句包含例外狀況篩選條件,則會將例外狀況物件指派給例外狀況變數,並評估例外狀況篩選條件。 catch當子句包含例外狀況篩選條件時,如果例外狀況篩選條件評估為 catch,該true子句就會被視為相符專案。 一般 catch~13.11) 子句會被視為任何例外狀況類型的相符專案。 如果找到比 catch 對子句,則會藉由將控件傳送至該 catch 子句的區塊來完成例外狀況傳播。
    • 否則,如果 try 區塊或 catchS 區塊括住擲回點,如果 S 具有 finally 區塊,則會將控件傳送至 finally 區塊。 finally如果區塊擲回另一個例外狀況,則會終止目前例外狀況的處理。 否則,當控件到達 區塊的 finally 結束點時,會繼續處理目前的例外狀況。
  • 如果例外狀況處理程式不在目前的函式調用中,則會終止函式調用,併發生下列其中一項:

    • 如果目前的函式為非異步函式,則上述步驟會針對函式的呼叫端重複,其擲回點會對應至叫用函式成員的語句。

    • 如果目前的函式是異步和工作傳回,則會在傳回工作中記錄例外狀況,這會放入錯誤或取消狀態,如 \15.15.3 中所述

    • 如果目前的函式是異步和 void-returning,則會如 \15.15.4 中所述通知目前線程的同步處理內容。

  • 如果例外狀況處理終止目前線程中的所有函式成員調用,表示線程沒有例外狀況的處理程式,則線程本身會終止。 這類終止的影響是實作定義。

13.11 try 語句

try語句提供攔截區塊執行期間發生的例外狀況的機制。 此外, try 語句可讓您指定一律在控件離開 try 語句時執行的程式代碼區塊。

try_statement
    : 'try' block catch_clauses
    | 'try' block catch_clauses? finally_clause
    ;

catch_clauses
    : specific_catch_clause+
    | specific_catch_clause* general_catch_clause
    ;

specific_catch_clause
    : 'catch' exception_specifier exception_filter? block
    | 'catch' exception_filter block
    ;

exception_specifier
    : '(' type identifier? ')'
    ;

exception_filter
    : 'when' '(' boolean_expression ')'
    ;

general_catch_clause
    : 'catch' block
    ;

finally_clause
    : 'finally' block
    ;

try_statement是由 關鍵詞所組成,後面接著區塊try,然後是零個或多個catch_clauses,然後是選擇性的finally_clause。 至少應有一個 catch_clausefinally_clause

在exception_specifier類型中,如果它是type_parameter,則為 或其有效基類,應System.Exception為 或衍生自它的型別。

catch當 子句同時指定class_type標識符時,就會宣告指定名稱和類型的例外狀況變數。 例外狀況變數會導入specific_catch_clause的宣告空間中({7.3)。 在執行 exception_filtercatch 區塊期間,例外狀況變數代表目前正在處理的例外狀況。 為了進行明確的指派檢查,例外狀況變數會被視為在其整個範圍中明確指派。

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 範例

這是 、 breakcontinue 語句的編譯時間錯誤goto,可將控制權移出finally區塊。 break當、 continuegoto 語句發生在區塊中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語句。 如果控件傳輸是因正常執行、執行 breakcontinuegotoreturn 語句而發生,還是因為將例外狀況傳播而傳出 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 已核取和未核取的語句

與語句可用來控制整數型別算術運算和轉換的溢位檢查內容。checkedunchecked

checked_statement
    : 'checked' block
    ;

unchecked_statement
    : 'unchecked' block
    ;

語句checked會使區塊中的所有表達式在核取的內容中評估,而 語句會導致區塊unchecked表達式在未核取的內容中評估。

checked和語句完全相當於 uncheckedcheckedunchecked 運算符 (~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();
    }
}

除了的resourceSystem.IDisposable演員不得造成拳擊發生。

否則,當 是ResourceTypedynamic,展開為

{
    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_bodyyieldaccessor_body之外,這是編譯時期錯誤
  • 在匿名函式內出現的語句(任一種形式)是編譯時間錯誤 yield
  • 語句出現在 語句的 子句yield中,這是一個編譯時期錯誤finallytry
  • 語句在包含任何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區塊的區塊所括住,則控件一開始會傳送至finallytry內層語句的 區塊。 當控件到達區塊的 finally 結束點時,控件會傳送至 finally 下一個封入 try 語句的 區塊。 此程式會重複執行, finally 直到執行所有封入 try 語句的區塊為止。
  • 控件會傳回反覆運算器區塊的呼叫端。 這是 MoveNext 列舉值物件的方法或 Dispose 方法。

yield break因為語句無條件地將控制權傳送到別處,所以無法連線到語句的yield break終點。