轉(zhuǎn)換使表達(dá)式可以當(dāng)做一個(gè)明確的類型來加以處理。轉(zhuǎn)換使得所給定類型的表達(dá)式以不同類型來處理,或使得沒有某個(gè)類型的表達(dá)式獲得該類型。轉(zhuǎn)換可以是顯式或隱式的,而這決定了是否需要顯式地強(qiáng)制轉(zhuǎn)換。比方說,從類型 int
向類型 long
的轉(zhuǎn)換是隱式的,所以 int
類型表達(dá)式可以隱式地當(dāng)做 long
的來處理。反過來轉(zhuǎn)換,從類型 long
轉(zhuǎn)換為 int
是顯式的,需要顯式的強(qiáng)制轉(zhuǎn)換(explicit cast)。
int a = 123;long b = a; // 從 int 到 long 隱式轉(zhuǎn)換int c = (int) b; // 從 long 到 int 顯式轉(zhuǎn)換
某些轉(zhuǎn)換由語言來定義。程序同樣可以定義它們子集的轉(zhuǎn)換(第六章第四節(jié))。
以下轉(zhuǎn)換被分類為隱式轉(zhuǎn)換:
隱式換磚(implicit conversions)可發(fā)生于多種情況下,包括函數(shù)成員調(diào)用(第七章第 5.4 節(jié))、強(qiáng)值轉(zhuǎn)換表達(dá)式(cast exPRessions,第七章第 7.6 節(jié))以及賦值(第七章第十七節(jié))。
預(yù)定義隱式轉(zhuǎn)換(pre-defined implicit conversions)總是成功的,永不會(huì)導(dǎo)致異常拋出。正確設(shè)計(jì)用戶定義隱式轉(zhuǎn)換(user-defined implicit conversions)也能表現(xiàn)(exhibit)出這些特征(characteristics)。
對(duì)于轉(zhuǎn)換的目的來說,object
和 dynamic
類型被視為等價(jià)。
然而,動(dòng)態(tài)轉(zhuǎn)換(dynamic conversions,第六章第 1.8 節(jié)以及第六章第 2.6 節(jié))僅適用于類型為 dynamic
(第四章第七節(jié))的表達(dá)式。
標(biāo)識(shí)轉(zhuǎn)換(identity conversion)可將任意類型轉(zhuǎn)換為其相同類型。這種轉(zhuǎn)換之所以存在,是因?yàn)橐屢咽撬桀愋偷膶?shí)體能被認(rèn)為是可轉(zhuǎn)換(為該類型)的。
由于 object
和 dynamic
被視為等價(jià),所以在 object
和 dynamic
之間以及在即將出現(xiàn)的所有 dynamic
轉(zhuǎn)為 object
的轉(zhuǎn)換具有相同構(gòu)造類型的之間,存在一個(gè)標(biāo)識(shí)轉(zhuǎn)換。
隱式數(shù)值轉(zhuǎn)換(implicit numeric conversions)包括:
sbyte
到 short
、int
、long
、float
、double
或 decimal
;byte
到 short
、ushort
、int
、uint
、long
、ulong
、float
、double
或 decimal
;short
到 int
、long
、float
、double
或 decimal
;ushort
到 int
、uint
、long
、ulong
、float
、double
或 decimal
;int
到 long
、float
、double
或 decimal
;uint
到 long
、ulong
、float
、double
或 decimal
;long
到 float
、double
或 decimal
;ulong
到 float
、double
或 decimal
;char
到 ushort
、int
、uint
、long
、ulong
、float
、double
或 decimal
;float
到 double
。從 int、uint、long 或 ulong 轉(zhuǎn)換為 float,從 long 或 ulong 轉(zhuǎn)換為 double 會(huì)丟失精度(loss of precision),但不會(huì)導(dǎo)致數(shù)量級(jí)的丟失(loss of magnitude)。其它的隱式數(shù)制轉(zhuǎn)換不會(huì)丟失任何信息。
不存在任何向 char
類型的隱式轉(zhuǎn)換,所以任何數(shù)值類型的值都不會(huì)自動(dòng)轉(zhuǎn)換為字符類型。
隱式枚舉轉(zhuǎn)換允許將 decimal-integer-literal 0 轉(zhuǎn)換為任何枚舉類型(enum-type)和任何基礎(chǔ)類型為枚舉類型(enum-type)的非空值類型(nullable-type)。在后一種情況下,這個(gè)轉(zhuǎn)換工作通過轉(zhuǎn)換為基礎(chǔ)枚舉類型并對(duì)結(jié)果進(jìn)行封裝(第四章第 1.10 節(jié))計(jì)算所得。
對(duì)不可為空值類型(non-nullable value types)的預(yù)定義隱式轉(zhuǎn)換操作也可以用于其可空類型的版本上。對(duì)于每個(gè)從非可空值類型 S
到非可空值類型 T
的預(yù)定義隱式標(biāo)識(shí)和數(shù)值轉(zhuǎn)換,都存在如下可空的隱式轉(zhuǎn)換:
S?
到 T?
的隱式轉(zhuǎn)換;S
到 T?
的隱式轉(zhuǎn)換。基于從 S 到 T 的基礎(chǔ)轉(zhuǎn)換來計(jì)算隱式可空轉(zhuǎn)換的過程如下:
S?
到 T?
:T?
類型的 null 值;S?
解包為 S
,然后進(jìn)行 S
到 T
的基礎(chǔ)轉(zhuǎn)換,最后將 T
包裝(第四章第 1.10 節(jié))為 T?
。S
到 T?
,則轉(zhuǎn)換計(jì)算過程為從 S
到 T
的基礎(chǔ)轉(zhuǎn)換,然后從 T
包裝為 T?
。從空值文本(null literal)到可空類型(nullable type)存在隱式轉(zhuǎn)換。這種轉(zhuǎn)換能產(chǎn)生所給定可空類型的空值(null value,第四章第 1.10 節(jié))。
隱式引用轉(zhuǎn)換(implicit reference conversions)是:
reference-type
到 object
或 dynamic
;class-type
S 到任何 class-type
T(前提是 S 是從 T 派生的);class-type
S 到任何 interface-type
T(前提是 S 是 T 的實(shí)現(xiàn))interface-type
S 任何 interface-type
T(前提是 S 是從 T 派生的);array-type
S 到元素類型為 TE 的 array-type
T(前提是下列所有條件均為 true):reference-type
;array-type
到 System.Array
及其實(shí)現(xiàn)的接口;S[]
到 System.Collections.Generic.IList<T>
及其基接口(前提是存在從 S 到 T 的隱式標(biāo)識(shí)或引用轉(zhuǎn)換);delegate-type
到 System.Delegate
及其實(shí)現(xiàn)的接口;reference-type
;reference-type
到 reference-type
T(前提是其具有到 reference-type
T0 的隱式標(biāo)識(shí)或引用轉(zhuǎn)換,且 T0 具有到 T 的標(biāo)識(shí)轉(zhuǎn)換);reference-type
到接口或委托類型 T(前提是具有到接口或委托類型 T0 的隱式標(biāo)識(shí)或引用轉(zhuǎn)換,且 T0 為可變化轉(zhuǎn)換(variance-convertible,第十三章第 1.3.2 節(jié))到 T);隱式引用轉(zhuǎn)換是在 reference-type
之間的轉(zhuǎn)換,可以證明這種轉(zhuǎn)換總能成功,故而不需要在「運(yùn)行時(shí)」對(duì)其進(jìn)行檢查。
引用轉(zhuǎn)換,不管是顯式還是隱式,都不會(huì)改變被轉(zhuǎn)換的對(duì)象的引用標(biāo)識(shí)(referential identity)。換而言之,雖然引用轉(zhuǎn)換可以改變引用的類型,但不會(huì)改變所引用的對(duì)象的類型或值
裝箱轉(zhuǎn)換(boxing conversion)允許值類型隱式轉(zhuǎn)換為引用類型。從任何非可空值類型到 object
和 dynamic
、System.ValueType
以及 non-nullable-value-type
實(shí)現(xiàn)的任何 interface-type
都存在裝箱轉(zhuǎn)換。此外,enum-type
能被轉(zhuǎn)換為 System.Enum
類型。
存在從可空類型到引用類型的裝箱轉(zhuǎn)換,當(dāng)且僅當(dāng)該可空類型的不可空值類型存在向引用類型裝箱轉(zhuǎn)換。
如果值類型具有到接口類型 I0 的裝箱轉(zhuǎn)換,且 I0 具有到接口類型 I 的標(biāo)識(shí)轉(zhuǎn)換,則值類型具有慈寧到 I 的裝箱轉(zhuǎn)換。
如果值類型具有到接口類型或委托類型 I0 的裝箱轉(zhuǎn)換,且 I0 可變化轉(zhuǎn)換(第十三章第 1.3.2 節(jié))為接口類型 I,則值類型具有到接口類型 I 的裝箱轉(zhuǎn)換。
對(duì)非可空值類型的值的裝箱可以包括以下操作:分配一個(gè)對(duì)象實(shí)例,然后將值類型的值復(fù)制進(jìn)該實(shí)例。結(jié)構(gòu)可裝箱為類型 System.ValueType
,因?yàn)樗撬薪Y(jié)構(gòu)的基類型(第十一章第 3.2 節(jié))。
對(duì)非可空類型的值的裝箱按以下步驟進(jìn)行:
關(guān)于裝箱轉(zhuǎn)換的介紹請(qǐng)閱讀第四章第 3.1 節(jié)。
存在從 dynamic 類型表達(dá)式到任何類型 T 的隱式動(dòng)態(tài)轉(zhuǎn)換(implicit dynamic conversion)。轉(zhuǎn)換是動(dòng)態(tài)綁定的(dynamically bound,第七章第 2.2 節(jié)),這意味著在「運(yùn)行時(shí)」能發(fā)現(xiàn)從表達(dá)式的「運(yùn)行時(shí)」類型到類型 T 的隱式轉(zhuǎn)換。如果未發(fā)現(xiàn)任何轉(zhuǎn)換,則拋出「運(yùn)行時(shí)」異常。
注意這種隱式轉(zhuǎn)換似乎違背了第六章第一節(jié)開頭部分的建議,隱式轉(zhuǎn)換不應(yīng)該導(dǎo)致異常。但這不是轉(zhuǎn)換自身導(dǎo)致的異常,而是轉(zhuǎn)換時(shí)動(dòng)詞「發(fā)現(xiàn)」(finding)所導(dǎo)致的異常。「運(yùn)行時(shí)」異常的風(fēng)險(xiǎn)是使用動(dòng)態(tài)綁定所固有的。如果不需要?jiǎng)討B(tài)綁定轉(zhuǎn)化,表達(dá)式首先轉(zhuǎn)換為一個(gè) object,然后轉(zhuǎn)換為所需的類型。
下例說明了隱式動(dòng)態(tài)轉(zhuǎn)換:
object o = “object”dynamic d = “dynamic”;string s1 = o; // 「編譯時(shí)」失敗,不存在轉(zhuǎn)換string s2 = d; // 編譯且「運(yùn)行時(shí)」成功int i = d; // 編譯但「運(yùn)行時(shí)」失敗,不存在轉(zhuǎn)換
對(duì) s2
和 i
的賦值都使用了隱式動(dòng)態(tài)轉(zhuǎn)換(implicit dynamic conversions),所綁定的操作會(huì)一直掛起,直到「運(yùn)行時(shí)(run-time)」才執(zhí)行。在「運(yùn)行時(shí)」,可以看到從 d
的「運(yùn)行時(shí)」類型(string)到目標(biāo)類型的隱式轉(zhuǎn)換。轉(zhuǎn)換會(huì)找到 string 而不是 int。
隱式常量表達(dá)式轉(zhuǎn)換(implicit constant expression conversions)允許以下轉(zhuǎn)換:
int
類型的 constant-expression
(第七章第十九節(jié))能被轉(zhuǎn)換為 sbyte
, byte
, short
, ushort
, uint
或 ulong
,所提供的轉(zhuǎn)換結(jié)果在目標(biāo)類型的合法區(qū)間內(nèi)。long
類型的 constant-expression
能被轉(zhuǎn)換為 ulong
,所提供的結(jié)果為非負(fù)數(shù)。對(duì)于給定類型形參 T 存在下列隱式轉(zhuǎn)換:
如果已知 T 是引用類型(第十章第 1.5 節(jié)),上述轉(zhuǎn)換都被劃歸為隱式引用轉(zhuǎn)換(第六章第 1.6 節(jié))。如果不知道是不是引用類型,則劃歸為裝箱轉(zhuǎn)換(boxing conversions,第六章第 1.7 節(jié)),
用戶定義隱式轉(zhuǎn)換(user-defined implicit conversion)由可選的標(biāo)準(zhǔn)隱式轉(zhuǎn)換(optional standard implicit conversion)、執(zhí)行用戶定義隱式轉(zhuǎn)換的操作符以及另一個(gè)可選的基礎(chǔ)隱式轉(zhuǎn)換等三部分組成。運(yùn)行用戶定義隱式轉(zhuǎn)換的確切規(guī)則詳見第六章第 4.4 節(jié)。
匿名函數(shù)(anonymous function)與方法組(method groups)自身不具有類型(do not have types in and of themselves),然可隱式轉(zhuǎn)換為委托類型或表達(dá)式樹類型。匿名函數(shù)的轉(zhuǎn)換詳見第六章第五節(jié),方法組轉(zhuǎn)換詳見第六章第六節(jié)。
下列轉(zhuǎn)換被劃歸為顯式轉(zhuǎn)換(explicit conversions):
顯式轉(zhuǎn)換可出現(xiàn)在強(qiáng)制轉(zhuǎn)換表達(dá)式(cast expressions,第七章第 7.6 節(jié))內(nèi)。
顯式轉(zhuǎn)換集包括所有隱式轉(zhuǎn)換,這意味著允許使用冗余的強(qiáng)制轉(zhuǎn)換表達(dá)式(redundant cast expressions)。
顯式轉(zhuǎn)換(排除隱式轉(zhuǎn)換的那部分)不能保證總能轉(zhuǎn)換成功,轉(zhuǎn)換可能會(huì)丟失信息,并且轉(zhuǎn)換前后類型顯著不同(sufficiently different)。
顯示數(shù)值轉(zhuǎn)換(explicit numeric conversions)是指從 numeric-type
到另一個(gè) numeric-type
的轉(zhuǎn)換,這種轉(zhuǎn)換不能用已知的隱式數(shù)值轉(zhuǎn)換(第六章第 1.2 節(jié))來實(shí)現(xiàn),它包括:
由于顯式轉(zhuǎn)換包括所有隱式轉(zhuǎn)換和顯式數(shù)值轉(zhuǎn)換,所以其總可以使用強(qiáng)制轉(zhuǎn)換表達(dá)式(cast expression,第七章第 7.6 節(jié))將任意 numeric-type
轉(zhuǎn)換為其它任意 numeric-type
。
顯式數(shù)值轉(zhuǎn)換可能會(huì)丟失信息或?qū)е庐惓伋觥o@式數(shù)值轉(zhuǎn)換的處理過程如下:
System.OverflowException
異常。System.OverflowException
異常。System.OverflowException
異常。System.OverflowException
異常。System.OverflowException
異常。顯式枚舉轉(zhuǎn)換(explicit enumeration conversions)是:
sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
或 decimal
到任何 enum-type
;enum-type
到 sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
或 decimal
;enum-type
到任何其它 enum-type
。在兩個(gè)類型之間進(jìn)行顯式枚舉轉(zhuǎn)換是通過處理任何參與的 enum-type
都按該 enum-type
的基礎(chǔ)類型處理,然后在產(chǎn)生的類型之間使用顯式或隱式的數(shù)值轉(zhuǎn)換。比方說,給定一個(gè) enum-type
E,其基礎(chǔ)類型為 int,從 E 到 byte 的轉(zhuǎn)換會(huì)按從 int 到 byte 的顯式數(shù)值轉(zhuǎn)換(第六章第 2.1 節(jié))來處理,而從 byte 到 E 的轉(zhuǎn)換則會(huì)按從 byte 到 int 的隱式數(shù)值轉(zhuǎn)換(第六章第 1.2 節(jié))來處理。
顯式可空值轉(zhuǎn)換(explicit nullable conversions)允許對(duì)不可空值類型(non-nullable value types)及其可空值形式的類型執(zhí)行預(yù)定義顯式轉(zhuǎn)換。對(duì)于每個(gè)從不可空值類型 S 到不可空值類型 T 的預(yù)定義顯式轉(zhuǎn)換(predefined explicit conversions)(第六章第 1.1 節(jié),1.2 節(jié),1.3 節(jié),2.1 節(jié)以及 2.2 節(jié)),存在下列可空轉(zhuǎn)換:
S?
到 T?
的顯式轉(zhuǎn)換。S
到 T?
的顯式轉(zhuǎn)換。S?
到 T
的顯式轉(zhuǎn)換。基于從 S
到 T
的基礎(chǔ)轉(zhuǎn)換的可空轉(zhuǎn)換運(yùn)算步驟如下:
S?
到 T?
的可空轉(zhuǎn)換:T?
的 null 值。S?
到 S
的解包,然后進(jìn)行從 S
到 T
的基礎(chǔ)轉(zhuǎn)換,最后從 T
包裝為 T?
。S
到 T?
的可空轉(zhuǎn)換,那么轉(zhuǎn)換的運(yùn)算過程是將 S
基礎(chǔ)轉(zhuǎn)換為 T
,然后將 T
包裝為 T?
。S?
到 T
的可空轉(zhuǎn)換,那么轉(zhuǎn)換的運(yùn)算過程是將 S?
解包為 S
,然后從 S
基礎(chǔ)轉(zhuǎn)換為 T
。注意,如果對(duì)一個(gè)為 null 值的可空值進(jìn)行解包會(huì)引發(fā)異常。
顯式引用轉(zhuǎn)換(explicit reference conversions)是:
object
和 dynamic
到任何其它 reference-type
class-type
S 到任何 class-type
T(前提是 S 為 T 的基類)class-type
S 到任何 interface-type
T(前提是 S 不是密封的(sealed)且 S 沒有實(shí)現(xiàn) T)interface-type
S 到任何 class-type
T(前提是 T 不是密封的或 T 實(shí)現(xiàn)了 S)interface-type
S 到任何 interface-type
T(前提是 S 不是派生自 T 的)array-type
S 到元素類型為 TE 的 `array-type
T(前提是以下條件均為 true):System.Array
和它所實(shí)現(xiàn)的接口道任何 array-type
S[]
到 System.Collections.Generic.IList<T>
及其基接口(前提是具有從 S 到 T 的顯式引用轉(zhuǎn)換)System.Collections.Generic.IList<S>
及其基接口到單維度數(shù)組類型 T[]
(前提是具有從 S 到 T 的顯示標(biāo)識(shí)或引用轉(zhuǎn)換)System.Delegate
及其所實(shí)現(xiàn)的接口道任何 delegate-type
顯式引用轉(zhuǎn)換是需要在「運(yùn)行時(shí)」檢查其正確的 reference-type
之間進(jìn)行的轉(zhuǎn)換。
為了顯式引用轉(zhuǎn)換在「運(yùn)行時(shí)」成功,源操作數(shù)的值必須為空(null),或源操作數(shù)所引用對(duì)象的實(shí)際類型必須是一個(gè)能通過隱式引用轉(zhuǎn)換(第六章第 1.6 節(jié))或裝箱轉(zhuǎn)換(第六章第 1.7 節(jié))轉(zhuǎn)換為目標(biāo)類型的類型。如果顯式引用轉(zhuǎn)換失敗,會(huì)拋出 System.InvalidCastException
異常。
引用轉(zhuǎn)換,無論是顯示還是隱式,都不會(huì)改變被轉(zhuǎn)換對(duì)象的引用標(biāo)識(shí)(referential identity)。換句話說,雖然引用轉(zhuǎn)換可以改變所引用的類型,但從不會(huì)改變所引用對(duì)象的類型或值。
拆箱轉(zhuǎn)換(unboxing conversion)引用類型顯式轉(zhuǎn)換為值類型。存在從類型 object
、dynamic
和 System.ValueType
到任何 non-nullable-value-type
以及從任何 interface-type
到任何實(shí)現(xiàn) interface-type
的 non-nullable-value-type
的拆箱轉(zhuǎn)換。而且,類型 System.Enum
可以拆箱為任何 enum-type
。
存在從引用類型到 nullable-type
的拆箱轉(zhuǎn)換,前提是存在從該引用類型到 nullable-type
的基礎(chǔ)類型 non-nullable-value-type
的拆箱轉(zhuǎn)換。
如果值類型 S
具有從接口類型 I
的拆箱轉(zhuǎn)換,且 I0 具有從接口類型到 I 的標(biāo)識(shí)轉(zhuǎn)換,則其具有來自 I 的拆箱轉(zhuǎn)換。
如果值類型 S
具有來自接口類型或委托類型 I0 的拆箱轉(zhuǎn)換,且 I0 可變化轉(zhuǎn)換為 I 或 I 可變換轉(zhuǎn)換為 I0(第十三章第 1.3.2 節(jié)),則其具有來自 I 的拆箱轉(zhuǎn)換。
拆箱操作包括以下步驟:一是檢查對(duì)象實(shí)例是否為給定值類型的裝箱值,二是將該值復(fù)制出該實(shí)例。對(duì)一個(gè) nullable-type
類型的 null 引用將產(chǎn)生該類型的 null 值。結(jié)構(gòu)可以從類型 System.ValueType
拆箱,因?yàn)樵擃愋褪撬薪Y(jié)構(gòu)的基類(第十一章第 3.2 節(jié))。
更多拆箱轉(zhuǎn)換的介紹可查看第四章第 3.2 節(jié)。
存在從 dynamic 到任意類型 T 的顯式動(dòng)態(tài)轉(zhuǎn)換(explicit dynamic conversion)。轉(zhuǎn)換時(shí)動(dòng)態(tài)綁定(第七章第 2.2 節(jié))的,這意味著在「運(yùn)行時(shí)」時(shí),可以看到從表達(dá)式的「運(yùn)行時(shí)」類型到 T 的顯式轉(zhuǎn)換。如果沒有轉(zhuǎn)換發(fā)生,那么將拋出「運(yùn)行時(shí)」異常。
如果轉(zhuǎn)換不需要?jiǎng)討B(tài)綁定,表達(dá)式可以先轉(zhuǎn)換為 object,然后轉(zhuǎn)為所需類型。
如下例所定義:
class C{ int i; public C(int i) { this.i = i; } public static explicit Operator C(string s) { return new C(int.Parse(s)); }}
下例列舉了顯式動(dòng)態(tài)轉(zhuǎn)換:
object o = "1";dynamic d = "2";var c1 = (C)o; // 編譯,但顯式引用轉(zhuǎn)換失敗var c2 = (C)d; // 編譯,用戶定義轉(zhuǎn)換成功
從 o
到 C
的最佳轉(zhuǎn)換發(fā)生于「編譯時(shí)」的顯式引用轉(zhuǎn)換。這在「運(yùn)行時(shí)」失敗,是因?yàn)?1
實(shí)際上不是 C。然而,從 d
到 C
的轉(zhuǎn)換作為顯式動(dòng)態(tài)轉(zhuǎn)換(explicit dynamic conversion),在「運(yùn)行時(shí)」之初一直被掛起,從 d
的「運(yùn)行時(shí)」類型——string——到 c
的用戶定義轉(zhuǎn)換出現(xiàn)并成功。
對(duì)于給定的類型形參 T 存在下列顯式轉(zhuǎn)換:
interface-type
I(前提條件是目前尚未存在從 T 到 I 的隱式轉(zhuǎn)換)。在「運(yùn)行時(shí)」,如果 T 為值類型,則其轉(zhuǎn)換將執(zhí)行以先裝箱轉(zhuǎn)換、爾后顯式引用轉(zhuǎn)換。不然,其轉(zhuǎn)換將執(zhí)行以顯式引用轉(zhuǎn)換或標(biāo)識(shí)轉(zhuǎn)換。如果已知 T 為引用類型,則上述轉(zhuǎn)換將盡數(shù)歸類為顯式引用轉(zhuǎn)換(第六章第 2.4 節(jié))。如果已知 T 不是引用類型,則上述轉(zhuǎn)換盡數(shù)歸類為拆箱轉(zhuǎn)換(第六章第 2.5 節(jié))。
上述規(guī)則不允許從未受約束的類型形參直接顯式轉(zhuǎn)換為非接口類型(non-interface type),其目的是為了避免混淆,并使轉(zhuǎn)換語義清晰。如下例所聲明:
class X<T>{ public static long F(T t) { return (long)t; // Error }}
如果允許從 t
直接轉(zhuǎn)換為 int,極有可能會(huì)認(rèn)為 x<int>.F(7)
將返回 7L
。然而并不是如此,因?yàn)閮H當(dāng)綁定時(shí)(binding-time)已知類型為數(shù)字類型時(shí),才會(huì)考慮標(biāo)準(zhǔn)的數(shù)字轉(zhuǎn)換(standard numeric conversions)。為了語義清晰,上例必須這樣寫:
class X<T>{ public static long F(T t) { return (long)(object)t; // Ok, but will only work when T is long }}
這段代碼現(xiàn)在能夠編譯,但當(dāng)「運(yùn)行時(shí)」執(zhí)行 X<int>.F(7)
時(shí)會(huì)拋出異常,這是因?yàn)椴荒軐⒁蜒b箱的 int
直接轉(zhuǎn)換為 long
。
用戶定義顯式轉(zhuǎn)換(user-defined explicit conversion)包括以下三部分:可選的標(biāo)準(zhǔn)顯式轉(zhuǎn)換、執(zhí)行用戶定義的隱式或顯式轉(zhuǎn)換操作、另一個(gè)可選的基礎(chǔ)顯式轉(zhuǎn)換。關(guān)于計(jì)算用戶定義顯式轉(zhuǎn)換的確切規(guī)則詳見第六章第 4.5 節(jié)。
標(biāo)準(zhǔn)轉(zhuǎn)換(standard conversions)是作為用戶定義轉(zhuǎn)換(user-defined conversion)的一部分出現(xiàn)的預(yù)定義轉(zhuǎn)換(pre-defined conversions)
下列隱式轉(zhuǎn)換被劃入標(biāo)準(zhǔn)隱式轉(zhuǎn)換(standard implicit conversions):
基礎(chǔ)隱式轉(zhuǎn)換不包括用戶定義隱式轉(zhuǎn)換(user-defined implicit conversions)。
標(biāo)準(zhǔn)顯式轉(zhuǎn)換(standard explicit conversions)包括所有基礎(chǔ)隱式轉(zhuǎn)換,以及由那些與已知的標(biāo)準(zhǔn)隱式轉(zhuǎn)換反向的轉(zhuǎn)換的子集(subset)所組成。換句話說,如果標(biāo)準(zhǔn)隱式轉(zhuǎn)換存在從 A 到 B 的轉(zhuǎn)換,那么標(biāo)準(zhǔn)顯示轉(zhuǎn)換就存在了從 A 到 B 以及從 B 到 A 的轉(zhuǎn)換。
C# 允許由用戶定義轉(zhuǎn)換(user-defined conversions)所擴(kuò)展的預(yù)定義隱式與顯示轉(zhuǎn)換。用戶定義轉(zhuǎn)換是通過在類類型與結(jié)構(gòu)類型中聲明轉(zhuǎn)換運(yùn)算符(conversion operators,第十章第 10.3 節(jié))而引入的。
C# 只允許某些用戶定義轉(zhuǎn)換的聲明。具體來說,不可重定義已存在的隱式或顯式轉(zhuǎn)換。
對(duì)于給定源類型 S 和目標(biāo)類型 T,如果 S 或 T 均為可空類型,設(shè) S0 及 T0 引用其基礎(chǔ)類型,否則 S0 及 T0 分別等于 S 與 T。只有當(dāng)以下條件為真時(shí),類型或結(jié)構(gòu)允許聲明從源類型 S 到目標(biāo)類型 T 的轉(zhuǎn)換:
interface-type
適用于用戶定義轉(zhuǎn)換的限制將在第十章第 10.3 節(jié)中進(jìn)一步討論。
給定一個(gè)從 non-nullable
值類型 S 到 non-nullable
值類型 T 的用戶定義轉(zhuǎn)換運(yùn)算符,存在從 S? 到 T? 的提升轉(zhuǎn)換操作符(lifted conversion operator)。提升轉(zhuǎn)換操作符執(zhí)行從 S? 到 S 的解包、然后是從 S 到 T 的用戶定義轉(zhuǎn)換,接著是從 T 到 T? 的包裝(null 值 S? 直接轉(zhuǎn)換為 null 值 T? 的除外)。
提升轉(zhuǎn)換操作符與其基礎(chǔ)用戶定義轉(zhuǎn)換運(yùn)算符具有相同的顯式或隱式類別。術(shù)語「用戶定義轉(zhuǎn)換(user-defined conversion)」適用于用戶定義轉(zhuǎn)換運(yùn)算符與提升轉(zhuǎn)換操作符的使用。
用戶定義轉(zhuǎn)換將一個(gè)值從其所屬之類型(即「源類型 source type」)e
轉(zhuǎn)換為另一個(gè)類型(即「目標(biāo)類型 target type」)。用戶定義轉(zhuǎn)換的運(yùn)算集中于查找符合特定源類型與目標(biāo)類型的最精確的用戶定義轉(zhuǎn)換符。次確定過程分為以下部分:
nullable-type
,則改為使用其基礎(chǔ)類型。當(dāng)最精準(zhǔn)用戶定義轉(zhuǎn)換操作符被明確了之后,確切的執(zhí)行步驟分為三步:
用戶定義轉(zhuǎn)換的執(zhí)行不會(huì)調(diào)用另一個(gè)用戶定義轉(zhuǎn)換或提升轉(zhuǎn)換操作符。換句話來說,從類型 S 到類型 T 的轉(zhuǎn)換將不會(huì)首選調(diào)用從 S 到 X 的用戶定義轉(zhuǎn)換,而后再調(diào)用從類型 X 到類型 T 的用戶定義轉(zhuǎn)換。
用戶定義轉(zhuǎn)換或顯式轉(zhuǎn)換的確切定義將在后述章節(jié)給出。這些定義使用以下術(shù)語:
interface-types
,那么我們可以說 A 被 B 包含(be encompassed by),或者說 B 包含 A(encompass)。從類型 S 到類型 T 的用戶定義隱式轉(zhuǎn)換(user-defined implicit conversion)的處理過程如下:
從類型 S 到類型 T 的用戶定義顯式轉(zhuǎn)換(user-defined explicit conversion)的過程如下:
anonymous-method-expression
或 lambda-expression
都被歸類到了匿名函數(shù)(anonymous function,第七章第十五節(jié))。此表達(dá)式不具有類型,但可隱式轉(zhuǎn)換為委托類型或表達(dá)式樹類型。具體而言,匿名函數(shù) F 可以與委托類型 D 兼容(compatible with):
anonymous-function-signature
,則 D 與 F 具有相同數(shù)量的形參個(gè)數(shù)。anonymous-function-signature
,那則 D 可以具有零或多個(gè)任意類型的形參,但 D 的任何形參都沒有 out
參數(shù)修飾符。ref
與 out
形參。Task
,則當(dāng) F 中每一個(gè)形參都被給定為 D 中對(duì)應(yīng)參數(shù)的類型時(shí),F(xiàn) 的主體是有效表達(dá)式(valid expression,請(qǐng)參考第七章),該表達(dá)式將允許作為 statement-expression
(第八章第六節(jié))。Task
,則將 F 中每一個(gè)形參都被給定為 D 中對(duì)應(yīng)參數(shù)的類型時(shí),F(xiàn) 的主體是有效語句塊(valid statement block,請(qǐng)參考第八章第二節(jié)),該語句塊沒有 return
語句指定了表達(dá)式。下文使用任務(wù)類型的簡(jiǎn)寫 Task
和 Task<T>
(第十章第十四節(jié))。
如果 F 能與委托類型 D 兼容,則 Lambda 表達(dá)式 F 能與表達(dá)式樹類型 Expression<D>
兼容。注意,此處不適用于匿名方法,而僅適用于 Lambda 表達(dá)式。
某些 Lambda 表達(dá)式不能被轉(zhuǎn)換為表達(dá)式樹類型——即使存在轉(zhuǎn)換,該過程也會(huì)在「編譯時(shí)」失敗。這種情況會(huì)發(fā)生在符合以下條件的時(shí)候:
在下面例子中使用了泛型委托類型 Func<A, R>
,該函數(shù)采用一個(gè)類型為 A 的實(shí)參并返回類型為 R 的值:
delegate R Func<A,R>(A arg);
在下面賦值中,
Func<int,int> f1 = x => x + 1; // OkFunc<int,double> f2 = x => x + 1; // OkFunc<double,int> f3 = x => x + 1; // 錯(cuò)誤Func<int, Task<int>> f4 = async x => x + 1; // Ok
每一個(gè)匿名函數(shù)的形參和返回值類型都由匿名函數(shù)所賦予的變量的類型來確定。
第一個(gè)賦值成功地把匿名函數(shù)轉(zhuǎn)換為委托類型 Func<int, int>
,因?yàn)楫?dāng) x
指定的類型是 int 的時(shí)候,x + 1
是一個(gè)可以隱式轉(zhuǎn)換為 int 類型的有效表達(dá)式。
同樣地,第二個(gè)賦值成功地把匿名函數(shù)轉(zhuǎn)換為委托類型 Func<int, double>
,這是因?yàn)?x + 1
的結(jié)果(類型為 int)可以隱式轉(zhuǎn)換為類型 double。
然而,第三個(gè)賦值會(huì)出現(xiàn)「編譯時(shí)錯(cuò)誤」,這是因?yàn)?x
給定的是 double 類型,x + 1
的結(jié)果(類型為 double)不能隱式轉(zhuǎn)換為 int。
第四個(gè)賦值成功地把匿名異步函數(shù)轉(zhuǎn)換為委托類型 Func<int, Task<int>>
,這是因?yàn)?x + 1
的結(jié)果(類型為 int)可以隱式轉(zhuǎn)換為任務(wù)類型 Task<int>
的結(jié)果類型 int。
匿名函數(shù)可能會(huì)影響重載策略(overload resolution),并參與類型推斷(type inference),關(guān)于這一點(diǎn)請(qǐng)參見第七章第五節(jié)。
匿名函數(shù)到委托類型的轉(zhuǎn)換將產(chǎn)生委托實(shí)例,這個(gè)委托實(shí)例引用匿名函數(shù)以及被捕獲的處于活動(dòng)狀態(tài)的外部變量的集合(可以為空)。當(dāng)委托被調(diào)用,將執(zhí)行匿名函數(shù)體。使用委托所引用的被捕獲的外部變量集執(zhí)行方法主體中的代碼。
自匿名函數(shù)所產(chǎn)生的委托的調(diào)用列表只含一項(xiàng),確切目標(biāo)對(duì)象與委托的目標(biāo)方法并未被明確指定。具體來說,沒有具體指定該委托的目標(biāo)對(duì)象是空(null)、所閉包的函數(shù)成員的 this
值,還是其它某個(gè)對(duì)象。
將具有相同的被捕獲外層變量實(shí)例集(可能為空集)的語義上相同的匿名函數(shù)轉(zhuǎn)換到委托類型,允許(但不要求)返回相同的委托實(shí)例。術(shù)語「語義上相同」表示在任何情況下,只要給定相同的參數(shù),匿名函數(shù)的執(zhí)行就會(huì)產(chǎn)生相同的結(jié)果。這條規(guī)定允許優(yōu)化如下面這樣的代碼:
delegate double Function(double x);class Test{ static double[] Apply(double[] a, Function f) { double[] result = new double[a.Length]; for (int i = 0; i < a.Length; i++) result[i] = f(a[i]); return result; } static void F(double[] a, double[] b) { a = Apply(a, (double x) => Math.Sin(x)); b = Apply(b, (double y) => Math.Sin(y)); ... }}
由于兩個(gè)匿名函數(shù)委托存在相同的(空集)被捕獲外層變量集,且這兩個(gè)匿名函數(shù)語義上相同,所以編譯器被允許使用這兩個(gè)委托引用同一個(gè)目標(biāo)方法。實(shí)際上,編譯器甚至被允許從這兩個(gè)匿名函數(shù)表達(dá)式返回同一個(gè)委托實(shí)例(delegate instance)。
將匿名函數(shù)轉(zhuǎn)換為表達(dá)式樹類型會(huì)產(chǎn)生一個(gè)表達(dá)式樹(expression tree,第四章第六節(jié))。準(zhǔn)確地講,匿名函數(shù)轉(zhuǎn)換計(jì)算會(huì)導(dǎo)致構(gòu)造對(duì)象結(jié)構(gòu)——表示匿名函數(shù)本身的結(jié)構(gòu)。表達(dá)式樹的精確結(jié)構(gòu)以及創(chuàng)建該目錄樹的確切過程為定義的實(shí)現(xiàn)(implementation defined)。
本節(jié)從其它 C# 構(gòu)造的角度描述可能的匿名函數(shù)轉(zhuǎn)換實(shí)現(xiàn)方法。此處所描述的實(shí)現(xiàn)基于 Microsoft C# 編譯器 所使用的相同原理(same principles),但這不意味著強(qiáng)制實(shí)現(xiàn),也不是唯一實(shí)現(xiàn)方式。此處僅簡(jiǎn)單介紹到表達(dá)式樹的轉(zhuǎn)換,因?yàn)樗鼈兊臉?biāo)準(zhǔn)語義超出了本規(guī)范的大綱范圍。
本節(jié)其余部分舉了幾個(gè)不同特點(diǎn)匿名函數(shù)的例子。在每個(gè)例子中,提供了到僅使用其他 C# 構(gòu)造的代碼的相應(yīng)轉(zhuǎn)換。在這些例子中,設(shè)標(biāo)識(shí)符 D
表示下面委托類型:
public delegate void D();
匿名函數(shù)的最簡(jiǎn)形式是不捕獲外層變量(outer variables)的形式:
class Test{ static void F() { D d = () => { Console.WriteLine("test"); }; }}
這可以轉(zhuǎn)換為引用編譯器生成的靜態(tài)方法,而匿名方法就在該靜態(tài)方法內(nèi):
class Test{ static void F() { D d = new D(__Method1); } static void __Method1() { Console.WriteLine("test"); }}
在下例中,匿名函數(shù)引用 this
實(shí)例成員:
class Test{ int x; void F() { D d = () => { Console.WriteLine(x); }; }}
這可以轉(zhuǎn)換為編譯器生成的實(shí)例方法(包含該匿名方法的代碼):
class Test{ int x; void F() { D d = new D(__Method1); } void __Method1() { Console.WriteLine(x); }}
在這個(gè)例子中,匿名函數(shù)捕獲一個(gè)本地局部變量:
class Test{ void F() { int y = 123; D d = () => { Console.WriteLine(y); }; }}
局部變量的生命周期現(xiàn)在至少被延長(zhǎng)到匿名函數(shù)委托的生命周期。這可以通過將局部變量「提升」到編譯器生成的類的字段來實(shí)現(xiàn)。局部變量的實(shí)例化(第七章第 15.2 節(jié))將對(duì)應(yīng)為編譯器生成的類創(chuàng)建實(shí)例,且訪問局部變量則對(duì)應(yīng)訪問編譯器生成的類的實(shí)例中的字段。而且匿名函數(shù)成為編譯器生成的類的實(shí)例方法:
class Test{ void F() { __Locals1 __locals1 = new __Locals1(); __locals1.y = 123; D d = new D(__locals1.__Method1); } class __Locals1 { public int y; public void __Method1() { Console.WriteLine(y); } }}
最后,下面這個(gè)匿名函數(shù)捕獲 this
以及兩個(gè)具有不同生命周期的局部變量:
class Test{ int x; void F() { int y = 123; for (int i = 0; i < 10; i++) { int z = i * 2; D d = () => { Console.WriteLine(x + y + z); }; } }}
這里,編譯器將對(duì)所捕獲的局部變量的每一個(gè)語句塊創(chuàng)建了一個(gè)類,這樣不同語句塊中的局部變量將具有獨(dú)立的生命周期。__Locals2
的實(shí)例——編譯器為內(nèi)部語句塊創(chuàng)建的類——包含局部變量 z
以及引用 __Locals1
的實(shí)例字段。__Locals1
的實(shí)例——編譯器為外部語句塊創(chuàng)建的類——包含局部變量 y
以及引用包容函數(shù)成員 this
的字段。對(duì)于這些數(shù)據(jù)結(jié)構(gòu),可以通過 __Locals2
的實(shí)例來獲得所有被捕獲的外層變量,匿名函數(shù)的代碼從而可以實(shí)現(xiàn)為該類的實(shí)例方法。
class Test{ void F() { __Locals1 __locals1 = new __Locals1(); __locals1.__this = this; __locals1.y = 123; for (int i = 0; i < 10; i++) { __Locals2 __locals2 = new __Locals2(); __locals2.__locals1 = __locals1; __locals2.z = i * 2; D d = new D(__locals2.__Method1); } } class __Locals1 { public Test __this; public int y; } class __Locals2 { public __Locals1 __locals1; public int z; public void __Method1() { Console.WriteLine(__locals1.__this.x + __locals1.y + z); } }}
此處用于捕獲局部變量的技術(shù)也可以用于將匿名函數(shù)轉(zhuǎn)換為表達(dá)式樹:對(duì)變意義所創(chuàng)建的對(duì)象的引用能存儲(chǔ)在表達(dá)式樹中,并對(duì)局部變量的訪問可以表示為對(duì)這些對(duì)象的字段的訪問。這種方法(approach)的優(yōu)勢(shì)在于允許「提升的(lifted)」局部變量在委托和表達(dá)式樹之間共享。
存在從方法組(method group,第七章第一節(jié))到兼容委托類型的隱式轉(zhuǎn)換(第六章第一節(jié))。對(duì)于給定的委托類型 D 以及歸類為方法組的表達(dá)式 E,如果 E 包含至少一個(gè)能以其正常形式(第七章第 5.3.1 節(jié))應(yīng)用于使用 D 的形參類型與修飾符構(gòu)造的實(shí)參列表的方法,則存在從 E 到 D 的隱式轉(zhuǎn)換。具體為:
從方法組 E 到委托類型 D 的轉(zhuǎn)換時(shí)「編譯時(shí)」應(yīng)用在下面的部分中描述。注意,存在從 E 到 D 的隱式轉(zhuǎn)換并不保證轉(zhuǎn)換產(chǎn)生的「編譯時(shí)」應(yīng)用會(huì)不帶錯(cuò)誤地成功。
E(A)
形式的方法調(diào)用(method invocation,第七章第 6.5.1 節(jié)),僅選擇單個(gè)方法 M,并進(jìn)行下列變更(modifications):formal-parameter-list
的形參對(duì)應(yīng)的類型與修飾符(ref 或 out)。注意,以下情況中,此過程可能會(huì)導(dǎo)致所創(chuàng)建到擴(kuò)展方法(extension method)的委托:第七章第 6.5.1 節(jié)的算法未能找到實(shí)例方法,但在以擴(kuò)展方法調(diào)用(第七章第 6.5.2 節(jié))的形式處理 E(A)
的調(diào)用時(shí)卻取得成功。故而創(chuàng)建委托將捕獲該方法的第一個(gè)實(shí)參。
下面例子展示了方法組的轉(zhuǎn)換:
delegate string D1(object o);delegate object D2(string s);delegate object D3();delegate string D4(object o, params object[] a);delegate string D5(int i);class Test{ static string F(object o) {...} static void G() { D1 d1 = F; // Ok D2 d2 = F; // Ok D3 d3 = F; // Error – not applicable D4 d4 = F; // Error – not applicable in normal form D5 d5 = F; // Error – applicable but not compatible }}
對(duì)于 d1 的賦值隱式地將方法組 F 轉(zhuǎn)換為 D1 類型的值。
對(duì)于 d2 的賦值展示如何創(chuàng)建到具有派生程度較小(逆變)的形參類型和派生程度較大(協(xié)變)的返回類型的方法的委托。
對(duì)于 d3 的賦值展示當(dāng)方法不適用時(shí)如何不不存在轉(zhuǎn)換。
對(duì)于 d4 的賦值展示方法如何必須以其正常形式應(yīng)用。
對(duì)于 d5 的賦值展示如何允許委托和方法的形參與返回值類型僅對(duì)引用類型不同。
與其它隱式和顯式轉(zhuǎn)換一樣,強(qiáng)制轉(zhuǎn)換操作符可以用于顯式執(zhí)行方法組轉(zhuǎn)換。因此下面這個(gè)例子
object obj = new EventHandler(myDialog.OkClick);
可以寫成
object obj = (EventHandler)myDialog.OkClick;
方法組可能影響重載策略,并參與類型推斷,具體參見第七章第五節(jié)。
方法組轉(zhuǎn)換的「運(yùn)行時(shí)」運(yùn)算如下所述:
reference-type
,則由實(shí)例表達(dá)式運(yùn)算所得的值將為目標(biāo)對(duì)象。如果所選的方法是實(shí)例方法且目標(biāo)對(duì)象為空(null),則拋出 System.NullReferenceException
異常并不再執(zhí)行后續(xù)步驟。value-type
,則執(zhí)行裝箱操作(boxing operation,第四章第 3.1 節(jié))以將值變?yōu)閷?duì)象,然后成為目標(biāo)對(duì)象。System.OutOfMemoryException
異常,并不執(zhí)行接下來的步驟。新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注