7 基本概念
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>
不符合進入點的資格。 end note
如果在程式內宣告了限定為進入點的多個方法,可以使用外部機制來指定哪個方法被視為應用程式的實際進入點。 如果找到傳回型別為 或 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 的字串參考,稱為 應用程式參數,這些字串是在應用程式啟動之前由主機環境指定實作定義的值。 其目的是要提供在裝載環境中其他地方啟動應用程式之前所決定的應用程式資訊。
注意:在支援命令行的系統上,應用程式參數會對應至通常稱為命令行自變數的自變數。 end note
如果有效進入點的傳回類型為 int
,則執行環境從方法調用的傳回值會用於應用程式終止 (~7.2)。
除了以上所列的情況以外,進入點方法的行為就像不是每個方面進入點的方法一樣。 特別是,如果進入點在應用程式存留期期間叫用至任何其他點,例如透過一般方法調用,則沒有任何特殊處理方法:如果有參數,它可能有初始值,或參考包含 null 參考之數位的非null
值null
。 同樣地,進入點的傳回值在從執行環境調用中沒有特殊意義。
7.2 應用程式終止
應用程式終止 會將控制權傳回至執行環境。
如果應用程式有效進入點方法的傳回型別為 int
且執行完成而不會產生例外狀況,則傳回的值int
會做為應用程式的終止狀態代碼。 此程式代碼的目的是允許成功或失敗的通訊到執行環境。 如果有效進入點方法的傳回型別是 void
且執行完成而不會產生例外狀況,則終止狀態代碼為 0
。
如果有效的進入點方法因例外狀況而終止(~21.4),結束代碼會定義實作。 此外,實作可能會提供替代 API 來指定結束代碼。
在應用程式終止時是否執行完成項(~15.13)是實作定義的。
注意:除非已隱藏這類清除,否則 .NET Framework 實作會針對尚未進行垃圾收集的所有物件,盡一切合理努力呼叫完成項(例如,透過對連結庫方法
GC.SuppressFinalize
的呼叫)。 end note
7.3 宣告
C# 程式中的宣告會定義程式的組成元素。 C# 程式是使用命名空間來組織。 這些是使用命名空間宣告 ({14) 來引進的,其中包含類型宣告和巢狀命名空間宣告。 類型宣告 ({14.7) 用來定義類別 ({15)、結構 ({16)、介面 ({18)、列舉 ({19) 和委派 ({20)。 類型宣告中允許的成員類型取決於類型宣告的形式。 例如, 類別宣告可以包含常數的宣告({15.4)、字段({15.5)、方法({15.6)、屬性({15.7)、事件({15.8)、索引器({15.8)。 9、運算符(~15.10)、實例建構函式(~15.11)、靜態建構函式(~15.12)、完成項(~15.13)和巢狀類型(~15.3.9)。
宣告會在宣告所屬的 宣告空間 中定義名稱。 在宣告空間中引進同名成員的兩個或多個宣告是編譯時期錯誤,但下列情況除外:
- 在相同的宣告空間中,允許有兩個或多個具有相同名稱的命名空間宣告。 這類命名空間宣告會匯總成形成單一邏輯命名空間,並共用單一宣告空間。
- 不同程式中的宣告,但允許在相同的命名空間宣告空間中共用相同的名稱。
注意:不過,如果包含在相同的應用程式中,這些宣告可能會造成模棱兩可。 end note
- 相同宣告空間中允許兩個或多個具有相同名稱但相異簽章的方法({7.6)。
- 相同宣告空間中允許兩個或多個具有相同名稱但類型參數相異數目的類型宣告({7.8.2)。
- 在相同宣告空間中具有部分修飾詞的兩個或多個類型宣告,可以共用相同名稱、相同類型參數數目和相同的分類(類別、結構或介面)。 在此情況下,類型宣告會參與單一類型,而且本身會匯總成形成單一宣告空間({15.2.7)。
- 命名空間宣告和相同宣告空間中的類型宣告可以共用相同的名稱,只要類型宣告至少有一個類型參數({7.8.2)。
宣告空間有數種不同類型的宣告空間,如下所述。
- 在程式的所有編譯單位中,沒有封入namespace_declaration的 namespace_member_declaration是稱為全域宣告空間的單一合併宣告空間成員。
- 在程式的所有編譯單位中,具有相同完整命名空間名稱之 namespace_declaration內的 namespace_member_declaration是單一合併宣告空間的成員。
- 每個 compilation_unit 和 namespace_body 都有 別名宣告空間。 每個 extern_alias_directive 和 using_alias_directivecompilation_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、s、 declaration_statement和 exception_specifiers,將這些宣告空間導入。 局部變數宣告空間可能是巢狀的,但局部變數宣告空間和巢狀局部變數宣告空間是包含同名元素的錯誤。 因此,在巢狀宣告空間中,不可能在封入宣告空間中宣告局部變數、局部函數或常數,其名稱與參數、類型參數、局部變數、局部函數或常數相同。 只要兩個宣告空間都不包含另一個宣告空間,即可以包含具有相同名稱的元素。 本機宣告空間是由下列建構所建立:
- 欄位和屬性宣告中的每個variable_initializer都會引進它自己的局部變數宣告空間,該空間不是巢狀於任何其他局部變數宣告空間內。
- 函式成員、匿名函式或局部函數的主體,如果有的話,會建立局部變數宣告空間,該空間會被視為巢狀於函式的局部變數宣告空間內。
- 每個 constructor_initializer 都會建立實例建構函式宣告內巢狀的局部變數宣告空間。 建構函式主體的局部變數宣告空間會接著巢狀在此局部變數宣告空間內。
- 每個 區塊、 switch_block、 specific_catch_clause、 iteration_statement 和 using_statement 都會建立巢狀局部變數宣告空間。
- 不屬於statement_list的每個embedded_statement都會建立巢狀局部變數宣告空間。
- 每個 switch_section 都會建立巢狀局部變數宣告空間。 不過,直接在switch_section statement_list內宣告的變數(但未在statement_list內的巢狀局部變數宣告空間內)直接新增至封入switch_block的局部變數宣告空間,而不是switch_section。
- query_expression的語法翻譯(~12.20.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; }
end note
宣告名稱的文字順序通常不重要。 特別是,宣告和使用命名空間、常數、方法、屬性、事件、索引器、運算元、實例建構函式、完成項、靜態建構函式和類型時,文字順序並不重要。 宣告順序在下列方面相當重要:
- 欄位宣告的宣告順序會決定執行其初始化表達式的順序(如果有的話)({15.5.6.2, {15.5.6.3)。
- 使用局部變數之前,應先定義局部變數(~7.7)。
- 省略constant_expression值時,列舉成員宣告的宣告順序({19.4) 相當重要。
範例:命名空間的宣告空間是「開放式結束」,而具有相同完整名稱的兩個命名空間宣告會參與相同的宣告空間。 例如:
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }
上述的兩個命名空間宣告會貢獻相同的宣告空間,在此案例中,宣告兩個具有完整名稱和
Megacorp.Data.Customer
Megacorp.Data.Order
的類別。 因為這兩個宣告會參與相同的宣告空間,所以如果每個宣告都包含同名類別的宣告,就會造成編譯時期錯誤。end 範例
注意:如上所述,區塊的宣告空間會包含任何巢狀區塊。 因此,在下列範例中,
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(); } } }
end note
7.4 成員
7.4.1 一般
命名空間和類型具有 成員。
注意:實體的成員通常是透過使用以實體參考開頭的限定名稱,後面接著 “
.
” 令牌,後面接著成員的名稱來取得。 end note
型別的成員是在型別宣告中宣告,或 繼承 自型別的基類。 當類型繼承自基類時,除了實例建構函式、完成項和靜態建構函式之外,基類的所有成員都會成為衍生型別的成員。 基類成員的宣告存取範圍不會控制是否繼承成員—繼承延伸至不是實例建構函式、靜態建構函式或完成項的任何成員。
注意:不過,繼承的成員可能無法在衍生型別中存取,例如,因為其宣告的輔助功能 (~7.5.2)。 end note
7.4.2 命名空間成員
沒有封入命名空間的命名空間和類型是全域命名空間的成員。 這會直接對應至全域宣告空間中宣告的名稱。
命名空間內宣告的命名空間和類型是該命名空間的成員。 這會直接對應至命名空間宣告空間中宣告的名稱。
命名空間沒有存取限制。 無法宣告私用、受保護或內部命名空間,且命名空間名稱一律可公開存取。
7.4.3 結構成員
結構的成員是結構中宣告的成員,以及繼承自結構直接基類 System.ValueType
和間接基類 object
的成員。
簡單型別的成員會直接對應至簡單型別所別名結構類型的成員(~8.3.5)。
7.4.4 列舉成員
列舉的成員是列舉中宣告的常數,以及繼承自列舉之直接基類和間接基類System.Enum
System.ValueType
和 的成員object
。
7.4.5 類別成員
類別的成員是類別中宣告的成員,以及繼承自基類的成員(除了沒有基類的類別 object
除外)。 繼承自基類的成員包括常數、字段、方法、屬性、事件、索引器、運算符和基類的類型,但不是基類的實例建構函式、完成項和靜態建構函式。 基類成員會繼承,而不考慮其存取範圍。
類別宣告可能包含常數、字段、方法、屬性、事件、索引器、運算元、實例建構函式、完成項、靜態建構函式和類型的宣告。
(~8.2.3) 和 object
(~8.2.5) 的成員string
會直接對應至他們別名的類別類型成員。
7.4.6 介面成員
介面的成員是在介面和介面的所有基底介面中宣告的成員。
注意:類別
object
中的成員不是任何介面的成員(~18.4)。 不過,類別中的object
成員可透過任何介面類型中的成員查閱取得(~12.5)。 end note
7.4.7 陣列成員
陣列的成員是繼承自 類別 System.Array
的成員。
7.4.8 委派成員
委派會從類別 System.Delegate
繼承成員。 此外,它包含名為 Invoke
的方法,其宣告中指定的傳回類型和參數清單相同({20.2)。 這個方法的叫用行為應該與相同委派實例上的委派調用 (~20.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
宣告的輔助功能。 end note - 結構成員可以有
public
、internal
或private
宣告的輔助功能,而且預設為private
宣告的輔助功能,因為結構是隱含密封的。 中struct
引進的結構成員(亦即,不是由該結構繼承),不能有protected
、protected internal
或private protected
宣告的存取範圍。注意:宣告為結構成員的類型可以具有
public
、internal
或private
宣告的輔助功能,而宣告為命名空間成員的類型只能public
有或internal
宣告的輔助功能。 end note - 介面成員已
public
隱含宣告輔助功能。 介面成員宣告上不允許任何存取修飾詞。 - 列舉成員會
public
隱含宣告輔助功能。 列舉成員宣告上不允許任何存取修飾詞。
7.5.3 輔助功能網域
成員 的存取範圍網域 是由允許存取成員的程式文字區段所組成。 為了定義成員的存取範圍定義域,如果成員未在型別內宣告,則表示成員是最上層,如果成員是在另一個類型內宣告,則表示成員會巢狀。 此外,程式的程式文字會定義為程式所有編譯單位中包含的所有文字,而型別的程式文字則定義為該類型之type_declaration中包含的所有文字(包括可能是類型內巢狀的類型)。
預先定義型別的輔助功能網域 (例如 object
、 int
或 double
) 是無限制的。
在程式中T
宣告的最上層未系結類型 (P
) 輔助功能定義域定義如下:
- 如果的宣告存取範圍
T
是公用的,則的輔助功能定義域T
是的程式文字,以及任何參考P
的程序文字P
。 - 如果的宣告存取範圍
T
是內部的,則的輔助功能定義域T
是的程式文字P
。
注意:從這些定義中,會遵循最上層未系結類型的輔助功能定義域,至少是宣告該類型之程序的程序文字。 end note
建構類型的 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
的程式文字。
注意:從這些定義中,它會遵循巢狀成員的輔助功能定義域,一律至少是宣告成員之型別的程序文字。 此外,接著成員的存取範圍網域絕不比宣告成員之類型的輔助功能網域更具包容性。 end note
注意:在直覺式方面,當存取類型或成員
M
時,系統會評估下列步驟,以確保允許存取:
- 首先,如果在
M
類型內宣告 ,而不是編譯單位或命名空間,則如果無法存取該類型,就會發生編譯時期錯誤。- 然後,如果
M
是public
,則允許存取。- 否則,如果
M
為protected internal
,則允許在宣告所在的程式M
內進行存取,或是在衍生自宣告之類別的類別M
內發生,而且會透過衍生類別類型進行存取(~7.5.4)。- 否則,如果 為 ,
M
則如果存取發生在宣告所在的類別內,或發生在衍生自宣告之類別的類別protected
M
內,則允許存取權通過衍生類別類型 (M
) 進行。- 否則,如果
M
是internal
,則允許在宣告的程式M
內進行存取。- 否則,如果
M
為private
,則如果存取發生在宣告所在的M
型別內,則允許存取權。- 否則,無法存取類型或成員,而且會發生編譯時期錯誤。 end note
範例:在下列程式代碼中
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
都有受包含類型限制的輔助功能網域。end 範例
如 •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
會失敗。end 範例
7.5.4 受保護的存取
protected
在宣告類別的程式文字之外存取 或 private protected
實例成員時,以及在protected internal
宣告實例成員的程式文字之外存取時,存取權應在衍生自宣告類別的類別宣告內進行。 此外,需要存取權,才能 透過 該衍生類別類型的實例或從中建構的類別類型進行。 這項限制可防止一個衍生類別存取其他衍生類別的受保護成員,即使成員繼承自相同的基類也一樣。
讓我們 B
成為宣告受保護實例成員 M
的基類,並讓 D
成為衍生自 B
的類別。 在 的 class_bodyD
中,存取 M
可以採用下列其中一種形式:
- 表單的不合格type_name或primary_expression
M
。 -
E.M
,前提是 的型E
別為T
或 衍生自T
的類別,其中T
是 類別D
,或建構自D
的類別型別。 -
base.M
。 -
表單
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
。 不過,在內B
,無法透過 實例x
存取A
,因為A
不會衍生自B
。end 範例
範例:
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
,因為它們全都是透過從泛型型別建構的類別型別實例進行。end 範例
注意:泛型類別中宣告之受保護成員的輔助功能定義域 ({7.5.3) 包含衍生自該泛型類別之任何類型衍生之所有類別宣告的程式文字。 在下列範例中:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
中的成員
protected
C<int>.x
參考D
是有效的,即使 類別D
衍生自C<string>
。 end note
7.5.5 輔助功能條件約束
C# 語言中的數個建構要求類型至少可以和成員或另一個類型一樣存取。 如果的存取範圍定義域是 輔助功能網域T
M
的超集,則類型T
至少可以做為成員或型M
別。 換句話說,T
至少可以存取,就像在可存取的所有內容M
中可以存取一樣T
M
。
下列輔助功能條件約束存在:
- 類別類型的直接基類至少可以存取類別類型本身。
- 介面類型的明確基底介面至少可以和介面類型本身一樣可存取。
- 委派型別的傳回型別和參數型別至少可以和委派型別本身一樣可存取。
- 常數的類型至少可以和常數本身一樣可存取。
- 欄位的類型至少可以和欄位本身一樣可存取。
- 方法的傳回型別和參數型別至少可以和方法本身一樣可存取。
- 屬性的類型至少應與屬性本身一樣可存取。
- 事件的類型至少應與事件本身一樣可存取。
- 索引器的類型和參數類型至少可以和索引器本身一樣可存取。
- 運算符的傳回型別和參數型別至少可以和運算符本身一樣可存取。
- 實例建構函式的參數類型應該至少與實例建構函式本身一樣可存取。
- 類型參數上的介面或類別類型條件約束至少可以和宣告條件約束的成員一樣可存取。
範例:在下列程式代碼中
class A {...} public class B: A {...}
類別
B
會導致編譯時期錯誤,因為A
至少不能和 一樣B
容易存取。end 範例
範例:同樣地,在下列程序代碼中
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
H
中的B
方法會產生編譯時期錯誤,因為傳回類型A
不至少與 方法一樣可存取。end 範例
7.6 簽章和多載
方法、實例建構函式、索引器和運算子的特點是其簽章:
- 方法的簽章包含方法的名稱、類型參數的數目,以及每個參數的類型和參數傳遞模式,以從左至右的順序來考慮。 針對這些目的,在參數類型中發生之方法的任何型別參數,都會依其名稱來識別,而是由方法型別參數清單中的序數位置來識別。 方法的簽章特別不包含傳回類型、參數名稱、類型參數名稱、類型參數條件約束、
params
或this
參數修飾詞,或參數修飾詞,或參數是否為必要或選擇性。 - 實例建構函式的簽章是由每個參數的類型和參數傳遞模式所組成,依左至右的順序來考慮。 實例建構函式的簽章特別不包含可能為最右邊參數指定的修飾詞,也不包含
params
必要參數或選擇性參數。 - 索引器簽章是由每個參數的類型所組成,以從左至右的順序來考慮。 索引器簽章特別不包含項目類型,也不包含可能為最右邊參數指定的修飾詞,也不包含
params
必要參數或選擇性參數。 - 運算子的簽章包含運算子的名稱及其每個參數的類型,以從左至右的順序來考慮。 運算子的簽章特別不包含結果類型。
- 轉換運算子的簽章包含來源類型和目標類型。 轉換運算子的隱含或明確分類不是簽章的一部分。
- 如果同一個成員類型(方法、實例建構函式、索引器或運算元)的兩個簽章具有相同的名稱、類型參數數目、參數數目和參數傳遞模式,而且對應參數的類型之間存在識別轉換({10.2.2)。
簽章是在類別、結構和介面中多載成員的啟用機制:
- 方法的多載可讓類別、結構或介面宣告具有相同名稱的多個方法,前提是其簽章在該類別、結構或介面內是唯一的。
- 實例建構函式的多載可讓類別或結構宣告多個實例建構函式,前提是其簽章在該類別或結構內是唯一的。
- 索引器多載可讓類別、結構或介面宣告多個索引器,前提是其簽章在該類別、結構或介面內是唯一的。
- 多載運算符可讓類別或結構宣告具有相同名稱的多個運算符,前提是其簽章在該類別或結構內是唯一的。
雖然 in
、 out
和 ref
參數修飾詞會被視為簽章的一部分,但單一類型中宣告的成員不能與、 in
和 out
完全ref
不同的簽章。 如果在相同類型中宣告了兩個成員,且簽章相同,則編譯時間錯誤會是相同的,如果具有 out
或 in
修飾詞的方法中的所有參數都變更為 ref
修飾詞, 就會發生編譯時期錯誤。 對於簽章比對的其他用途(例如隱藏或覆寫), in
、 out
和 ref
會被視為簽章的一部分,而且彼此不符。
注意:這項限制是允許 C# 程式輕鬆地轉譯為在 Common Language Infrastructure (CLI) 上執行,這不提供定義在 、
in
和out
中ref
唯一差異的方法。 end note
比較簽章時,不會區分型 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[])
上述識別,會導致編譯時期錯誤。 end 範例
7.7 範圍
7.7.1 一般
名稱 的範圍 是程式文字的區域,其中可以參考名稱所宣告的實體,而沒有名稱的限定性。 範圍可以 巢狀化,而內部範圍可能會從外部範圍重新宣告名稱的意義。 (不過,這不會移除 7.3 所強加的限制,即在巢狀區塊內,不可能宣告與封入區塊中局部變數或局部常數同名的局部變數或局部常數。然後,外部範圍的名稱會隱藏在內部範圍所涵蓋的程式文字區域中,而且只有限定名稱才能存取外部名稱。
namespace_member_declaration (14.6) 所宣告的命名空間成員範圍,不含封入namespace_declaration是整個程序文字。
命名空間成員的範圍,由
N
所宣告,是其完整名稱為 或 開頭N
之每個N
namespace_body,後面接著句號。extern_alias_directive所定義之名稱的範圍會延伸至using_directives、global_attributes和namespace_member_declaration s,其立即包含compilation_unit或namespace_body。 extern_alias_directive不會為基礎宣告空間貢獻任何新成員。 換句話說, extern_alias_directive 不是可轉移的,而是只會 影響發生compilation_unit 或 namespace_body 。
using_directive所定義或匯入的名稱範圍會延伸至發生using_directive compilation_unit或namespace_body的global_attributes和namespace_member_declaration。 using_directive可能會在特定compilation_unit或namespace_body內提供零或多個命名空間或類型名稱,但不會為基礎宣告空間貢獻任何新成員。 換句話說, using_directive 不是可轉移的,而是只會 影響發生compilation_unit 或 namespace_body 。
class_declaration上type_parameter_list所宣告的類型參數範圍(15.2)是該class_declaration的class_base、type_parameter_constraints_clauses和class_body。
注意:不同於類別的成員,此範圍不會延伸至衍生類別。 end note
struct_declaration上type_parameter_list宣告的類型參數範圍是struct_interfaces、type_parameter_constraints_clause和該struct_declaration的struct_body。
interface_declaration上type_parameter_list所宣告的類型參數範圍是interface_base、type_parameter_constraints_clauses 和該interface_declaration的interface_body。
delegate_declaration上type_parameter_list所宣告的類型參數範圍(20.2)是該delegate_declaration的return_type、parameter_list和type_parameter_constraints_clause。
method_declaration 上type_parameter_list所宣告的類型參數範圍(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 ({19.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_declaration的constructor_initializer和區塊。
在 lambda_expression 中宣告的參數範圍(•12.19)是該lambda_expression的lambda_expression_body。
在anonymous_method_expression中宣告的參數範圍(\12.19)是該anonymous_method_expression的區塊。
在labeled_statement中宣告的標籤範圍({13.5)是宣告發生所在的區塊。
在 local_variable_declaration ({13.6.2) 中宣告的局部變數範圍是宣告發生所在的區塊。
在語句switch_block中宣告的局部變數範圍是
switch
switch_block。在語句for_initializer中宣告
for
在 local_constant_declaration ({13.6.3) 中宣告的本機常數範圍是宣告發生所在的區塊。 在constant_declarator之前的文字位置中,參考本機常數是編譯時期錯誤。
宣告為foreach_statement、using_statement、lock_statement或query_expression之變數的範圍取決於指定建構的擴充。
在命名空間、類別、結構或列舉成員的範圍內,可以參考成員在成員宣告之前文字位置的成員。
範例:
class A { void F() { i = 1; } int i = 0; }
在這裡,它適用於
F
在宣告之前參考i
。end 範例
在局部變數的範圍內,參考其宣告子前面的文字位置中的局部變數是編譯時期錯誤。
範例:
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中先前宣告子中宣告的局部變數。end 範例
注意:局部變數和局部常數的範圍規則是設計來保證表達式內容中使用的名稱意義一律在區塊內相同。 如果局部變數的範圍只從其宣告延伸至區塊的結尾,則上述範例中,第一個指派會指派給實例變數,而第二個指派會指派給局部變數,如果區塊的語句稍後重新排列,可能會導致編譯時間錯誤。
區塊內名稱的意義可能會因使用名稱的內容而有所不同。 在範例中
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
。end note
7.7.2 名稱隱藏
7.7.2.1 一般
實體的範圍通常包含比實體宣告空間更多的程序文字。 特別是,實體的範圍可能包含引進包含相同名稱實體之新宣告空間的宣告。 這類宣告會導致原始實體變成 隱藏。 相反地,實體在未隱藏時會顯示。
當範圍透過巢狀重疊,以及範圍透過繼承重疊時,就會發生名稱隱藏。 下列子集合會說明這兩種隱藏類型的特性。
7.7.2.2 隱藏巢狀
隱藏巢狀的名稱可能會因為巢狀命名空間或命名空間內的類型而發生,因為類別或結構內的巢狀類型、本機函式或 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 主體內的 。end 範例
當內部範圍中的名稱隱藏外部範圍中的名稱時,它會隱藏該名稱的所有多載專案。
範例:在下列程式代碼中
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")
會導致編譯時間錯誤。end 範例
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
的方法不存在於舊版 類別中。end 範例
隱藏繼承名稱所造成的警告可以透過使用 new
修飾詞來消除:
範例:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
修飾
new
詞表示 中的F
Derived
是 「new」 而且確實打算隱藏繼承的成員。end 範例
新成員的宣告只會隱藏新成員範圍內的繼承成員。
範例:
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
end 範例
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?
| namespace_or_type_name '.' identifier type_argument_list?
| qualified_alias_member
;
namespace_name是參考命名空間的namespace_or_type_name。
下列解決方法如下所述,namespace_name的namespace_or_type_name應參考命名空間,否則會發生編譯時期錯誤。 namespace_name中不能有類型自變數 (~8.4.2) (只有類型可以有類型自變數)。
type_name是參考類型的namespace_or_type_name。 依照以下所述解決方式,type_name的namespace_or_type_name應參考類型,否則會發生編譯時期錯誤。
如果namespace_or_type_name是qualified_alias_member其意義如 •14.8.1 中所述。 否則,namespace_or_type_name有四種形式之一:
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
其中 I
是單一標識符, N
是 namespace_or_type_name ,是 <A₁, ..., Aₓ>
選擇性 type_argument_list。 未 指定type_argument_list 時,請考慮 x
為零。
namespace_or_type_name的意義取決於:
- 如果namespace_or_type_name是qualified_alias_member,則意義如 ≦14.8.1 中所指定。
- 否則,如果
I<A₁, ..., Aₓ>
:- 如果
x
為零,且namespace_or_type_name出現在泛型方法宣告 ({15.6) 內,但在其 method-header 的屬性之外,而且如果該宣告包含名稱為 的類型參數 (I
),則namespace_or_type_name會參考該類型參數。 - 否則,如果 namespace_or_type_name 出現在類型宣告內,則針對每個實例類型
T
({15.3.2),從該類型宣告的實例類型開始,並繼續每個封入類別或結構宣告的實例類型(如果有的話):- 如果
x
為零,且的T
宣告包含名稱I
為的類型參數,則 namespace_or_type_name 會參考該類型參數。 - 否則,如果namespace_or_type_name出現在類型宣告的主體內,或其
T
任何基底類型都包含具有名稱和I
x
類型參數的巢狀可存取型別,則namespace_or_type_name會參考以指定類型自變數建構的型別。 如果有多個這類類型,則會選取在更多衍生類型內宣告的類型。
注意:判斷namespace_or_type_name意義時,會忽略非類型成員(常數、字段、方法、屬性、索引器、運算元、實例建構函式、完成項和靜態建構函式)和類型成員,以及具有不同類型參數數目的類型成員。 end note
- 如果
- 否則,針對每個命名空間
N
,從發生namespace_or_type_name的命名空間開始,繼續進行每個封入命名空間(如果有的話),並以全域命名空間結尾,則會評估下列步驟,直到實體找到為止:- 如果
x
為零,且I
是 中的N
命名空間名稱,則:- 如果發生namespace_or_type_name的位置是由 命名空間宣告括住,而命名空間宣告包含
N
或using_alias_directive,將名稱與命名空間或類型產生關聯,則I
模棱兩可,而且會發生編譯時期錯誤。 - 否則,namespace_or_type_name會參考 中名為 的
I
N
命名空間。
- 如果發生namespace_or_type_name的位置是由 命名空間宣告括住,而命名空間宣告包含
- 否則,如果
N
包含具有名稱和I
x
類型參數的可存取類型,則:- 如果
x
為零,且發生namespace_or_type_name的位置會由 的命名空間宣告括住,且命名空間宣告N
包含與命名空間或類型相關聯的extern_alias_directive或I
,則namespace_or_type_name模棱兩可,而且會發生編譯時期錯誤。 - 否則, namespace_or_type_name 會參考以指定型別自變數建構的類型。
- 如果
- 否則,如果發生namespace_or_type_name的位置是由 的
N
命名空間宣告括住:- 如果
x
為零,且命名空間宣告包含 extern_alias_directive 或 using_alias_directive ,使名稱I
與匯入的命名空間或類型產生關聯,則 namespace_or_type_name 會參考該命名空間或類型。 - 否則,如果命名空間宣告的 using_namespace_directive所匯入的命名空間只包含一個具有名稱和
I
x
類型參數的類型,則namespace_or_type_name會參考以指定型別自變數建構的型別。 - 否則,如果命名空間宣告的 using_namespace_directive 所匯入的命名空間包含多個具有名稱和
I
類型參數的類型,則x
模棱兩可且發生錯誤。
- 如果
- 如果
- 否則, namespace_or_type_name 未定義,而且會發生編譯時期錯誤。
- 如果
- 否則,
N.I<A₁, ..., Aₓ>
。N
會先解析為 namespace_or_type_name。 如果的解決N
方式未成功,就會發生編譯時期錯誤。 否則,N.I
或N.I<A₁, ..., Aₓ>
會解析如下:- 如果
x
為零且N
參考命名空間,且N
包含名稱I
為的巢狀命名空間,則 namespace_or_type_name 會參考該巢狀命名空間。 - 否則,如果
N
參考命名空間並N
包含具有名稱和I
x
型別參數的可存取類型,則namespace_or_type_name會參考以指定型別自變數建構的型別。 - 否則,如果
N
參考 (可能建構的) 類別或結構類型,以及N
或其任何基類包含具有名稱和I
x
類型參數的巢狀可存取型別,則namespace_or_type_name會參考以指定型別自變數建構的型別。 如果有多個這類類型,則會選取在更多衍生類型內宣告的類型。注意:如果將 的意義
N.I
判斷為解析 的N
基類規格的一部分,則會將N
的object
直接基類視為 (~15.2.4.2)。 end note - 否則,
N.I
是無效 的namespace_or_type_name,而且會發生編譯時期錯誤。
- 如果
只有當靜態類別 (≦15.2.2.4) 時才允許參考namespace_or_type_name
-
namespace_or_type_name是
T
格式為、 或 namespace_or_type_name的T.I
。 -
namespace_or_type_name 表示在 typeof_expression (§12.8.18)形式
typeof(T)
中的T
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<> } }
end 範例
7.9 自動記憶體管理
C# 會採用自動記憶體管理,以釋放開發人員手動配置和釋放物件所佔用的記憶體。 垃圾收集行程會實作自動記憶體管理原則。 物件的記憶體管理生命週期如下所示:
- 建立物件時,會為其配置記憶體、執行建構函式,並將對象視為 即時。
- 如果物件及其任何實例欄位都無法透過任何可能的執行接續來存取,但執行完成項以外的任何實例欄位,該物件就會被視為 不再使用 中,而且它有資格進行最終處理。
注意:C# 編譯程式和垃圾收集行程可能會選擇分析程式代碼,以判斷未來可能會使用的物件參考。 例如,如果範圍中的局部變數是物件的唯一現有參考,但該程式中目前執行點執行的任何可能接續中都不會參考該局部變數,垃圾收集行程可能會(但不需要)將對象視為不再使用中。 end note
- 一旦對象有資格進行最終處理,在稍後執行物件的完成項(若有的話)時,在一些未指定的時間。 在正常情況下,物件的完成項只會執行一次,不過實作定義的 API 可能會允許覆寫此行為。
- 執行物件的完成項之後,如果物件及其任何實例欄位都無法透過任何可能的繼續執行來存取,包括執行完成項,物件就會被視為無法存取,而且物件會變成符合集合資格。
注意:先前無法存取的物件可能會因為完成項而再次存取。 以下提供此範例。 end note
- 最後,在對象變成符合收集資格的某個時間,垃圾收集行程會釋放與該對象相關聯的記憶體。
垃圾收集行程會維護物件使用方式的相關信息,並使用這項資訊來進行記憶體管理決策,例如在記憶體中找出新建立的物件的位置、重新放置對象的時間,以及物件不再使用或無法存取時。
如同假設垃圾收集行程存在的其他語言,C# 的設計目的是讓垃圾收集行程可以實作各種不同的記憶體管理原則。 C# 不會指定該範圍內的時間條件約束,也不會指定執行完成項的順序。 是否執行完成項做為應用程式終止的一部分,是實作定義的 (~7.2)。
垃圾收集行程的行為可以透過 類別 System.GC
上的靜態方法來控制。 這個類別可用來要求集合發生、要執行完成項(或未執行),依此類推。
範例:由於垃圾收集行程在決定何時收集對象和執行完成項時允許寬緯度,因此一個符合的實作可能會產生與下列程式代碼所示不同的輸出。 程式
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
。end 範例
7.10 執行順序
執行 C# 程式會繼續進行,讓每個執行線程的副作用都會保留在關鍵執行點。
副作用定義為揮發性欄位的讀取或寫入、對非揮發性變數的寫入、外部資源的寫入,以及擲回例外狀況。 要保留這些副作用順序的關鍵執行點是對變動性字段(~15.5.4lock
)和線程建立和終止的參考。 執行環境可以自由地變更 C# 程式的執行順序,但受限於下列條件約束: