7.1 應用程式啟動
程式可以編譯為類別庫,以做為其他應用程式的一部分,或作為可能直接啟動的應用程式。 判斷此編譯模式的機制是實作定義,而且是此規格的外部。
編譯為應用程式的程式至少應包含一個符合下列需求的進入點的方法:
- 名稱應為
Main。 - 它應該是
static。 - 它不得為泛型。
- 它應該以非泛型類型宣告。 如果宣告方法的類型是巢狀類型,則其封入類型不可以是泛型。
- 如果方法的傳回型別為
async或System.Threading.Tasks.Task,它可能會有System.Threading.Tasks.Task<int>修飾詞。 - 傳回型別應該是
void、int、System.Threading.Tasks.Task或System.Threading.Tasks.Task<int>。 - 它不可以是沒有實作的部分方法(~15.6.9)。
- 參數清單應該是空的,或具有類型的
string[]單一值參數。
注意:具有
async修飾詞的方法必須具備上面指定的兩種回傳型別中的其中一種,才能作為進入點。async void方法或async方法傳回不同的可等待型別,例如ValueTask或ValueTask<int>,不符合作為進入點的資格。 註尾
如果在程式內宣告了限定為進入點的多個方法,可以使用外部機制來指定哪個方法被視為應用程式的實際進入點。 如果找到傳回型別為int或void的合格方法,那麼任何傳回型別為System.Threading.Tasks.Task或System.Threading.Tasks.Task<int>的合格方法將不被視為進入點方法。 將程式編譯為不具有唯一進入點的應用程式是編譯時錯誤。 編譯為類別庫的程式可能包含符合應用程式進入點的方法,但產生的連結庫沒有進入點。
一般而言,方法的宣告輔助功能({7.5.2)是由宣告中指定的存取修飾詞({15.3.6)所決定,而型別的宣告輔助功能則由其宣告中指定的存取修飾詞所決定。 為了讓給定型別的給定方法可被呼叫,型別和成員都必須是可存取的。 不過,應用程式進入點是特殊案例。 具體而言,不論應用程式的宣告存取範圍為何,以及其封入類型宣告的宣告存取範圍為何,執行環境都可以存取應用程式的進入點。
當進入點方法的傳回型別為 System.Threading.Tasks.Task 或 System.Threading.Tasks.Task<int>時,編譯程式應合成呼叫對應 Main 方法的同步進入點方法。 方法的參數和傳回型別是基於合成方法:Main。
- 合成方法的參數清單與方法的參數
Main清單相同 - 如果方法的
Main傳回型別為System.Threading.Tasks.Task,則合成方法的傳回型別為void - 如果方法的
Main傳回型別為System.Threading.Tasks.Task<int>,則合成方法的傳回型別為int
合成方法的執行如下所示:
- 合成方法會呼叫
Main方法,如果string[]方法具有這類參數,則會Main將其參數值當做自變數傳遞。 - 若
Main方法擲回例外狀況,則該例外狀況由合成方法傳播。 - 否則,合成的進入點會等待所傳回的工作完成,並在該工作上呼叫
GetAwaiter().GetResult(),使用無參數的實例方法或由§C.3所描述的擴充方法。 如果任務失敗,GetResult()將會拋出例外,而這個例外會由合成方法進行傳播。 - 對於傳回型別為
Main的方法System.Threading.Tasks.Task<int>,如果工作順利完成,來自int的GetResult()值將從合成方法中傳回。
應用程式的有效進入點是程式中宣告的進入點,或者,如果需要,則是如上所述的合成方法。 因此,有效進入點的傳回型別一律 void 為 或 int。
執行應用程式時,會建立新的應用程式域。 應用程式的不同具現化可能同時存在於同一部計算機上,而且每個都有它自己的應用程式域。 應用程式域可藉由作為應用程式狀態的容器來啟用應用程式隔離。 應用程式域做為應用程式中所定義型別的容器和界限,以及其所使用的類別庫。 載入至一個應用程式域的類型與載入另一個應用程式域的相同類型不同,而且物件實例不會直接在應用程式域之間共用。 例如,每個應用程式域都有其本身的靜態變數複本,而且每個應用程式域最多會執行一次類型的靜態建構函式。 實作可以自由提供實作定義的政策或機制,用於建立和銷毀應用程式域。
當執行環境呼叫應用程式的有效進入點時,就會發生應用程式啟動。 如果有效的進入點宣告參數,則在應用程式啟動期間,實作應確保該參數的初始值是字串陣列的非 Null 參考。 此陣列應該包含字串的非 Null 參考,稱為 應用程式參數s,這些值會在應用程式啟動之前由主機環境提供實作定義的值。 其目的是要在應用程式啟動之前,從裝載環境中的其他地方提供已決定的資訊給該應用程式。
注意:在支援命令列的系統上,應用程式參數對應於通常所稱的命令列參數。 註尾
如果有效進入點的傳回類型為 int,則執行環境從方法調用的傳回值會用於應用程式終止 (~7.2)。
除了上述情況,進入點方法的行為在各方面都與非進入點的方法相同。 特別是,如果在應用程式的生命週期中,進入點在任何其他時間被調用,例如透過一般方法調用,則不會有任何特殊處理:如果有參數,它可能的初始值為null,或是引用了一個包含 null 參考的數值的非null值。 同樣地,進入點的傳回值除了被執行環境呼叫時之外,沒有特別的意義。
7.2 應用程式終止
將控制權傳回執行環境稱為 應用程式終止。
如果應用程式有效進入點方法的傳回型別為 int 且執行完成而不會產生例外狀況,則傳回的值int會做為應用程式的終止狀態代碼。 此程式代碼的目的是允許成功或失敗的通訊到執行環境。 如果有效進入點方法的傳回型別是 void 且執行完成而不會產生例外狀況,則終止狀態代碼為 0。
如果有效的進入點方法因例外狀況而終止 (§22.4) ,則結束碼是實作定義的。 此外,實作可能會提供替代 API 來指定結束代碼。
在應用程式終止時是否執行終結器(§15.13)是實作定義的。
注意:除非清除已被抑制(例如,透過呼叫連結庫方法),否則 .NET Framework 的實作會盡一切合理努力呼叫其所有尚未被垃圾收集的物件終結器(
GC.SuppressFinalize)。 註尾
7.3 宣告
C# 程式中的宣告會定義程式的組成元素。 C# 程式是使用命名空間來組織。 這些是使用命名空間宣告 ({14) 來引進的,其中包含類型宣告和巢狀命名空間宣告。 類型宣告 (§14.7) 可用來定義類別 (§15)、結構 (§16)、介面 (§19)、列舉 (§20) 和委派 (§21)。 類型宣告中允許的成員類型取決於類型宣告的形式。 例如,類別宣告可以包含常數的宣告(§15.4)、欄位(§15.5)、方法(§15.6)、屬性(§15.7)、事件(§15.8)、索引器(§15.9)、運算子(§15.10)、實例建構函式(§15.11)、靜態建構函式(§15.12)、終結者(§15.13)和巢狀類型(§15.3.9)。
宣告會在宣告所屬的 宣告空間 中定義名稱。 在宣告空間中引入同名成員的兩個或多個宣告會導致編譯時錯誤,但以下情況除外:
- 在相同的宣告空間中,允許有兩個或多個具有相同名稱的命名空間宣告。 這類命名空間宣告會匯總成形成單一邏輯命名空間,並共用單一宣告空間。
- 在不同程式中宣告的項目,若位於相同的命名空間中,允許使用相同的名稱。
注意:不過,如果包含在相同的應用程式中,這些宣告可能會造成模棱兩可。 註尾
- 相同宣告空間中允許兩個或多個具有相同名稱但相異簽章的方法({7.6)。
- 相同宣告空間中允許兩個或多個具有相同名稱但類型參數相異數目的類型宣告({7.8.2)。
- 在相同宣告空間中具有部分修飾詞的兩個或多個類型宣告,可以共用相同名稱、相同類型參數數目和相同的分類(類別、結構或介面)。 在此情況下,類型宣告會貢獻於單一類型,並且本身會合併成單一宣告空間(§15.2.7)。
- 命名空間宣告和相同宣告空間中的類型宣告可以共用相同的名稱,只要類型宣告至少有一個類型參數({7.8.2)。
宣告空間有數種不同類型的宣告空間,如下所述。
- 在程式的所有編譯單位中,namespace_member_declaration如果沒有包圍在namespace_declaration中,將屬於稱為全域宣告空間的單一合併宣告空間的成員。
- 在程式的所有編譯單位中,namespace_declaration中具有相同完整命名空間名稱的namespace_member_declaration是單一合併宣告空間的成員。
- 每個 compilation_unit 和 namespace_body 都有 別名宣告空間。 每個extern_alias_directive和using_alias_directive的compilation_unit或namespace_body都會為別名宣告區域(§14.5.2)貢獻成員。
- 每個非部分類別、結構或介面宣告都會建立新的宣告空間。 每個部分類別、結構或介面宣告都會參與相同程式中所有相符元件共用的宣告空間({16.2.4)。 名稱會透過 class_member_declarations、struct_member_declarations、interface_member_declarations 或 type_parameters 導入此宣告空間。 除了多載的實例建構函式宣告和靜態建構函式宣告之外,類別、結構或介面不能包含與類別、結構或介面同名的成員宣告。 類別、結構或介面允許多載方法和索引器的宣告。 此外,類別或結構允許多載實例建構函式和運算符的宣告。 例如,類別、結構或介面可能包含多個具有相同名稱的方法宣告,前提是這些方法宣告在簽章中不同({7.6)。 請注意,基類不會參與類別的宣告空間,而基底介面不會參與介面的宣告空間。 因此,衍生類別或介面可以宣告與繼承成員同名的成員。 據說這樣一個成員會 隱藏 繼承的成員。
- 每個委派宣告都會建立新的宣告空間。 名稱會透過參數(fixed_parameter s 和 parameter_arrays) 和 type_parameters 匯入此宣告空間。
- 每個列舉宣告都會建立新的宣告空間。 名稱會透過 enum_member_declarations 導入此宣告空間。
- 每個方法宣告、屬性宣告、屬性存取子宣告、索引器宣告、索引器存取子宣告、運算符宣告、實例建構函式、匿名函式和local函式都會建立稱為局部變數宣告空間的新宣告空間。 名稱會透過參數(fixed_parameter s 和 parameter_arrays) 和 type_parameters 匯入此宣告空間。 屬性或索引器的 set 存取子會將名稱
value引進為參數。 函式成員、匿名函式或區域函式主體,如果有的話,會被視為嵌入在局部變數宣告的空間內。 當區域變數宣告範圍和巢狀區域變數宣告範圍包含具有相同名稱的變數時,在巢狀區域名稱的範圍內,外部區域名稱會被巢狀區域名稱隱藏 (§7.7.1)。 - 其他局部變數宣告空格可能會發生在成員宣告、匿名函式和區域函式內。 名稱會透過 模式、declaration_expression、declaration_statement 和 exception_specifier,引入這些宣告空間中。 局部變數宣告空間是可以巢狀的,但是如果局部變數宣告空間和其巢狀局部變數宣告空間中存在同名元素,則會發生錯誤。 因此,在巢狀宣告空間中,不可能在封入宣告空間中宣告局部變數、局部函數或常數,其名稱與參數、類型參數、局部變數、局部函數或常數相同。 只要兩個宣告空間都不包含另一個宣告空間,即可以包含具有相同名稱的元素。 本機宣告空間是由下列建構所建立:
- 欄位和屬性宣告中的每個變數初始化器都會引入其自身的本地變數宣告空間,該空間不嵌套於任何其他本地變數宣告空間中。
- 函式成員、匿名函式或局部函數的主體,如果有的話,會建立局部變數宣告空間,該空間會被視為巢狀於函式的局部變數宣告空間內。
- 每個 constructor_initializer 都會建立實例建構函式宣告內巢狀的局部變數宣告空間。 建構函式主體的局部變數宣告空間會進一步巢狀於這個宣告空間內。
- 每個 區塊、 switch_block、 specific_catch_clause、 iteration_statement 和 using_statement 都會建立巢狀局部變數宣告空間。
- 不屬於statement_list的每個embedded_statement都會建立巢狀局部變數宣告空間。
- 每個 switch_section 都會建立巢狀局部變數宣告空間。 不過,直接在statement_list的switch_section內宣告的變數(但不在statement_list內的巢狀局部變數宣告空間內)會直接新增至封入的switch_block的局部變數宣告空間,而不是switch_section的局部變數宣告空間。
- query_expression (§12.22.3) 的語法轉譯可能會引進一或多個 lambda 運算式。 做為匿名函式,每個函式都會建立局部變數宣告空間,如上所述。
- 每個 區塊 或 switch_block 都會為標籤建立個別的宣告空間。 名稱會透過 labeled_statements 導入此宣告空間,而名稱會透過 goto_statement來參考。 區塊 的標籤宣告空間 包含任何巢狀區塊。 因此,在巢狀區塊內,不可能宣告與封閉區塊中同名的標籤。
注意:直接在switch_section中宣告的變數會被新增至switch_block的局部變數宣告空間,而不是switch_section,這可能會導致出人意料的程式碼。 在下列範例中,局部變數
y位於預設案例的 switch 區段中,儘管宣告出現在案例 0 的 switch 區段中。 局部變數z不在預設案例的 switch 區段範圍內,因為它會在發生宣告之 switch 區段的局部變數宣告空間中引進。int x = 1; switch (x) { case 0: int y; break; case var z when z < 10: break; default: y = 10; // Valid: y is in scope Console.WriteLine(x + y); // Invalid: z is not scope Console.WriteLine(x + z); break; }註尾
宣告名稱的文字順序通常不重要。 特別是,宣告和使用命名空間、常數、方法、屬性、事件、索引器、運算元、實例建構函式、完成項、靜態建構函式和類型時,文字順序並不重要。 宣告順序在下列方面相當重要:
- 欄位宣告的宣告順序會決定執行其初始化表達式的順序(如果有的話)({15.5.6.2, {15.5.6.3)。
- 使用局部變數之前,應先定義局部變數(~7.7)。
- 列舉成員宣告 (§20.4) 的宣告順序在省略 constant_expression 值時很重要。
範例:命名空間的宣告空間是「開放式的」,而具有相同完整名稱的兩個命名空間宣告會參與相同的宣告空間。 例如:
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }上述的兩個命名空間宣告會貢獻到同一個宣告空間,在此案例中,宣告了兩個類別,其完整名稱以
Megacorp.Data.Customer和Megacorp.Data.Order表示。 因為這兩個宣告會參與相同的宣告空間,所以如果每個宣告都包含同名類別的宣告,就會造成編譯時期錯誤。結束範例
注意:如上所述,區塊的宣告空間會包含任何巢狀區塊。 因此,在下列範例中,
F和G方法會產生編譯時期錯誤,因為名稱i是在外部區塊中宣告,而且無法在內部區塊中重新宣告。 不過,由於兩個H的宣告在不同的非巢狀區塊中,因此I和i方法都是有效的。class A { void F() { int i = 0; if (true) { int i = 1; } } void G() { if (true) { int i = 0; } int i = 1; } void H() { if (true) { int i = 0; } if (true) { int i = 1; } } void I() { for (int i = 0; i < 10; i++) { H(); } for (int i = 0; i < 10; i++) { H(); } } }註尾
7.4 成員
7.4.1 一般
命名空間和類型具有 成員s。
注意:實體的成員通常是透過使用以實體參考開頭的限定名稱,後面接著 “
.” 令牌,後面接著成員的名稱來取得。 註尾
型別的成員是在型別宣告中宣告,或 繼承 自型別的基類。 當類型繼承自基類時,除了實例建構函式、完成項和靜態建構函式之外,基類的所有成員都會成為衍生型別的成員。 基類成員的宣告存取範圍不會控制是否繼承成員—繼承延伸至不是實例建構函式、靜態建構函式或完成項的任何成員。
注意:不過,繼承的成員可能無法在衍生型別中存取,例如,因為其宣告的可見性(§7.5.2)。 註尾
7.4.2 命名空間成員
沒有封入命名空間的命名空間和類型是全域命名空間的成員。 這會直接對應至全域宣告空間中宣告的名稱。
命名空間內宣告的命名空間和類型是該命名空間的成員。 這會直接對應至命名空間宣告空間中宣告的名稱。
命名空間沒有存取限制。 無法宣告私用、受保護或內部命名空間,且命名空間名稱一律可公開存取。
7.4.3 結構成員
結構的成員是結構中宣告的成員,以及繼承自結構直接基類 System.ValueType 和間接基類 object的成員。
簡單型別的成員會直接對應至簡單型別所別名結構類型的成員(~8.3.5)。
7.4.4 列舉成員
列舉的成員包括列舉中宣告的常數,以及繼承自列舉的直接基類System.Enum和間接基類System.ValueTypeobject的成員。
7.4.5 類別成員
類別的成員是類別中宣告的成員,以及繼承自基類的成員(除了沒有基類的類別 object 除外)。 繼承自基類的成員包括常數、字段、方法、屬性、事件、索引器、運算符和基類的類型,但不是基類的實例建構函式、完成項和靜態建構函式。 基類成員會繼承,不論其存取權限。
類別宣告可能包含常數、字段、方法、屬性、事件、索引器、運算元、實例建構函式、完成項、靜態建構函式和類型的宣告。
object(§8.2.3)和string(§8.2.5)的成員直接對應到它們作為別名的類別類型成員。
7.4.6 介面成員
在介面及其所有基底介面中宣告的成員,都是這個介面的成員。
注意:嚴格來說,類別
object中的成員不是任何介面的成員 (§19.4)。 然而,類別object的成員可以透過任何介面類型中的成員查閱來取得(§12.5)。 註尾
7.4.7 陣列成員
陣列的成員是繼承自 類別 System.Array的成員。
7.4.8 委派成員
委派會從類別 System.Delegate繼承成員。 此外,它包含一個方法, Invoke 其返回類型和參數清單在其聲明中指定相同 (§21.2) 。 此方法的叫用行為應與相同委派實例上的委派調用 (§21.6) 相同。
實作過程可能透過繼承或直接在委派本身中提供其他成員。
7.5 成員存取
7.5.1 一般
成員的宣告允許控制成員存取。 成員的可存取性是由成員的宣告存取範圍(§7.5.2)和直接包含其的型別的存取範圍結合確定的(如果存在的話)。
允許存取特定成員時,該成員據稱是 可存取的。 相反地,不允許存取特定成員時,表示成員無法 存取。 當存取發生的文字位置包含在成員的可存取域(§7.5.3)中時,即允許存取該成員。
7.5.2 宣告的輔助功能
成員 的宣告存取範圍 可以是下列其中一項:
- Public,這是藉由在
public成員宣告中包含修飾詞來選取。 的public直覺意義是「存取不受限制」。 - Protected 是在成員宣告中包含
protected修飾詞來設定的。 直觀protected的含義是「存取僅限於包含的類別或接口,或從包含類型派生的類別或介面」。 - 內部,這是藉由在
internal成員宣告中包含修飾詞來選取。 的直覺意義internal是「存取受限於此元件」。 - 受保護的內部存取層級,透過在成員宣告中包含
protected和internal修飾詞來選取。protected internal的直覺意義是「在此組件內可存取,並可由從包含類別衍生的型別存取」。 - 私用保護,藉由在成員宣告中包含
private和protected修飾詞來選取。private protected的直覺意義是「在此元件內可由包含類別及其衍生類別的型別存取」。 - Private,這是藉由在
private成員宣告中包含修飾詞來選取。 的直覺意義private是「存取受限於包含的類型」。
根據成員宣告所在的上下文,僅允許特定類型的宣告可存取性。 此外,當成員宣告不包含任何存取修飾詞時,宣告所在的內容會決定預設宣告的存取範圍。
- 命名空間隱含地有
public宣告的存取性。 命名空間宣告上不允許任何存取修飾詞。 - 直接在編譯單位或命名空間中宣告的類型(而不是在其他類型內)可以具有
public或internal宣告的可見性,且預設為internal宣告的可見性。 - 類別成員可以具有任何允許的宣告存取權限類型,且預設為
private宣告的存取權限。注意:宣告為類別成員的類型可以具有任何允許的宣告的存取層級,而宣告為命名空間成員的類型只能有
public或internal的宣告存取層級。 註尾 - 結構成員可以具有
public、internal或private宣告的存取層級,且預設為private宣告的存取層級,因為結構是隱含封閉的。 中struct引進的結構成員(亦即,不是由該結構繼承),不能有protected、protected internal或private protected宣告的存取範圍。注意:宣告為結構成員的類型可以具有
public、internal或private宣告的輔助功能,而宣告為命名空間成員的類型只能public有或internal宣告的輔助功能。 註尾 - 介面成員已隱式宣告存取權限。 在介面成員宣告中不允許使用任何存取修飾詞。
- 列舉成員隱含地具有已宣告的存取權限。 列舉成員宣告上不允許任何存取修飾詞。
7.5.3 無障礙領域
成員的 存取網域 由程式文字的各個(可能不相交的)區段組成,在這些區段中允許對成員進行存取。 為了定義成員的存取範圍域,如果成員未在類型內宣告,則表示成員是最上層,如果成員是在另一個類型內宣告,則表示成員是巢狀。 此外,程式的程式文字會定義為程式所有編譯單位中包含的所有文字,而型別的程式文字則定義為該類型之type_declaration中包含的所有文字(包括可能是類型內巢狀的類型)。
預先定義型別的可及性領域(例如 object、int 或 double)是無限的。
在程式中宣告的最上層未系結類型T(P)的可存取性領域定義如下:
- 如果
T的宣告可存取範圍是公用的,則T的可存取域為P的程式文本,以及任何參考P的程式。 - 如果
T的宣告可存取性是內部的,那麼T的可存取性範圍是P的程式碼。
注意:根據這些定義,最上層未綁定類型的可存取性範圍必定至少涵蓋宣告該類型之程序的程式碼。 註尾
構造型別 T<A₁, ..., Aₑ> 的可存取性領域是未綁定泛型類型 T 和型別引數 A₁, ..., Aₑ 的可存取性領域交集。
在程式M內類型T中宣告之巢狀成員P的存取範圍定義域如下(指出M本身可能是類型):
- 如果
M的宣告存取範圍是public,M的存取範圍領域就是T的存取範圍領域。 - 如果
M的宣告存取範圍是protected internal,則讓D成為P的程式文字與宣告於T之外的、任何衍生自P之型別的程式文字的聯集。M的輔助功能網域是在與T和D的輔助功能網域相交的部分。 - 如果
M的宣告存取範圍是private protected,請讓D成為P的程式文字與T的程式文字及任何衍生自T的型別之交集。M的輔助功能網域是在與T和D的輔助功能網域相交的部分。 - 如果
M的宣告存取範圍是protected,請讓D成為T的程式碼與任何從T衍生的型別的程式碼的聯集。M的輔助功能網域是在與T和D的輔助功能網域相交的部分。 - 如果
M的宣告存取範圍是internal,M的存取範圍領域是T的存取範圍領域與P的程式文字的交集。 - 如果
M的宣告存取範圍是private,M的存取範圍領域就是T的程式文字。
注意:從這些定義可以得出,巢狀成員的可存取性範圍至少是宣告該成員之型別的程式碼。 此外,成員的存取範圍永遠不會超過宣告成員的類型的存取範圍。 註尾
注意:在直覺式方面,當存取類型或成員
M時,系統會評估下列步驟,以確保允許存取:
- 首先,如果在
M類型內宣告 ,而不是編譯單位或命名空間,則如果無法存取該類型,就會發生編譯時期錯誤。- 然後,如果
M是public,則允許存取。- 否則,如果
M為protected internal,則允許在宣告所在的程式M內進行存取,或是在從宣告所在類別衍生出的類別M內發生,並且會透過衍生類別類型進行存取(§7.5.4)。- 否則,如果
Mprotected為M,則當存取發生在宣告所在的類別內,或發生於宣告所屬類別的衍生類別中,並且存取是透過衍生類別類型(M)進行時,允許存取(§7.5.4)。- 否則,如果
M是internal,則只有在M被宣告的程式內進行的存取才被允許。- 否則,如果
M為private,則如果存取發生在宣告所在的M型別內,則允許存取權。- 否則,無法存取類型或成員,而且會發生編譯時期錯誤。 註尾
範例:在下列程式代碼中
public class A { public static int X; internal static int Y; private static int Z; } internal class B { public static int X; internal static int Y; private static int Z; public class C { public static int X; internal static int Y; private static int Z; } private class D { public static int X; internal static int Y; private static int Z; } }類別和成員具有下列存取權限領域:
A和A.X的輔助功能網域皆不受限制。A.Y、B、B.X、B.Y、B.C、B.C.X和B.C.Y的可存取性領域是包含程式的程式碼。A.Z的輔助功能領域是A的程式文字。B.Z和B.D的可訪問性領域是B的程序文本,包括B.C和B.D的程序文本。B.C.Z的輔助功能領域是B.C的程式文字。B.D.X和B.D.Y的可訪問性領域是B的程序文本,包括B.C和B.D的程序文本。B.D.Z的輔助功能領域是B.D的程式文字。 如範例所示,成員的存取範圍定義域絕不大於包含類型的存取範圍。 例如,即使所有X成員均具公用的可存取性,但是只有A.X的可存取性網域沒有受到包含類型的限制。結束範例
如 §7.4 中所述,除了實例建構函式、終結器和靜態建構函式之外,基類的所有成員都是由衍生型別繼承。 這甚至包括基類的私人成員。 不過,私人成員的存取領域只包含宣告該成員之型別的程式碼。
範例:在下列程式代碼中
class A { int x; static void F(B b) { b.x = 1; // Ok } } class B : A { static void F(B b) { b.x = 1; // Error, x not accessible } }類別
B會從x類別繼承私用成員A。 因為成員是私有的,所以只能在的A中訪問。 因此,對b.x的存取在A.F方法中成功,但在B.F方法中失敗。結束範例
7.5.4 受保護的存取
當在宣告類別的程式文本之外存取protected或private protected實例成員,或在宣告實例成員的程式文本之外存取protected internal時,存取必須在衍生自宣告類別的類別宣告內進行。 此外,必須透過該衍生類別類型的實例或從中建構的類別類型進行存取。 這項限制可防止一個衍生類別存取其他衍生類別的受保護成員,即使成員繼承自相同的基類也一樣。 無法從實作該介面的 或 protected 存取定義private protectedclass的實例介面成員;struct這些只能從衍生介面存取。 不過, class 和 struct 類型可以定義在其實作的介面中宣告的覆寫 protected 實例成員。
讓我們 B 成為宣告受保護實例成員 M的基類,並讓 D 成為衍生自 B的類別。 在 的 class_bodyD中,存取 M 可以採用下列其中一種形式:
- 表單的不合格type_name或primary_expression
M。 - 一種形式為的
E.M,前提是E的型別為T或者是從T衍生的類別,其中T是類別D,或者是由D建構的類別型別。 - 一個形式為primary_expression的
base.M。 -
primary_expression的形式是
base[。
除了這些形式的存取之外,衍生類別還可以存取constructor_initializer中基類的受保護實例建構函式(~15.11.2)。
範例:在下列程式代碼中
public class A { protected int x; static void F(A a, B b) { a.x = 1; // Ok b.x = 1; // Ok } } public class B : A { static void F(A a, B b) { a.x = 1; // Error, must access through instance of B b.x = 1; // Ok } }在
A中,可以透過x和A的實例來存取B,因為在任何情況下,存取都是透過A的實例或從A派生的類。 不過,在內B,無法透過 實例x存取A,因為A不會衍生自B。結束範例
範例:
class C<T> { protected T x; } class D<T> : C<T> { static void F() { D<T> dt = new D<T>(); D<int> di = new D<int>(); D<string> ds = new D<string>(); dt.x = default(T); di.x = 123; ds.x = "test"; } }因為所有的指派都由從泛型型別建構的類別型別實例進行,所以在這裡允許對
x的三次指派。結束範例
注意:泛型類別中宣告之受保護成員的可存取性定義域(§7.5.3)包含所有從該泛型類別構造的任何型別衍生出的類別宣告的程式文本。 在下列範例中:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }成員
protectedC<int>.x在D中的參考是有效的,即使類別D衍生自C<string>。 註尾
7.5.5 無障礙限制條件
C# 語言中的數個建構要求類型至少可以和成員或另一個類型一樣存取。 如果類型T的存取性範圍是類型或成員M的存取性範圍的超集,則類型T至少與類型或成員M同樣可存取。 換句話說,如果T在所有M可存取的情景中是可存取的,那麼T至少和M一樣容易存取。
現有下列無障礙限制:
- 類別的直接基類存取層級應至少與該類別本身相同或更高。
- 介面類型的明確基底介面至少可以和介面類型本身一樣可存取。
- 委派型別的傳回型別和參數型別至少可以和委派型別本身一樣可存取。
- 常數的類型至少可以和常數本身一樣可存取。
- 欄位的類型至少可以和欄位本身一樣可存取。
- 方法的傳回型別和參數型別至少可以和方法本身一樣可存取。
- 屬性的類型至少應與屬性本身一樣可存取。
- 事件的類型至少應與事件本身一樣可存取。
- 索引器的類型和參數類型至少可以和索引器本身一樣可存取。
- 運算符的傳回型別和參數型別至少可以和運算符本身一樣可存取。
- 實例建構函式的參數類型應該至少與實例建構函式本身一樣可存取。
- 類型參數上的介面或類別類型條件約束至少可以和宣告條件約束的成員一樣可存取。
範例:在下列程式代碼中
class A {...} public class B: A {...}類別
B會導致編譯時期錯誤,因為A的存取權限不如B。結束範例
範例:同樣地,在下列程序代碼中
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
H中的B方法會產生編譯時期錯誤,因為傳回類型A不至少與 方法一樣可存取。結束範例
7.6 簽章和重載
方法、實例建構函式、索引子和運算子的特徵是它們的 簽章:
- 方法的簽章包含方法的名稱、類型參數的數目,以及每個參數的類型和參數傳遞模式,以從左至右的順序來考慮。 針對這些目的,方法中涉及參數型別的任何型別參數,不是依其名稱來識別,而是依其在方法型別參數清單中的序數位置來識別。 方法的簽章特別不包括傳回類型、參數名稱、類型參數名稱、類型參數的條件約束、
params或this參數修飾詞,以及參數是否為必要或選擇性。 - 實例建構函式的簽章是由每個參數的類型和參數傳遞模式所組成,依左至右的順序來考慮。 實例建構函式的簽章特別不包含可能為最右邊參數指定的修飾詞,也不會表明參數是必要的還是選擇性的。
- 索引器簽章是由每個參數的類型所組成,以從左至右的順序來考慮。 索引器簽章特別不包含項目類型,也不包含可能為最右邊參數指定的修飾詞,也不包含
params必要參數或選擇性參數。 - 運算子的簽章包含運算子的名稱及其每個參數的類型,以從左至右的順序來考慮。 運算子的簽章特別不包含結果類型。
- 轉換運算子的簽章包含來源類型和目標類型。 轉換運算子的隱含或明確分類不是簽章的一部分。
- 如果同一個成員類型(方法、實例建構函式、索引器或運算元)的兩個簽章具有相同的名稱、類型參數數目、參數數目和參數傳遞模式,而且對應參數的類型之間存在身分轉換,這些簽章即被視為相同的簽章(§10.2.2)。
簽章是在類別、結構和介面中多載成員的啟用機制:
- 方法的多載可讓類別、結構或介面宣告具有相同名稱的多個方法,前提是其簽章在該類別、結構或介面內是唯一的。
- 實例建構函式的多載可讓類別或結構宣告多個實例建構函式,前提是其簽章在該類別或結構內是唯一的。
- 索引器多載可讓類別、結構或介面宣告多個索引器,前提是其簽章在該類別、結構或介面內是唯一的。
- 多載運算符可讓類別或結構宣告具有相同名稱的多個運算符,前提是其簽章在該類別或結構內是唯一的。
雖然 in、out 和 ref 參數修飾詞被視為簽章的一部分,但單一類型中宣告的成員不能僅因 in、out 和 ref 而在簽章上有所差異。 如果在相同類型中宣告了兩個成員,且簽章相同,則編譯時間錯誤會是相同的,如果具有 out 或 in 修飾詞的方法中的所有參數都變更為 ref 修飾詞, 就會發生編譯時期錯誤。 對於簽章比對的其他用途(例如隱藏或覆寫), in、 out和 ref 會被視為簽章的一部分,而且彼此不符。
注意:這項限制是允許 C# 程式輕鬆地轉譯為在未提供方法的平台上執行,而無法定義在 、
in和out中ref唯一不同的方法。 註尾
比較簽章時,不會區分類型 object 和 dynamic。 因此,不允許在單一型別中宣告的成員,其簽章僅因將 object 替換為 dynamic 而有所不同。
範例:下列範例顯示一組多載方法宣告及其簽章。
interface ITest { void F(); // F() void F(int x); // F(int) void F(ref int x); // F(ref int) void F(out int x); // F(out int) error void F(object o); // F(object) void F(dynamic d); // error. void F(int x, int y); // F(int, int) int F(string s); // F(string) int F(int x); // F(int) error void F(string[] a); // F(string[]) void F(params string[] a); // F(string[]) error void F<S>(S s); // F<0>(0) void F<T>(T t); // F<0>(0) error void F<S,T>(S s); // F<0,1>(0) void F<T,S>(S s); // F<0,1>(1) ok }請注意,任何
in、out和ref參數修飾詞 ({15.6.2) 都是簽章的一部分。 因此,F(int)、F(in int)、F(out int)和F(ref int)都是唯一的簽章。 不過,F(in int)、F(out int)和F(ref int)無法在同一個介面內宣告,因為它們的簽章僅在in、out和ref上有所不同。 此外,請注意,傳回類型和params修飾詞不是簽章的一部分,因此無法只根據傳回型別或包含或排除params修飾詞來多載。 因此,上述識別的方法F(int)和F(params string[])的宣告,會導致編譯時期錯誤。 結束範例
7.7 範圍
7.7.1 一般
名稱 的範圍 是程式文字的區域,其中可以參考名稱所宣告的實體,而沒有名稱的限定性。 範圍可以 巢狀,內部範圍可能會重新定義外部範圍中名稱的意義。 不過,這不會移除由 §7.3 所強加的限制,即在巢狀區塊內,不可能宣告與封入區塊中局部變數或局部常數同名的局部變數或局部常數。接著,外部範圍的名稱會在內部範圍所涵蓋的程式文字區域中被隱藏,而且只有透過限定名稱才能存取外部名稱。
namespace_member_declaration (14.6) 宣告的命名空間成員範圍,若沒有外層的 namespace_declaration,則涵蓋整個程序的所有文本。
命名空間成員的範圍,由namespace_member_declaration在namespace_declaration中宣告,其完整名稱為
N,是每一個完整名稱為或以開頭,後面接著句號的N。extern_alias_directive 所定義的名稱範圍會延伸至 using_directive、global_attributes 和 namespace_member_declaration 這些其下即時包含的 compilation_unit 或 namespace_body。 extern_alias_directive不會為基礎宣告空間貢獻任何新成員。 換句話說,extern_alias_directive 不具傳遞性,而是只會影響其所在的 compilation_unit 或 namespace_body。
由using_directive定義或匯入的名稱範圍延伸至該compilation_unit或namespace_body中發生的global_attributes和namespace_member_declaration。 using_directive可能會在特定compilation_unit或namespace_body內提供零或多個命名空間或類型名稱,但不會為基礎宣告空間貢獻任何新成員。 換句話說,using_directive 並不是具有傳遞性的,而是只影響其所出現的 compilation_unit 或 namespace_body。
type_parameter_list 所宣告的類型參數在 class_declaration (§15.2)上的範圍是 class_base、type_parameter_constraints_clause和該 class_declaration的 class_body。
注意:不同於類別的成員,此範圍不會延伸至衍生類別。 註尾
在struct_declaration中,由type_parameter_list宣告的類型參數的範圍包括該struct_declaration的struct_interfaces、type_parameter_constraints_clause以及struct_body。
interface_declaration (§19.2) 上type_parameter_list所宣告的類型參數範圍是該interface_declaration的interface_base、type_parameter_constraints_clauses 和 interface_body。
delegate_declaration上 type_parameter_list 所宣告的類型參數範圍 (§21.2) 是該delegate_declaration的 return_type、parameter_list和 type_parameter_constraints_clauses。
type_parameter_list 在 method_declaration(§15.6.1)上所宣告的類型參數其範圍是 method_declaration。
class_member_declaration(§15.3.1)宣告的成員範圍是發生宣告所在的class_body。 此外,類別成員的範圍會延伸到那些包含在成員可存取範圍(§7.5.3)中的衍生類別的class_body。
struct_member_declaration(§16.3)所宣告的成員範圍是該宣告發生所在的struct_body。
enum_member_declaration (§20.4) 所宣告的成員範圍是宣告發生的enum_body。
在method_declaration(§15.6)中宣告的參數範圍是該method_declaration的method_body或ref_method_body。
在indexer_declaration(§15.9)中宣告的參數範圍是該indexer_declaration的indexer_body。
在operator_declaration(§15.10)中宣告的參數範圍是該operator_declaration的operator_body。
在constructor_declaration(§15.11)中宣告的參數,其範圍是constructor_initializer和區塊。
lambda_expression (§12.21) 中宣告的參數範圍是該lambda_expression的lambda_expression_body。
anonymous_method_expression (§12.21) 中宣告的參數範圍是該anonymous_method_expression的區塊。
在labeled_statement(§13.5)中宣告的標籤範圍是宣告發生所在的區塊。
在local_variable_declaration(§13.6.2)中宣告的局部變數範圍是宣告發生所在的區塊。
在 switch_block 語句 (
switch) 中宣告的局部變數,其範圍是該 switch_block。在for_initializer中宣告的區域變數的範圍包括語句的
for、for_condition、for_iterator以及embedded_statement。在local_constant_declaration(§13.6.3)中宣告的本機常數的範圍是該宣告所在的區塊。 在編譯時期錯誤中,若在文字位置中參考一個本機常數,而該位置位於其常量宣告符之前,則會發生錯誤。
宣告為foreach_statement、using_statement、lock_statement或query_expression之變數的範圍取決於指定建構的擴充。
在命名空間、類別、結構或列舉的範圍內,可以在文字出現位置早於成員宣告之前,參考該成員。
範例:
class A { void F() { i = 1; } int i = 0; }這裡,
F在宣告之前參考i是有效的。結束範例
在局部變數的範圍內,若在宣告之前的位置參考該局部變數,將會導致編譯時期錯誤。
範例:
class A { int i = 0; void F() { i = 1; // Error, use precedes declaration int i; i = 2; } void G() { int j = (j = 1); // Valid } void H() { int a = 1, b = ++a; // Valid } }在上述方法中
F,第一次將i賦值在特別設定下不會參考外部範疇中宣告的欄位。 相反地,它會參考局部變數,而且會導致編譯時期錯誤,因為它在變數宣告之前以文字表示。 在G方法中,將j用於宣告j的初始化表達式是有效的,因為此使用不會早於宣告子。 在方法中H,後續宣告子會正確地參考相同 local_variable_declaration中先前宣告子中宣告的局部變數。結束範例
注意:局部變數和局部常數的範圍規則是設計來保證表達式內容中使用的名稱意義一律在區塊內相同。 如果局部變數的範圍只從其宣告延伸至區塊的結尾,則上述範例中,第一個指派會指派給實例變數,而第二個指派會指派給局部變數,如果區塊的語句稍後重新排列,可能會導致編譯時間錯誤。
區塊內名稱的意義可能會因使用名稱的內容而有所不同。 在範例中
class A {} class Test { static void Main() { string A = "hello, world"; string s = A; // expression context Type t = typeof(A); // type context Console.WriteLine(s); // writes "hello, world" Console.WriteLine(t); // writes "A" } }名稱
A用於表示式內容中,以參考局部變數A,並在型別內容中參考 類別A。註尾
7.7.2 名稱隱藏
7.7.2.1 一般
實體的範圍通常包含比實體宣告空間更多的程序文字。 特別是,實體的範圍可能包含引進包含相同名稱實體之新宣告空間的宣告。 這類宣告會導致原始實體變成 隱藏。 相反地,當實體未隱藏時,稱其為可見。
當範圍透過巢狀結構重疊,以及範圍透過繼承重疊時,就會發生命名遮蔽。 下列子集合會說明這兩種隱藏類型的特性。
透過巢狀結構進行隱藏
隱藏巢狀的名稱可能會因為巢狀命名空間或命名空間內的類型而發生,因為類別或結構內的巢狀類型、本機函式或 Lambda 的結果,以及參數、局部變數和局部常數宣告的結果。
範例:在下列程式代碼中
class A { int i = 0; void F() { int i = 1; void M1() { float i = 1.0f; Func<double, double> doubler = (double i) => i * 2.0; } } void G() { i = 1; } }在
F方法中,實例變數i會由局部變數i隱藏,但在方法中G,i仍會參考實例變數。 在本地函式M1內,float i遮蓋了最接近的外部i。 Lambda 參數i會將float i隱藏在 lambda 主體內部。結束範例
當內部範圍中的名稱隱藏外部範圍中的名稱時,它會隱藏該名稱的所有重載情況。
範例:在下列程式代碼中
class Outer { static void F(int i) {} static void F(string s) {} class Inner { static void F(long l) {} void G() { F(1); // Invokes Outer.Inner.F F("Hello"); // Error } } }呼叫
F(1)會叫用 中F宣告的Inner,因為 內部宣告會隱藏 所有外部出現的F。 基於相同的原因,呼叫F("Hello")會導致編譯時間錯誤。結束範例
7.7.2.3 透過繼承隱藏
當類別或結構重新宣告名稱時,透過繼承進行名稱隱藏的情況就會發生。 這種類型的名稱隱藏採用下列其中一種形式:
- 類別、結構或介面中引進的常數、欄位、屬性、事件或類型會隱藏所有具有相同名稱的基底類別成員。
- 類別、結構或介面中引進的方法會隱藏所有具有相同名稱的非方法基底類別成員,以及具有相同簽章的所有基底類別方法 (§7.6) 。
- 類別、結構或介面中引進的索引子會隱藏具有相同簽章的所有基底類型索引子 (§7.6) 。
控管運算符宣告的規則 ({15.10) 使得衍生類別不可能宣告與基類中運算符具有相同簽章的運算符。 因此,運算符永遠不會彼此隱藏。
與隱藏外部範疇的名稱相反,隱藏繼承範疇中的可見名稱將導致報告警告。
範例:在下列程式代碼中
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }在
F中的Derived宣告會導致產生警告。 隱藏繼承的名稱特別不是錯誤,因為這會排除基類的個別演進。 例如,上述情況可能是由於新版的Base引入了一個在舊版類別中不存在的F方法。結束範例
隱藏繼承名稱所造成的警告可以透過使用 new 修飾詞來消除:
範例:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
new修飾詞表示F中的Derived是「new」,且確實用來隱藏繼承的成員。結束範例
新成員的宣告只會隱藏新成員範圍內的繼承成員。
範例:
class Base { public static void F() {} } class Derived : Base { private new static void F() {} // Hides Base.F in Derived only } class MoreDerived : Derived { static void G() { F(); // Invokes Base.F } }在上述範例中,
F在Derived中的宣告會隱藏繼承自F的Base,但由於F中的 新Derived具有私用存取權,因此其範圍不會延伸至MoreDerived。 因此,在F()中,MoreDerived.G的呼叫是有效的,並且會叫用Base.F。結束範例
7.8 命名空間和類型名稱
7.8.1 一般
C# 程式中的數個內容需要指定 namespace_name 或 type_name 。
namespace_name
: namespace_or_type_name
;
type_name
: namespace_or_type_name
;
namespace_or_type_name
: identifier type_argument_list? ('.' identifier type_argument_list?)*
| qualified_alias_member ('.' identifier type_argument_list?)*
;
namespace_name 是一個 namespace_or_type_name,該名稱指的是命名空間。
如下所述解決方案,namespace_or_type_name 所屬的 namespace_name 應該參考一個命名空間,否則會發生編譯時期錯誤。 namespace_name中不能有類型自變數 (~8.4.2) (只有類型可以有類型自變數)。
type_name 是指一個類型的 namespace_or_type_name。
依照以下所述的解決方式,type_name 的 namespace_or_type_name 必須引用一個類型,否則會發生編譯時期錯誤。
namespace_or_type_name是指類型或命名空間。 解析特定命名空間或類型涉及兩個步驟,首先將文法劃分為前導部分,這是文法片段之一:
identifier type_argument_list?qualified_alias_member
和結尾部分,是文法片段:
('.' identifier type_argument_list?)*
首先,透過解析前置部分來判定R₀的開始命名空間或類型。
如果 namespace_or_type_name 的前置部分是 qualified_alias_member,則 R₀ 解析所識別的命名空間或類型,如 \14.8.1 中所述。
否則,前置部分作為文法片段識別符號type_argument_list?會有下列其中一種形式:
II<A₁, ..., Aₓ>
地點:
-
I是單一標識符;和 -
<A₁, ..., Aₓ>是一個 type_argument_list,當未指定 type_argument_list 時,將x視為零。
R₀ 判斷方式如下:
- 如果
x是零,且 namespace_or_type_name 出現在泛型方法宣告 (§15.6)中,但在其 方法標頭 的 屬性之外,且該宣告包含名稱為 的類型參數(I),則R₀參考該類型參數。 - 否則,如果 namespace_or_type_name 出現在類型宣告內,則針對每個實例類型
T(§15.3.2),從該類型宣告的實例類型開始,並繼續使用每個封閉類別、結構或介面宣告的實例類型 (如果有的話):- 如果
x為零,且的T宣告包含名稱I為的類型參數,則R₀參考該類型參數。 - 否則,如果namespace_or_type_name出現在類型宣告的主體內,或其
T任何基底類型都包含具有名稱和Ix類型參數的巢狀可存取型別,則R₀參考以指定型別自變數建構的型別。 如果有多個這類類型,則會選取在更多衍生類型內宣告的類型。 如果沒有類型比所有其他類型派生更多,則這是編譯器錯誤。注意:判斷namespace_or_type_name意義時,會忽略非類型成員(常數、字段、方法、屬性、索引器、運算元、實例建構函式、完成項和靜態建構函式)和類型成員,以及具有不同類型參數數目的類型成員。 註尾
- 否則,針對每個命名空間
N,從發生 namespace_or_type_name 的命名空間開始,繼續進行每個封閉命名空間(如果有的話),並以全域命名空間結尾,則會評估下列步驟,直到找到實體為止:- 如果
x為零,且I是 中的N命名空間名稱,則:- 如果namespace_or_type_name出現的位置由
N包圍,且該命名空間宣告包含一個extern_alias_directive或using_alias_directive,將名稱I與某個命名空間或類型建立關聯,那麼namespace_or_type_name就是模糊不清的,並且會發生編譯時期錯誤。 - 否則,
R₀會參考I中名為N的命名空間。
- 如果namespace_or_type_name出現的位置由
- 否則,如果
N包含具有名稱和Ix類型參數的可存取類型,則:- 如果
x為零,且 namespace_or_type_name 出現的位置被命名空間N的宣告括住,且該命名空間宣告包含將名稱與命名空間或類型相關聯的extern_alias_directive 或I,則 namespace_or_type_name 會很模糊,並且會發生編譯時期錯誤。 - 否則,
R₀指的是使用指定型別參數建構的類型。
- 如果
- 否則,如果namespace_or_type_name出現的位置被
N的命名空間宣告包圍:- 如果
x為零,且命名空間宣告包含 extern_alias_directive 或 using_alias_directive ,使名稱I與匯入的命名空間或類型產生關聯,則R₀參考該命名空間或類型。 - 否則,如果命名空間宣告中的using_namespace_directive所匯入的命名空間包含一個且僅一個具有名稱及
Ix型別參數的類型,則R₀參考該類型,並以指定的型別參數建構。 - 否則,如果命名空間宣告的 using_namespace_directive所匯入的命名空間包含具有名稱和
Ix類型參數的多個類型,則namespace_or_type_name模棱兩可,而且會發生編譯時期錯誤。
- 如果
- 如果
- 否則, namespace_or_type_name 未定義,而且會發生編譯時期錯誤。
- 如果
如果 R₀ 已成功解析,則會解析 namespace_or_type_name 的後續部分。 尾端文法片段是由 k ≥ 0 重複所組成,每個重複都會進一步解析參考的命名空間或類型。
如果 k 為零,亦即沒有尾端部分,則 namespace_or_type_name 會解析為 R₀。
否則,每個重複都會有其中一種形式:
.I.I<A₁, ..., Aₓ>
其中 I, A 和 x 定義為上述。
針對每次重複n,當1 ≤ n ≤ k的解析度為Rₙ時,涉及Rₚ;此解析度,依照先前重複的解析p = n - 1,決定如下:
- 如果
x為零且Rₚ參考命名空間,且Rₚ包含名稱I為的巢狀命名空間,則Rₙ參考該巢狀命名空間。 - 否則,如果
Rₚ參考命名空間並Rₚ包含具有名稱和Ix類型參數的可存取型別,則Rₙ參考以指定型別自變數建構的型別。 - 否則,如果參考 (可能建構的) 類別、結構或介面類型,和
Rₚ/或其任何基底類型包含具有名稱Rₚ和I類型參數的巢狀可存取類型,則xRₙ會參考使用指定類型引數建構的類型。 如果有多個這類類型,則會選取在更多衍生類型內宣告的類型。 如果沒有類型比所有其他類型派生更多,則這是編譯器錯誤。注意:如果在解析
T.I的基類規格時,T的意義為某些型T被確定是其中的一部分,那麼T的直接基類會被視為object(§15.2.4.2)。 註尾 - 否則, namespace_or_type_name 無效,而且會發生編譯時期錯誤。
namespace_or_type_name 的解析結果是最終重複的解析結果。 Rₖ
只有當 namespace_or_type_name 參考一個靜態類別(§15.2.2.4)時才被允許。
-
namespace_or_type_name 是在形式為
T的 namespace_or_type_name 中的T.I,或 -
namespace_or_type_name 表示在
T(§12.8.18)形式 中的typeof(T)
範例:
interface A { class NestedClass { public static void M() {} } } interface B { class NestedClass { public static void M() {} } } interface C : A, B { public void Test() { NestedClass.M(); } // ambiguity between // A.NestedClass and B.NestedClass }在上述範例中,對 in
NestedClass.M()的C.Test()呼叫介於 和B.NestedClass.M()之間A.NestedClass.M()是模棱兩可的,因為兩者都不比另一個衍生更多。 需要明確引用A.NestedClass.M()或B.NestedClass.M()才能解決歧義。結束範例
7.8.2 未限定的名稱
每個命名空間宣告和類型宣告都有無限定名稱,如下所示:
- 對於命名空間宣告,未限定的名稱是 宣告中指定的qualified_identifier 。
- 對於沒有 type_parameter_list的類型宣告,未限定的名稱是 宣告中指定的標識碼 。
- 對於具有 K 類型參數的類型宣告,未限定名稱是宣告中指定的 標識碼,後面接著表示 K 類型參數的 泛型維度規範器(§12.8.18)。
7.8.3 完整名稱
每個命名空間和類型宣告都有完整名稱,可唯一識別程式內所有其他命名空間或類型宣告。 命名空間或類型宣告的完整名稱具有不限定名稱 N 的判斷方式如下:
- 如果
N是全域命名空間的成員,則其完整名稱為N。 - 否則,其完整名稱為
S.N,其中S是宣告所在N命名空間或類型宣告的完整名稱。
換句話說,N的完整限定名稱是從全域命名空間開始,包含標識符和泛型維度指定符的完整階層路徑,形成到N的路徑。 因為命名空間或類型的每個成員都應該有唯一的名稱,因此,命名空間或類型宣告的完整名稱一律是唯一的。 這是在編譯期間中,讓相同完整名稱指涉兩個不同實體時所發生的錯誤。 特別是:
- 命名空間宣告和型宣告如果有相同的完整限定名稱,就會發生錯誤。
- 兩種不同類型的類型宣告具有相同完整名稱是錯誤的(例如,如果結構與類別宣告具有相同的完整名稱)。
- 沒有部分修飾詞的類型宣告與另一個類型宣告具有相同完整名稱({15.2.7)是錯誤的。
範例:下列範例顯示數個命名空間和類型宣告及其相關聯的完整名稱。
class A {} // A namespace X // X { class B // X.B { class C {} // X.B.C } namespace Y // X.Y { class D {} // X.Y.D } } namespace X.Y // X.Y { class E {} // X.Y.E class G<T> // X.Y.G<> { class H {} // X.Y.G<>.H } class G<S,T> // X.Y.G<,> { class H<U> {} // X.Y.G<,>.H<> } }結束範例
7.9 自動記憶體管理
C# 會採用自動記憶體管理,以釋放開發人員手動配置和釋放物件所佔用的記憶體。 垃圾收集器會實作自動記憶體管理原則。 物件的記憶體管理生命週期如下所示:
- 建立物件時,會為其配置記憶體、執行建構函式,並將對象視為 即時。
- 如果物件及其任何實例欄位都無法透過任何可能的執行接續來存取,但執行終結器除外,該物件就會被視為 不再使用,因而有資格進行最終處理。
注意:C# 編譯器和垃圾收集器可能會選擇分析程式碼,以判斷未來可能會使用的物件參考。 例如,如果在作用域內的局部變數是物件的唯一現有引用,但在該程序中當前執行點的任何可能後續執行中,都不會引用該局部變數,垃圾收集器可能會(但不需要)將該物件視為已不再使用。 註尾
- 一旦物件有資格進行最終處理,在某個未指定的時間,物件的完成項(§15.13)(如果有的話)將會被執行。 在正常情況下,物件的終結器只會執行一次,不過實作定義的 API 可能會允許覆蓋此行為。
- 當一個物件的析構函式執行後,如果該物件及其任何實例欄位都無法由任何可能的執行接續存取,包括其他析構函式的執行,這個物件就會被視為無法存取,並變得可以被回收。
注意:先前無法存取的物件可能會因其終結器而變得可存取。 以下提供此範例。 註尾
- 最後,在物件變成符合收集條件後的某個時間,垃圾回收器會釋放與該物件相關聯的記憶體。
垃圾收集器會維護物件使用情況的相關資訊,並使用這項資訊來進行記憶體管理決策,例如在記憶體中找出新建立的物件的位置、何時重新定位物件,以及物件不再使用或無法存取的時機。
如同假設垃圾收集行程存在的其他語言,C# 的設計目的是讓垃圾收集行程可以實作各種不同的記憶體管理原則。 C# 不會指定該範圍內的時間條件約束,也不會指定執行完成項的順序。 是否將終結器作為應用程式終止的一部分執行,取決於實作定義(§7.2)。
資源回收程式的行為可以透過類System.GC上的靜態方法來控制。 這個類別可用來要求集合發生、要執行完成項(或未執行),依此類推。
範例:由於垃圾收集器在決定何時回收物件和執行 finalizer 時擁有很大的自由度,因此一個符合標準的實作可能會產生與下列程式碼所示不同的輸出。 程式
class A { ~A() { Console.WriteLine("Finalize instance of A"); } } class B { object Ref; public B(object o) { Ref = o; } ~B() { Console.WriteLine("Finalize instance of B"); } } class Test { static void Main() { B b = new B(new A()); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }會建立 類別
A的實例和 類別的B實例。 當變數b被指派值null時,這些物件會成為垃圾收集的資格,因為在此之後,任何使用者撰寫的程式代碼都無法存取它們。 輸出可以是其中一項Finalize instance of A Finalize instance of B或
Finalize instance of B Finalize instance of A因為語言不會對垃圾收集物件的順序施加任何條件約束。
在微妙的情況下,「符合最終處理資格」和「符合集合資格」之間的差異可能很重要。 例如,
class A { ~A() { Console.WriteLine("Finalize instance of A"); } public void F() { Console.WriteLine("A.F"); Test.RefA = this; } } class B { public A Ref; ~B() { Console.WriteLine("Finalize instance of B"); Ref.F(); } } class Test { public static A RefA; public static B RefB; static void Main() { RefB = new B(); RefA = new A(); RefB.Ref = RefA; RefB = null; RefA = null; // A and B now eligible for finalization GC.Collect(); GC.WaitForPendingFinalizers(); // B now eligible for collection, but A is not if (RefA != null) { Console.WriteLine("RefA is not null"); } } }在上述程式中,如果垃圾收集器選擇在
A的完成項之前執行B的完成項,那麼此程式的輸出可能是:Finalize instance of A Finalize instance of B A.F RefA is not null請注意,雖然
A的實例未被使用,且A的完成項已執行,但仍有可能從其他完成項呼叫A的方法(在此例中,為F)。 此外,請注意,執行終結器可能會導致物件再次可被主程序使用。 在這種情況下,執行B的終結器會使先前未使用的A實例從存在的參考Test.RefA中變得可存取。 在呼叫WaitForPendingFinalizers之後,B的實例符合被回收的條件,但A的實例不符合,因為存在Test.RefA的參考。結束範例
7.10 執行順序
執行 C# 程式的過程中,使得每個執行緒的副作用能被保留在關鍵的執行點。
副作用定義為揮發性欄位的讀取或寫入、對非揮發性變數的寫入、外部資源的寫入,以及擲回例外狀況。 要保留這些副作用順序的關鍵執行點包括對變動性字段的參考(§15.5.4)、lock語句(§13.13),以及執行緒的創建和終止。 執行環境可以自由地變更 C# 程式的執行順序,但受限於下列條件約束: