8.1 一般規定
C#語言的類型分為兩大類: 引用類型 和 值類型。 值類型或參照類型可以是 泛型類型,它採用一或多個 類型參數s。 類型參數可以指定值類型和參考類型。
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§24.3) 僅適用於不安全的程式碼 (§24)。
值類型與參照類型的不同之處在於,值類型的變數直接包含其資料,而參照類型的變數儲存其資料的 參照 ,後者稱為 物件。 使用引用類型時,兩個變數可以引用相同的對象,因此對一個變數的操作可能會影響另一個變數所引用的對象。 使用值類型時,每個變數都有自己的資料副本,而且對一個變數的操作不可能影響另一個變數。
附註: 當變數是參照或輸出參數時,它沒有自己的儲存體,但參照另一個變數的儲存體。 在此情況下,ref 或 out 變數實際上是另一個變數的別名,而不是不同的變數。 結尾註釋
C# 的類型系統是統一的,因此 任何類型的值都可以被視為物件。 C# 中的每個類型都直接或間接衍生自 object 類別類型,並且 object 是所有類型的最終基類。 參考類型的值只需將值視為類型 object,即可將參考類型的值視為物件。 值類型的值會藉由執行裝箱和取消裝箱作業 (§8.3.13) 來處理為物件。
為了方便起見,在本規範中,某些程式庫類型名稱是在不使用其全名限定的情況下編寫的。 有關更多信息,請參閱 §C.5 。
8.2 參考類型
8.2.1 一般規定
參考類型是類別類型、介面類型、陣列類型、委派類型、 dynamic 類型,或任何限制為參考類型的類型參數 (也就是,具有參考類型條件約束或類別類型條件約束的任何類型參數 (§15.2.5) ) 。 針對每個不可為 Null 的參考類型,都會有對應的可為 Null 的參考類型,方法是將 附加至 ? 類型名稱。
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type 僅適用於不安全的程式碼 (§24.3)。 nullable_reference_type 在 §8.9 中進一步討論。
參考類型值是對類型 實例 的引用,後者稱為物件。 特殊值 null 與所有參考類型相容,並指出沒有實例。
8.2.2 類別類型
類別類型會定義包含 資料成員(常數和欄位)、 函式成員(方法、屬性、事件、索引子、運算子、實例建構函式、終結函式和靜態建構函式) 和巢狀類型的資料結構。 類別類型支援繼承,這是一種衍生類別可以擴充和特製化基底類別的機制。 類別類型的實例是使用 object_creation_expression(§12.8.17.2) 建立。
類別類型在 §15 中描述。
某些預先定義的類別類型在 C# 語言中具有特殊意義,如下表所述。
| 班級類型 | Description |
|---|---|
System.Object |
所有其他類型的終極基類。 參見 §8.2.3。 |
System.String |
C# 語言的字串類型。 參見 §8.2.5。 |
System.ValueType |
所有值類型的基類。 參見 §8.3.2。 |
System.Enum |
所有 enum 類型的基類。 參見 §20.5。 |
System.Array |
所有陣列類型的基類。 請參閱 §17.2.2。 |
System.Delegate |
所有 delegate 類型的基類。 參見 §21.1。 |
System.Exception |
所有例外狀況類型的基類。 參見 §22.3。 |
8.2.3 物件類型
類別類型是 object 所有其他類型的最終基類。 C# 中的每個類型都直接或間接衍生自 object 類別類型。
關鍵字 object 只是預先定義類別 System.Object的別名。
8.2.4 動態類型
類型( dynamic 例如 object)可以引用任何物件。 當作業套用至類型 dynamic的運算式時,會延遲其解析,直到程式執行為止。 因此,如果操作無法合法地應用於引用的對象,則在編譯期間不會給出錯誤。 相反地,當執行階段作業解析失敗時,會擲回例外狀況。
dynamic該類型在 §8.7 中進一步描述,動態綁定在 §12.3.1 中進一步描述。
8.2.5 字串類型
該 string 類型是直接繼承自 object的密封類別類型。 類別的 string 實例代表 Unicode 字串。
類型的值 string 可以寫成字串常值 (§6.4.5.6) 。
關鍵字 string 只是預先定義類別 System.String的別名。
8.2.6 介面類型
介面定義合約。 實作介面的類別或結構體應遵守其合約。 一個介面可以繼承自多個基底接口,而一個類別或結構可以實作多個介面。
介面類型在 §19 中描述。
8.2.7 陣列類型
陣列是包含零個或多個變數的資料結構,這些變數透過計算索引存取。 數組中包含的變量,也稱為數組的元素,都是同一類型,這種類型稱為數組的元素類型。
陣列類型在 §17 中描述。
8.2.8 委託類型
委派是參照一或多個方法的資料結構。 對於實例方法,它也指其對應的物件實例。
附註: C 或 C++ 中委派最接近的對等專案是函式指標,但函式指標只能參考靜態函式,而委派可以同時參考靜態和實例方法。 在後一種情況下,委派不僅會儲存方法進入點的參考,還會儲存要叫用方法之物件實例的參考。 結尾註釋
委派類型會在 §21 中說明。
8.3 值類型
8.3.1 一般規定
值類型是結構類型或列舉類型。 C# 提供一組預先定義的結構類型,稱為 簡單類型s。 簡單類型是透過關鍵字來識別。
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
不同於參考類型的變數,只有在值類型是可為 Null 的值類型時,值類型的變數才能包含值 null (§8.3.12) 。 針對每個不可為 Null 的值類型,都有對應的可為 Null 值類型,表示一組相同的值加上值 null。
指派給值類型的變數會建立所指派值的 副本 。 這與對參照類型變數的賦值不同,參照類型會複製參照,但不會複製參照所識別的物件。
8.3.2 System.ValueType 類型
所有值類型都隱含地繼承自 classSystem.ValueType,而 又繼承自類別 object。 任何類型都不可能衍生自值類型,因此值類型會隱含密封 (§15.2.2.3) 。
請注意,這 System.ValueType 本身並不是 value_type。 相反,它是一個 class_type ,所有 value_type都是自動派生出來的。
8.3.3 預設建構函式
所有值類型都會隱含地宣告稱為 預設建構函式的公用無參數實例建構函式。 預設建構函式會傳回稱為值類型的 預設值 的零初始化實例:
- 對於所有 simple_type,預設值是由所有零的位模式所產生的值:
- 對於
sbyte、byte、shortushortintuintlong和ulong,預設值為 。0 - 對於
char,預設值為'\x0000'。 - 對於
float,預設值為0.0f。 - 對於
double,預設值為0.0d。 - 對於
decimal,預設值為0m(亦即,比例為 0 的值為零)。 - 對於
bool,預設值為false。 - 對於 enum_type
E,預設值為0,轉換為類型E。
- 對於
- 對於 struct_type,預設值是將所有值類型欄位設為其預設值,並將所有參照類型欄位設為
null所產生的值。 - 對於 nullable_value_type 預設值是屬性為 false 的
HasValue實例。 預設值也稱為可為 Null 值類型的 Null 值 。 嘗試讀取Value這類值的屬性會導致擲回類型的System.InvalidOperationException例外狀況 (§8.3.12)。
如同任何其他實例建構函式,值類型的預設建構函式會使用運算子來 new 叫用。
附註: 基於效率考量,此需求並非要讓實作實際產生建構函式呼叫。 針對值類型,預設值運算式 (§12.8.21) 會產生與使用預設建構函式相同的結果。 結尾註釋
範例:在下面的程式碼中,變數
i和jk都初始化為零。class A { void F() { int i = 0; int j = new int(); int k = default(int); } }結束範例
因為每個值類型都隱含地具有公用無參數實例建構函式,所以結構類型無法包含無參數建構函式的明確宣告。 不過,允許結構類型宣告參數化實例建構函式 (§16.4.9) 。
8.3.4 結構體類型
結構類型是一種值類型,可以宣告常數、欄位、方法、屬性、事件、索引子、運算子、實例建構函式、靜態建構函式和巢狀類型。 結構類型的宣告會在 §16 中說明。
8.3.5 簡單類型
C# 提供一組稱為簡單類型的預先定義 struct 類型。 簡單型別是透過關鍵字來識別,但這些關鍵字只是命名空間中struct預先定義System類型的別名,如下表所述。
| Keyword | 別名類型 |
|---|---|
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
因為簡單類型會別名結構類型,所以每個簡單類型都有成員。
範例:
int有宣告的System.Int32成員和繼承自System.Object的成員,並且允許以下語句:int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method結束範例
注意:簡單類型與其他結構類型的不同之處在於它們允許某些額外的操作:
- 大部分的簡單類型都允許藉由撰寫 常值 來建立值 (§6.4.5),不過 C# 一般不會提供結構類型的常值。 範例:
123是類型的int文字,'a'是類型的char文字。 結束範例- 當表達式的操作數都是簡單的類型常數時,編譯程式可以在編譯時期評估表達式。 這種表達式稱為 constant_expression (§12.25)。 涉及其他結構類型所定義運算子的運算式不會被視為常數運算式
- 透過
const宣告,可以宣告簡單類型的常數 (§15.4)。 不可能有其他結構類型的常數,但靜態唯讀欄位會提供類似的效果。- 涉及簡單類型的轉換可以參與其他結構類型所定義的轉換運算子的評估,但使用者定義的轉換運算子永遠無法參與另一個使用者定義轉換運算子的評估 (§10.5.3)。
尾註。
8.3.6 積分類型
C# 支援九種整數類型:sbyte、byte、 shortushortintuintlongulong和 。char 整數類型具有下列大小和值範圍:
- 類型代表
sbyte值從 到-128127,包括 的有符號 8 位整數。 - 類型
byte代表值從 到0255,包括 的無正負號 8 位整數。 - 類型
short代表值從 到-3276832767,包括 的有符號 16 位整數。 - 類型代表
ushort值從 到 (0包括 ) 的65535無正負號 16 位整數。 - 類型
int代表值從 到-21474836482147483647,包括 的有符號 32 位整數。 - 類型代表
uint值從 到04294967295,包括 的無正負號 32 位整數。 - 類型
long代表值從 到-9223372036854775808(包括 ) 的9223372036854775807有符號 64 位整數。 - 類型
ulong代表值從 到018446744073709551615,包括 的無正負號 64 位整數。 - 類型代表
char值從 到 (0包括 ) 的65535無正負號 16 位整數。 類型的可能值char集對應至 Unicode 字元集。附註: 雖然具有與 相同的
char表示法,但ushort並非一種類型上允許的所有作業都允許在另一種類型上。 結尾註釋
所有有符號整數類型都使用二的補碼格式表示。
integral_type一元和二進位運算子一律會以帶正負號的 32 位精確度、無正負號的 32 位精確度、有正負號的 64 位精確度或無正負號的 64 位精確度來運作,如 §12.4.7 中所述。
該 char 類型被歸類為整數類型,但它與其他整數類型有兩個不同之處:
- 沒有預先定義的從其他類型到該
char類型的隱含轉換。 特別是,即使 和byteushort類型具有使用類型完全char可表示的值範圍,但從 sbyte、byte 或ushorttochar的隱含轉換並不存在。 - 該
char類型的常數應寫成 character_literals 或 integer_literals,並結合轉換為 char 類型。
範例:
(char)10與'\x000A'相同。 結束範例
checked and unchecked 運算子和陳述式可用來控制整數類型算術運算和轉換的溢位檢查 (§12.8.20) 。 在內容中checked,溢位會產生編譯時間錯誤或導致擲回 。System.OverflowException 在內容中 unchecked ,溢位會被忽略,並且會捨棄任何不適合目的地類型的高位。
8.3.7 浮點類型
C# 支援兩種浮點類型: float 和 double.
float和double類型會使用 32 位單精確度和 64 位雙精確度 IEC 60559 格式來表示,這些格式提供下列值集:
- 正零和負零。 在大部分情況下,正零和負零的行為與簡單值零相同,但某些運算會區分兩者 (§12.12.3)。
- 正無窮大和負無窮大。 無窮大是通過將非零數除以零等運算產生的。
範例:
1.0 / 0.0產生正無窮大,產生–1.0 / 0.0負無窮大。 結束範例 - Not-a-Number 值,通常縮寫為 NaN。 NaN 是由無效的浮點運算所產生,例如將零除以零。
- 形式的非零值的有限集合 s × m × 2e,其中 s 是 1 或 -1,並且 m 和 e 由特定的浮點類型決定:對於
float,0 <m< 2²⁴ 和 -149 ≤ e ≤ 104,對於 ,對於double< 0 m< 2⁵³ 和 -1075 ≤ e ≤ 970。 非正規化浮點數會被視為有效的非零值。 C# 既不需要也不禁止符合規範的實作支援非正規化浮點數。
該 float 類型可以表示範圍從大約 1.5 × 10⁻⁴⁵ 到 3.4 × 10³⁸ 的值,精度為 7 位數。
該 double 類型可以表示範圍從大約 5.0 × 10⁻³²⁴ 到 1.7 × 10³⁰⁸ 的值,精度為 15-16 位數。
如果二進位運算子的任一運算元是浮點類型,則會套用標準數值提升,如 §12.4.7 中所述,並使用 或 float 精確度執行double運算。
浮點運算子 (包括指派運算子) 永遠不會產生例外狀況。 相反地,在特殊情況下,浮點運算會產生零、無窮大或 NaN,如下所述:
- 浮點運算的結果會四捨五入至目的地格式中最接近的可表示值。
- 如果浮點運算結果的大小對於目的地格式來說太小,則運算的結果會變成正零或負零。
- 如果浮點運算結果的大小對於目的地格式來說太大,則運算的結果會變成正無窮大或負無窮大。
- 如果浮點運算無效,則運算的結果會變成 NaN。
- 如果浮點運算的一或兩個運算元是 NaN,則運算的結果會變成 NaN。
浮點運算的執行精度可能高於運算的結果類型。 若要強制浮點類型的值達到其類型的確切精確度,可以使用明確轉換 (§12.9.8) 。
範例:某些硬體架構支援「擴充」或「長雙精度」浮點類型,其範圍和精確度都比類型
double更大,並隱含地使用這種更高精度的類型執行所有浮點運算。 只有以過高的效能成本,才能讓這類硬體架構以 較低 的精確度執行浮點運算,而且不需要實作來犧牲效能和精確度,而是 C# 允許將更高精度的類型用於所有浮點運算。 除了提供更精確的結果外,這很少有任何可衡量的效果。 然而,在形式的表達式x * y / z中,乘法產生超出範圍的double結果,但後續的除法將臨時結果帶回範圍,double以更高範圍格式評估表達式的事實可能會導致產生有限結果而不是無窮大。 結束範例
8.3.8 十進制類型
該 decimal 類型是適用於財務和貨幣計算的 128 位數據類型。 類型 decimal 可以表示值,包括至少 -7.9 × 10⁻²⁸ 到 7.9 × 10²⁸ 範圍內的值,精確度至少為 28 位數。
類型的 decimal 有限值集的形式為 (–1)v × c × 10⁻e,其中符號 v 為 0 或 1,係數 c 由 0 ≤ c<Cmax 給出,尺度 e 使得 Emin ≤ e ≤ Emax,其中 Cmax 至少為 1 × 10²⁸, Emin ≤ 0, 埃 馬克斯 ≥ 28 歲。 類型 decimal 不一定支援有符號的零、無窮大或 NaN。
A decimal 表示為以 10 冪縮放的整數。 對於絕對值小於 decimal的 s,1.0m該值至少精確到小數點後第 28 位。 對於絕對值大於或等decimal於 的 s,1.0m該值至少精確到 28 位數。 與 float 和 double 資料類型相反,十進位小數 (例如 ) 0.1 可以精確地以十進位表示形式表示。 在 和 floatdouble 表示中,此類數字通常具有非終止二進位展開,使這些表示更容易出現捨入錯誤。
如果二進位運算子的任一運算元類型為類型, decimal 則會套用標準數值提升,如 §12.4.7 中所述,並精確執行 double 作業。
對類型 decimal 值進行操作的結果是計算精確結果(保留為每個運算子定義的比例),然後四捨五入以符合表示法所產生的結果。 結果會四捨五入至最接近的可表示值,當結果同樣接近兩個可表示值時,會四捨五入至在最低有效位數位置具有偶數的值 (這稱為「莊家捨入」)。 也就是說,結果至少精確到小數點後第 28 位。 請注意,四捨五入可能會從非零值產生零值。
如果算術運算產生的結果大小對於格式decimal來說太大,則decimal會擲出 aSystem.OverflowException。
該 decimal 類型具有更高的精度,但範圍可能比浮點類型小。 因此,從浮點類型轉換成 decimal 可能會產生溢位異常狀況,而從浮點類型轉換成 decimal 浮點類型可能會導致精確度遺失或溢位異常狀況。 基於這些原因,浮點類型 和 decimal之間不存在隱含轉換,如果沒有明確強制轉換,當浮點和 decimal 運算元直接混合在相同的表示式中時,就會發生編譯階段錯誤。
8.3.9 布爾類型
類型代表 bool 布林邏輯量。 type bool 的可能值是 true 和 false。 的 false 表示在 §8.3.3 中描述。 雖然沒有 true 具體說明,但應與 的 false表示不同。
與其他值類型之間 bool 不存在標準轉換。 特別是,類型 bool 與整數類型不同且分開, bool 值不能取代整數值,反之亦然。
注意:在C和C++語言中,可以將零整數或浮點值,或空指標轉換為布林值
false,將非零整數或浮點值,或非空指標轉換為布林值true。 在 C# 中,這類轉換是藉由明確比較整數或浮點值與零,或明確比較物件參考null來完成。 結尾註釋
8.3.10 枚舉類型
列舉類型是具有具名常數的特殊類型。 每個列舉類型都有一個基礎類型,它應該是 byte、 、 sbyteshortushort、 、 intuint、 或 。 longulong 列舉類型的值集與基礎類型的值集相同。 列舉類型的值不限於具名常數的值。 列舉類型是透過列舉宣告 (§20.2) 來定義。
8.3.11 元組類型
元組類型代表具有可選名稱和個別類型的有序、固定長度的值序列。 元組類型中的元素數稱為其 arity。 元組類型是用 n ≥ 2 編寫 (T1 I1, ..., Tn In) 的,其中標識符 I1...In 是可選的 元組元素名稱s。
此語法是使用 T1...Tn 類型 所 System.ValueTuple<...>建構的類型的簡寫,該類型應該是一組泛型結構類型,能夠直接表示介於 2 到 7 之間的任何元組類型。
不需要存在 System.ValueTuple<...> 直接符合任何元組類型具有對應類型參數數目的元組類型 arity 的宣告。 相反地,arity 大於 7 的元組會以泛型結構類型 System.ValueTuple<T1, ..., T7, TRest> 表示,除了元組元素之外,還有一個 Rest 欄位,其中包含其餘元素的巢狀值,使用另一種 System.ValueTuple<...> 類型。 這種嵌套可以通過多種方式觀察到,例如通過場的 Rest 存在。 如果只需要單一附加欄位,則會使用泛型結構類型 System.ValueTuple<T1> ;此類型本身不會被視為元組類型。 如果需要七個以上的額外欄位, System.ValueTuple<T1, ..., T7, TRest> 則遞迴使用。
元組類型內的元素名稱應該是不同的。 形式的元組元素名稱 ItemX,其中 X 是可以代表元組元素位置的任何非0起始十進制數字序列,僅允許在 表示的位置 X。
選擇性元素名稱不會在類型中 ValueTuple<...> 表示,也不會儲存在元組值的執行階段表示法中。 身分轉換 (§10.2.2) 存在於具有元素類型身分可轉換序列的元組之間。
new運算子 §12.8.17.2 無法與元組類型語法new (T1, ..., Tn)搭配套用。 元組值可以從元組運算式 (§12.8.6) 建立,或將運算子直接套用 new 至從 ValueTuple<...>建構的類型。
元組元素是名稱 Item1為 , Item2, 等的公用欄位,可透過元組值的成員存取 (§12.8.7. 此外,如果元組類型具有給定元素的名稱,則該名稱可用於存取相關元素。
注意:即使大元組以巢狀
System.ValueTuple<...>值表示,每個元組元素仍然可以使用與其位置相對應的Item...名稱直接存取。 結尾註釋
範例:給定以下範例:
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");、
pair1和pair2的pair3元組類型都有效,名稱為 no、部分或所有元組類型元素。的
pair4元組類型是有效的,因為名稱Item1和Item2匹配它們的位置,而不允許的pair5元組類型是不允許的,因為名稱Item2Item123和不匹配。的
pair6宣告 和pair7示範元組類型可與 形式的ValueTuple<...>建構類型互換,而且new允許運算子使用後者的語法。最後一行顯示元組元素可以透過與其位置相對應的
Item名稱以及對應的元組元素名稱(如果類型中存在)來存取元組元素。 結束範例
8.3.12 可為空值類型
可為 Null 的值類型可以代表其基礎類型的所有值,以及額外的 Null 值。 會寫入 T?可為 Null 的值類型,其中 T 是基礎類型。 此語法是 的簡寫 System.Nullable<T>,這兩種形式可以互換使用。
相反地,不可為空值的值類型是 及其簡寫 System.Nullable<T> ( T?的任何 ) 以外的任何值類型T,加上任何受限為不可為空值類型的類型參數 (,也就是具有值類型條件約束 (§15.2.5) ) 的任何類型參數。 類型會 System.Nullable<T> 指定 的 T值類型條件約束,這表示可為 Null 值類型的基礎類型可以是任何不可為 Null 的值類型。 可為 Null 值類型的基礎類型不能是可為 Null 的值類型或參考類型。 例如, int?? 是無效的類型。 可為 Null 的參考類型包含在 §8.9 中。
可為 Null 值類型的 T? 實例有兩個公用唯讀屬性:
-
HasValue類型為bool -
Value類型為T
的HasValue實例true稱為非空值。 非 Null 實例包含已知值並 Value 傳回該值。
的HasValue實例false稱為空值。 空值實例具有未定義的值。 嘗試讀取Value空值實例會導致擲回 。System.InvalidOperationException 存取可為 Null 實例的 Value 屬性的程式稱為 解包。
除了預設建構函式之外,每個可為 Null 的值類型 T? 都有一個公用建構函式,其單一參數類型為 T。 給定類型 x的值 T ,建構函式呼叫
new T?(x)
建立非 Null 實例 T? ,其 Value 屬性為 x。 為指定值建立可為 Null 值類型的非 Null 實例的程式稱為 包裝。
隱含轉換可從 null 文字 T? (§10.2.7) 和從 TT? (§10.2.6) 取得。
可為 Null 的值類型 T? 不會實作任何介面 (§19) 。 特別是,這表示它不會實作基礎類型 T 所執行的任何介面。
8.3.13 裝箱和拆箱
裝箱和拆箱的概念通過允許將value_type的任何值轉換為類型和從類型轉換,在value_type和 object之間架起了一座橋樑。 裝箱和取消裝箱可啟用類型系統的統一檢視,其中任何類型的值最終都可以被視為 object。
裝箱會在 §10.2.9 中更詳細地說明,而取消裝箱會在 §10.3.7 中說明。
8.4 構造類型
8.4.1 一般規定
泛型類型宣告本身表示 未繫結的泛型類型 ,該類型會用來做為「藍圖」,藉由套用 類型引數s 來形成許多不同的類型。 類型引數會寫入尖括弧 (< 和 >) 內,緊接在泛型類型名稱之後。 包含至少一個類型引數的類型稱為 建構類型。 建構的類型可以在類型名稱可以出現的語言中的大部分位置使用。 未繫結的泛型類型只能在 typeof_expression 內使用 (§12.8.18) 。
建構類型也可以在運算式中使用為簡單名稱 (§12.8.4) 或存取成員時 (§12.8.7) 。
評估 namespace_or_type_name 時,只會考慮具有正確類型參數數目的泛型類型。 因此,可以使用相同的標識符來識別不同的類型,只要這些類型具有不同數量的類型參數即可。 當在同一程式中混合泛型和非泛型類別時,這很有用。
Example:
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }結束範例
namespace_or_type_name作品中名稱查找的詳細規則在 §7.8 中進行了描述。 這些作品中歧義的解決在 §6.2.5 中進行了描述。
type_name可能會識別建構的類型,即使它沒有直接指定類型參數。 當類型巢狀在泛型 class 宣告內,且包含宣告的實例類型隱含地用於名稱查閱 (§15.3.9.7) 時,可能會發生這種情況。
Example:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }結束範例
非枚舉建構類型不得用作 unmanaged_type (§8.8)。
8.4.2 類型引數
類型引數清單中的每個引數都只是一個 類型。
type_argument_list
: '<' type_argument (',' type_argument)* '>'
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
每個類型引數都應滿足對應類型參數的任何條件約束 (§15.2.5) 。 其可 Null 不符合類型參數可 Null 的參考類型引數滿足條件約束;但是,可能會發出警告。
8.4.3 開放式和封閉式
任何類型都可以分為開放式或封閉式。 開放類型是涉及類型參數的類型。 更具體地說:
- 類型參數會定義開啟類型。
- 陣列類型是開放類型,當且僅當其元素類型是開放類型時。
- 建構型別是開放型別,當且僅當其一或多個類型引數是開啟型別時。 建構巢狀類型是開放類型,當且僅當其一或多個類型引數或其一或多個包含類型的類型引數是開啟類型時。
封閉式是不是開放式的類型。
在執行階段,泛型類型宣告內的所有程式碼都會在封閉建構類型的內容中執行,該內容是透過將類型引數套用至泛型宣告所建立。 泛型類型內的每個類型參數都會系結至特定的執行時間類型。 所有陳述式和表示式的執行時期處理一律會以封閉類型發生,而開放類型只會在編譯時期處理期間發生。
如果兩個封閉的建構類型是從相同的未繫結泛型類型建構,且每個對應的類型引數之間存在身分轉換,則它們是可轉換身分識別的 (§10.2.2) 。 對應的類型引數本身可以是封閉的建構類型或元組,這些類型或元組是可轉換的。 身分可轉換的封閉建構類型會共用一組靜態變數。 否則,每個封閉的建構類型都有自己的一組靜態變數。 由於執行時期不存在開放類型,因此沒有與開放類型相關聯的靜態變數。
8.4.4 綁定和未綁定類型
術語未繫結類型是指非泛型類型或未繫結泛型類型。 術語繫結類型是指非泛型類型或建構類型。
未繫結類型是指類型宣告所宣告的實體。 未繫結泛型類型本身不是類型,不能作為變數、引數或傳回值的類型,或作為基底類型。 唯一可以參考未繫結泛型類型的建構是 typeof 運算式 (§12.8.18) 。
8.4.5 滿足約束
每當參考建構類型或泛型方法時,都會根據泛型類型或方法上宣告的類型參數條件約束檢查提供的類型引數 (§15.2.5) 。 針對每個 where 子句,會針對每個條件約束檢查對應至具名類型參數的類型引數 A ,如下所示:
- 如果條件約束是
class類型、介面類型或類型參數,請使用C提供的類型引數來取代條件約束中出現的任何類型參數來表示該條件約束。 為了滿足條件約束,類型A應該可以透過下列其中一項轉換為類型C: - 如果條件約束是參考類型條件約束 (
class),則類型A應滿足下列其中一項:-
A是介面類型、類別類型、委派類型、陣列類型或動態類型。
附註:
System.ValueType和System.Enum是滿足此約束的參考類型。 結尾註釋-
A是已知為參考類型的類型參數 (§8.2)。
-
- 如果條件約束是值類型條件約束 (
struct),則類型A應滿足下列其中一項:-
A是struct類型或enum類型,但不是可為 Null 的值類型。
附註:
System.ValueType和System.Enum是不滿足此約束的參考類型。 結尾註釋-
A是具有值類型條件約束的類型參數 (§15.2.5)。
-
- 如果條件約束是建構函式條件約束
new(),則類型A不應是abstract,且應具有公用無參數建構函式。 如果符合下列其中一項,則符合此條件:
如果指定的類型引數不滿足一或多個類型參數的條件約束,就會發生編譯階段錯誤。
由於類型參數不會繼承,因此也永遠不會繼承約束。
範例:在以下情況下,
D需要對其類型參數T指定約束,以滿足T基數classB<T>施加的約束。 相反地,class不需要指定條件約束,因為EList<T>任何IEnumerable.Tclass B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}結束範例
8.5 類型參數
類型參數是識別碼,指定參數在執行階段繫結至的值類型或參考類型。
type_parameter
: identifier
;
由於類型參數可以使用許多不同的類型引數進行實例化,因此類型參數的操作和限制與其他類型略有不同。
注意:這些包括:
- 類型參數無法直接用來宣告基底類別 (§15.2.4.2) 或介面 (§19.2.4) 。
- 類型參數的成員查閱規則取決於套用至類型參數的限制 (如果有的話)。 它們在 §12.5 中詳細說明。
- 類型參數的可用轉換取決於套用至類型參數的條件約束 (如果有的話)。 它們在 §10.2.12 和 §10.3.8 中詳細說明。
- 常值
null無法轉換成類型參數所指定的類型,除非已知類型參數是參考類型 (§10.2.12) 。 不過,可以改用預設運算式 (§12.8.21) 。 此外,除非類型參數具有值類型條件約束,否則可以使用 and==(!=) 將類型所指定類型的值與 null 進行比較。new只有在類型參數受到constructor_constraint或值類型條件約束 (§15.2.5) 時,運算式 (§12.8.17.2) 才能與類型參數搭配使用。- 類型參數無法在屬性內的任何位置使用。
- 類型參數無法在成員存取 (§12.8.7) 或類型名稱 (§7.8) 中使用,以識別靜態成員或巢狀類型。
- 類型參數不能用作 unmanaged_type (§8.8)。
結尾註釋
作為類型,類型參數純粹是編譯階段建構。 在執行階段,每個類型參數都會系結至執行時期類型,該類型是透過提供泛型類型宣告的類型引數來指定的。 因此,使用類型參數宣告的變數類型會在執行階段是封閉的建構類型 §8.4.3。 所有涉及類型參數的陳述式和表示式的執行時期執行都會使用提供為該參數類型引數的類型。
8.6 運算式樹狀結構類型
運算式樹狀結構允許將 lambda 運算式表示為資料結構,而不是可執行程式碼。 運算式樹是 形式的System.Linq.Expressions.Expression<TDelegate>運算式樹狀結構類型的值,其中 TDelegate 是任何委派類型。 對於本規範的其餘部分,這些類型將使用簡寫 Expression<TDelegate>來引用。
如果存在從 lambda 運算式到委派類型的 D轉換,則也存在運算式樹狀結構類型的 Expression<TDelegate>轉換。 雖然將 lambda 運算式轉換為委派類型會產生參考 lambda 運算式可執行程式碼的委派,但轉換成運算式樹狀結構類型會建立 lambda 運算式的運算式樹表示法。 有關此轉換的更多詳細信息,請參閱 §10.7.3。
範例:下列程式會將 lambda 運算式表示為可執行程式碼和運算式樹狀結構。 因為轉換存在
Func<int,int>於 ,所以轉換也存在Expression<Func<int,int>>於:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data在這些指派之後,委派
del會參考傳回x + 1的方法,而運算式樹狀結構 exp 會參考描述運算式x => x + 1的資料結構。結束範例
Expression<TDelegate> 提供實例方法 Compile ,可產生類型 TDelegate為 :
Func<int,int> del2 = exp.Compile();
叫用此委派會導致執行運算式樹狀結構所代表的程式碼。 因此,給定上述定義, del 並且 del2 是等價的,以下兩個陳述將具有相同的效果:
int i1 = del(1);
int i2 = del2(1);
執行此程式碼後, i1 並且 i2 都會有值 2。
所 Expression<TDelegate> 提供的 API 介面是實作定義,超出上述方法的需求 Compile 。
注意:雖然為運算式樹狀結構提供的 API 詳細資料是實作定義的,但預期實作會:
- 啟用程式碼來檢查和回應作為 lambda 運算式轉換結果所建立的運算式樹狀結構
- 允許在使用者程式碼中以程式設計方式建立運算式樹狀結構
結尾註釋
8.7 動態類型
類型 dynamic 會使用動態繫結,如 §12.3.2 中詳細所述,而不是所有其他類型所使用的靜態繫結。
除了以下方面外,該類型 dynamic 被視為相同 object :
- 類型
dynamic運算式的運算可以動態系結 (§12.3.3)。 - 類型推斷 (§12.6.3) 會優先
dynamicobject於兩者都是候選。 -
dynamic不能用作- object_creation_expression中的類型 (§12.8.17.2)
- class_base (§15.2.4)
- member_access中的predefined_type (§12.8.7.1)
- 運算子的
typeof運算元 - 屬性引數
- 約束
- 延伸方法類型
- struct_interfaces (§16.2.5) 或 interface_type_list (§15.2.4.1) 內類型引數的任何部分。
由於這種等價性,以下內容成立:
- 有隱含的身分轉換
- between
object和dynamic - 在建構類型之間,這些類型在替換
dynamic為object - 在元組類型之間,當取代
dynamic為object
- between
- 隱含和明確的轉換 to 和 from
object也適用於dynamic和 from 。 - 替換
dynamic時object相同的簽名會被視為相同的簽名。 - 類型
dynamic與執行階段的類型object無法區分。 - 類型的
dynamic運算式稱為 動態運算式。
8.8 非受管理類型
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
unmanaged_type 是既不是reference_type也不是非受type_parameter的任何類型,且不包含類型不是unmanaged_type的實例欄位。 換句話說, unmanaged_type 是下列其中一項:
-
sbyte、byte、short、ushortintuintlongulongcharfloatdoubledecimal或 。bool - 任何 enum_type。
- 任何僅包含unmanaged_type實例欄位的使用者定義struct_type。
- 任何受限為非受控的類型參數。
- 任何 pointer_type (§24.3)。
8.9 參考類型和可空性
8.9.1 一般規定
可為 Null 的參考類型是藉由將nullable_type_annotation (?) 附加至不可為 Null 的參考類型來表示。 不可為空的參考類型與其對應的可為空值類型之間沒有語意差異,兩者都可以是物件的參考或 null。
nullable_type_annotation的存在或不存在會宣告運算式是否要允許空值。 當運算式未根據該意圖使用時,編譯器可能會提供診斷。 運算式的 Null 狀態定義在 §8.9.5 中。 身分識別轉換存在於可為 Null 的參考類型及其對應的不可為 Null 參考類型之間 (§10.2.2) 。
參考類型有兩種形式的可空值:
-
nullable:可以指派
null。 其預設 Null 狀態為 maybe-null。 -
不可為 Null:不應為不可 為 Null 的參考 指派值
null。 其預設 Null 狀態為 not-null。
便條: 類型
R和R?會以相同的基礎類型 表示R。 該基礎類型的變數可以包含對物件的引用,也可以是值null,表示「沒有參考」。 結尾註釋
可為 Null 的參考類型與其對應的不可為 Null 的參考類型之間的語法差異可讓編譯器產生診斷。 編譯器必須允許 §8.2.1 中定義的nullable_type_annotation。 診斷必須僅限於警告。 除了編譯階段產生的任何診斷訊息變更之外,無論是是否存在可為 Null 的註解,或可為 Null 內容的狀態都無法變更程式的編譯時間或執行階段行為。
8.9.2 不可為空的參考類型
不可為空的參考類型是形式 T的參考類型,其中 T 是類型的名稱。 不可為空變數的預設 Null 狀態為 not-null。 當需要非 Null 值時,可能會使用可能為 Null 的運算式時,可能會產生警告。
8.9.3 可為 Null 的參考類型
形式的 T? 參考類型 (例如 string?) 是 可為 Null 的參考類型。 可為 Null 變數的預設 Null 狀態 可能是 Null。 註釋 ? 指出此類型的變數可為 Null 的意圖。 編譯程式可以辨識這些意圖來發出警告。 停用可為 Null 的註解內容時,使用此註解可能會產生警告。
8.9.4 可為 Null 的內容
8.9.4.1 一般規定
每一行原始程式碼都有 可為 Null 的內容。 可為 Null 內容的批註和警告旗標分別控制可為 Null 的批註 (§8.9.4.3) 和可為 Null 的警告 (§8.9.4.4) 。 每個旗標都可以 啟用 或 停用。 編譯程式可以使用靜態流程分析來判斷任何參考變數的 Null 狀態。 參考變數的 Null 狀態 (§8.9.5) 不是 Null、 可能是 Null,或 可能是預設值。
可為 Null 的內容可以透過可為 Null 的指示詞 (§6.5.9) 和/或透過原始程式碼外部的某些實作特定機制在原始程式碼中指定。 如果同時使用這兩種方法,則可為 Null 的指示詞會取代透過外部機制所做的設定。
可為 Null 內容的預設狀態是實作定義。
在此規格中,所有不包含可為 Null 的指示詞,或未針對目前可為 Null 內容狀態的陳述式進行的任何 C# 程式碼,都應假設已使用同時啟用註解和警告的可 Null 內容來編譯。
便條: 這兩個旗標都已停用的可 Null 內容符合參考類型的先前標準行為。 結尾註釋
8.9.4.2 可為空的停用
當警告和註釋旗標都停用時,會 停用可為 Null 的內容。
停用可為 Null 的內容時:
- 當未註釋的參考類型的變數初始化或指派值
null時,不會產生警告。 - 當參考類型的變數可能具有空值時,不應產生警告。
- 對於任何參考類型
T,中的?註釋T?都會產生訊息,且類型T?T與 相同。 - 對於任何類型參數限制
where T : C?,中的?註解C?都會產生訊息,且類型C?C與 相同。 - 對於任何類型參數限制
where T : U?,中的?註解U?都會產生訊息,且類型U?U與 相同。 - 泛型限制
class?會產生警告訊息。 type 參數必須是參考類型。附註: 此訊息的特性是「資訊性」,而不是「警告」,以免將它與不相關的可為 Null 警告設定的狀態混淆。 結尾註釋
- 空容恕運算子
!(§12.8.9) 沒有作用。
Example:
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored結束範例
8.9.4.3 可為空的註釋
停用警告旗標且啟用註解旗標時,可為 Null 的內容是 註解。
當可為 Null 的內容是 註釋時:
- 對於任何參考類型
T,中的?註解T?表示T?可為 Null 的類型,而未註釋的T則不可為 Null。 - 不會產生與 Null 可能性相關的診斷警告。
- 空容容操作員
!(§12.8.9) 可能會變更其運算元的分析空值狀態,以及產生的編譯時間診斷警告。
Example:
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings結束範例
8.9.4.4 可為空的警告
當啟用警告旗標且停用註解旗標時,可為 Null 的內容是 警告。
當可為 Null 的內容是 警告時,編譯器可以在下列情況下產生診斷:
- 已判定為 空值的參照變數會取消參照。
- 不可為 Null 類型的參考變數會指派給 可能為 Null 的運算式。
- 用
?來記下可為 Null 的參考類型。 - Null 容錯運算子
!(§12.8.9) 可用來將其運算元的 Null 狀態設定為 不 Null。
Example:
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed結束範例
8.9.4.5 可為空的啟用
當同時啟用警告旗標和註釋旗標時,會 啟用可為 Null 的內容。
啟用可為 Null 的內容時:
- 對於任何參考類型
T,中的?註解T?會造成T?可為 Null 的類型,而未註T解的 Comments 是不可為空的類型。 - 編譯程式可以使用靜態流程分析來判斷任何參考變數的 Null 狀態。 啟用可為 Null 的警告時,參考變數的 Null 狀態 (§8.9.5) 不是 Null、 可能是 Null,或 可能是預設 值和
- Null 容錯運算子
!(§12.8.9) 會將其運算元的 Null 狀態設定為 not Null。 - 如果類型參數的 Null 性不符合其對應型別自變數的 Null 屬性,編譯程式可能會發出警告。
8.9.5 空值可及性和空值狀態
8.9.5.1 一般規定
編譯器不需要執行任何靜態分析,也不需要產生任何與 Null 性相關的診斷警告。
本子條款的其餘部分有條件地具有規範性。
8.9.5.2 流量分析
產生診斷警告的編譯器符合這些規則。
每個運算式都有三個 空值狀態之一:
- may null:運算式的值可能計算為 null。
- may default:運算式的值可能會評估為該類型的預設值。
- not null:運算式的值不是 null。
運算式的 預設 Null 狀態 取決於其類型,以及宣告時註解旗標的狀態:
- 可為 Null 的參考類型的預設 Null 狀態為:
- 當其宣告位於啟用註解旗標的文字中時,可能是 null。
- 當其宣告位於停用註解旗標的文字中時,不為 Null。
- 不可為 Null 的參考類型的預設 Null 狀態不是 Null。
便條:當類型是不可為 Null 的類型時,
string與不受限制的類型參數搭配使用,例如,且運算式default(T)是 Null 值。 因為 null 不在不可為 Null 類型的網域中,所以狀態可能是預設值。 結尾註釋
當初始化不可為 Null 的參考類型的變數 (§9.2.1) 或指派給在啟用註解旗標的文字中宣告該變數時可能為 Null 的運算式時,可以產生診斷。
範例:請考慮下列方法,其中參數可為 Null,且該值會指派給不可為 Null 的類型:
#nullable enable public class C { public void M(string? p) { // Warning: Assignment of maybe null value to non-nullable variable string s = p; } }
編譯程式可能會發出警告,其中可能為 Null 的參數會指派給不應該為 Null 的變數。 在指派前若先檢查參數是否為 Null,編譯器可以利用這點在其可空狀態分析中,不會發出警告。
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // No warning // Use s } } }結束範例
編譯程式可以更新變數的 Null 狀態,做為其分析的一部分。
範例:編譯程式可以選擇根據程式中的任何語句來更新狀態:
#nullable enable public void M(string? p) { int length = p.Length; // Warning: p is maybe null string s = p; // No warning. p is not null if (s != null) { int l2 = s.Length; // No warning. s is not null } int l3 = s.Length; // Warning. s is maybe null }在上述範例中,編譯器可能會決定在語句
int length = p.Length;之後,p的空狀態不是空值。 如果它是 null,則該陳述式會擲出NullReferenceException. 這類似於程式碼前面有 的if (p == null) throw NullReferenceException();行為,只是所寫的程式碼可能會產生警告,其目的是警告可能會隱含擲回例外狀況。 結束範例
在方法的後面,程式碼會檢查是否 s 不是 Null 參考。 在 Null 檢查區塊關閉之後,的 null s 狀態可能會變更為 null。 編譯程式可以推斷 s 可能是 null,因為程式代碼已撰寫來假設它可能是 Null。 一般而言,當程式代碼包含 Null 檢查時,編譯程式可能會推斷值可能是 Null:
範例:下列每個運算式都包含某種形式的 Null 檢查。 在下列每個陳述式之後,的 null
o狀態可以從 not null 變更為 may null:#nullable enable public void M(string s) { int length = s.Length; // No warning. s is not null _ = s == null; // Null check by testing equality. The null state of s // is maybe null length = s.Length; // Warning, and changes the null state of s // to not null _ = s?.Length; // The ?. is a null check and changes the null state of s // to maybe null if (s.Length > 4) // Warning. Changes null state of s to not null { _ = s?[4]; // ?[] is a null check and changes the null state of s // to maybe null _ = s.Length; // Warning. s is maybe null } }
自動屬性和類似欄位的事件宣告都會使用編譯器產生的支援欄位。 空狀態分析可能會推斷事件或屬性的指派是指派給由編譯器生成的後設欄位。
範例:編譯器可以判斷寫入自動屬性或類似欄位的事件會寫入對應的編譯器產生的支援欄位。 屬性的 Null 狀態符合支援欄位的 Null 狀態。
class Test { public string P { get; set; } public Test() {} // Warning. "P" not set to a non-null value. static void Main() { var t = new Test(); int len = t.P.Length; // No warning. Null state is not null. } }在上述範例中,建構函式不會將
P設定為非 Null 值,而且編譯程式可能會發出警告。 存取P屬性時沒有顯示警告,因為該屬性的類型是不可為空值的參考型別。 結束範例
編譯程式可以將屬性 (•15.7) 視為具有狀態的變數,或將屬性視為獨立的 get 和 set 存取子 (\15.7.3)。
範例:編譯程式可以選擇寫入屬性是否會變更讀取屬性的 Null 狀態,或讀取屬性變更該屬性的 Null 狀態。
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; // No warning. A compiler can // assume property is stateful } } }在上一個範例中,讀取時,的
DisappearingProperty支援欄位會設定為 null。 不過,編譯器可能會假設讀取屬性不會變更該運算式的 Null 狀態。 結束範例
編譯程式可以使用取值變數、屬性或事件的任何表達式,將 Null 狀態設定為非 Null。 如果是 null,則取值表達式會拋出 NullReferenceException:
Example:
public class C { private C? child; public void M() { _ = child.child.child; // Warning. Dereference possible null value var greatGrandChild = child.child.child; // No warning. } }結束範例
8.9.5.3 類型轉換
產生診斷警告的編譯器符合這些規則。
便條: 類型中最上層或巢狀 Null 性批註的差異不會影響是否允許類型之間的轉換,因為不可為 Null 的參考類型與其對應的 Null 類型之間沒有語意差異 (§8.9.1) 。 結尾註釋
當轉換縮小時,當可 Null 性註解在兩種類型 (最上層或巢狀) 之間不同時,編譯器可能會發出警告。
範例:頂層註解不同的類型
#nullable enable public class C { public void M1(string p) { _ = (string?)p; // No warning, widening } public void M2(string? p) { _ = (string)p; // Warning, narrowing _ = (string)p!; // No warning, suppressed } }結束範例
範例:巢狀可空性註解不同的類型
#nullable enable public class C { public void M1((string, string) p) { _ = ((string?, string?))p; // No warning, widening } public void M2((string?, string?) p) { _ = ((string, string))p; // Warning, narrowing _ = ((string, string))p!; // No warning, suppressed } }結束範例
編譯器可以遵循介面變異 (§19.2.3.3) 、委派變異 (§21.4) 和陣列共變異數 (§17.6) 的規則,以決定是否發出類型轉換的警告。
#nullable enable public class C { public void M1(IEnumerable<string> p) { IEnumerable<string?> v1 = p; // No warning } public void M2(IEnumerable<string?> p) { IEnumerable<string> v1 = p; // Warning IEnumerable<string> v2 = p!; // No warning } public void M3(Action<string?> p) { Action<string> v1 = p; // No warning } public void M4(Action<string> p) { Action<string?> v1 = p; // Warning Action<string?> v2 = p!; // No warning } public void M5(string[] p) { string?[] v1 = p; // No warning } public void M6(string?[] p) { string[] v1 = p; // Warning string[] v2 = p!; // No warning } }結束範例
當 Null 可值在不允許變體轉換的類型中任一方向不同時,編譯器可能會發出警告。
#nullable enable public class C { public void M1(List<string> p) { List<string?> v1 = p; // Warning List<string?> v2 = p!; // No warning } public void M2(List<string?> p) { List<string> v1 = p; // Warning List<string> v2 = p!; // No warning } }結束範例
有條件規範文本的結尾