10 個轉換
10.1 一般
轉換會導致將表達式轉換成或視為特定型別的 ,在先前的案例中,轉換可能會涉及表示法的變更。 轉換可以是隱含或明確,這可決定是否需要明確轉換。
範例:例如,從型
int
別轉換成型long
別的轉換是隱含的,因此型別的int
表達式可以隱含地視為類型long
。 從型long
別到型int
別的相反轉換是明確的,因此需要明確的轉換。int a = 123; long b = a; // implicit conversion from int to long int c = (int) b; // explicit conversion from long to int
end 範例
某些轉換是由語言所定義。 程式也可以定義自己的轉換(~10.5)。
語言中的某些轉換是從表達式定義為類型,其他則是從類型到類型。 從類型轉換會套用至具有該類型的所有表達式。
範例:
enum Color { Red, Blue, Green } // The expression 0 converts implicitly to enum types Color c0 = 0; // Other int expressions need explicit conversion Color c1 = (Color)1; // Conversion from null expression (no type) to string string x = null; // Conversion from lambda expression to delegate type Func<int, int> square = x => x * x;
end 範例
10.2 隱含轉換
10.2.1 一般
下列轉換會分類為隱含轉換:
- 身分識別轉換 (~10.2.2)
- 隱含數值轉換(~10.2.3)
- 隱含列舉轉換(~10.2.4)
- 隱含插補字串轉換 (~10.2.5)
- 隱含參考轉換(~10.2.8)
- 拳擊轉換 (\10.2.9)
- 隱含動態轉換 (~10.2.10)
- 隱含類型參數轉換(\10.2.12)
- 隱含常數表示式轉換(•10.2.11)
- 使用者定義 (包括提升) 隱含轉換 (•10.2.14)
- 匿名函式轉換(\10.2.15)
- 方法群組轉換 (\10.2.15)
- Null 常值轉換 (~10.2.7)
- 隱含可為 Null 的轉換 (~10.2.6)
- 隱含 Tuple 轉換(•10.2.13)
- 預設常值轉換 (\10.2.16)
- 隱含擲回轉換(•10.2.17)
隱含轉換可能發生在各種情況中,包括函式成員調用(•12.6.6)、轉換表達式(~12.9.7)和指派(~12.21)。
預先定義的隱含轉換一律會成功,而且永遠不會擲回例外狀況。
注意:適當設計的使用者定義隱含轉換也應該顯示這些特性。 end note
為了轉換的目的,型 object
別和 dynamic
是可轉換的身分識別 (~10.2.2)。
不過,動態轉換 (~10.2.10) 僅適用於類型 dynamic
為 (~8.2.4) 的運算式。
10.2.2 身分識別轉換
身分識別轉換會從任何類型轉換成相同類型,或運行時間相等的類型。 此轉換存在的其中一個原因是類型 T
或類型的 T
表達式可以說可以轉換成 T
本身。 下列身分識別轉換存在:
- 在與
T
之間T
,針對任何類型T
。 - 在與
T?
之間T
,用於任何參考型別T
。 - 在和
dynamic
之間object
。 - 在具有相同arity的所有 Tuple 類型與對應的建構
ValueTuple<...>
型別之間,當每個對應的專案類型組之間存在識別轉換時。 - 從相同泛型型別建構的類型之間,每個對應型別自變數之間都有識別轉換。
範例:下列說明第三個規則的遞歸本質:
(int a , string b) t1 = (1, "two"); (int c, string d) t2 = (3, "four"); // Identity conversions exist between // the types of t1, t2, and t3. var t3 = (5, "six"); t3 = t2; t2 = t1; var t4 = (t1, 7); var t5 = (t2, 8); // Identity conversions exist between // the types of t4, t5, and t6. var t6 =((8, "eight"), 9); t6 = t5; t5 = t4;
Tuple
t1
的類型,t2
而且t3
都有兩個元素:int
後面接著 。string
Tuple 元素類型本身可能由 Tuple,如 、t5
和t6
中t4
所示。 每個對應的元素類型之間都有識別轉換,包括巢狀元組,因此元組t4
t5
、 和t6
的類型之間存在識別轉換。end 範例
所有身分識別轉換都是對稱的。 如果身分識別轉換存在 至 T₁
T₂
,則識別轉換會從 T₂
到 T₁
。 當識別轉換存在於兩種類型之間時,兩種類型都是 可轉換 的身分識別。
在大部分情況下,身分識別轉換在運行時間沒有作用。 不過,由於浮點運算的精確度可能高於其類型所指定的有效位數(~8.3.7),因此其結果指派可能會導致精確度喪失,而且保證明確轉換能夠將精確度降低到類型所規定的內容(12.9.7)。
10.2.3 隱含數值轉換
隱含數值轉換如下:
- 從
sbyte
到short
、、long
int
、float
、double
或decimal
。 - 從
byte
到short
、ushort
、int
、、uint
、ulong
long
、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
、 long
uint
或 ulong
到 float
或 long
ulong
double
的轉換可能會導致精確度遺失,但永遠不會造成大小損失。 其他隱含數值轉換永遠不會遺失任何資訊。
沒有預先定義的隱含轉換類型 char
,因此其他整數型別的值不會自動轉換成型別 char
。
10.2.4 隱含列舉轉換
隱含列舉轉換允許任何整數型別的constant_expression (12.23)和值零轉換成任何enum_type,以及任何基礎類型為enum_type的nullable_value_type。 在後一種情況下,轉換會透過轉換成基礎 enum_type 並包裝結果來評估轉換(~8.3.12)。
10.2.5 隱含插補字串轉換
隱含插補字串轉換允許 將 interpolated_string_expression (12.8.3) 轉換成 System.IFormattable
或 System.FormattableString
(實作 System.IFormattable
)。
套用此轉換時,字串值不會從插補字串組成。 相反地,會建立的System.FormattableString
實例,如 •12.8.3 中所述。
10.2.6 隱含可為 Null 的轉換
隱含可為 Null 的轉換是衍生自隱含預先定義轉換的可為 Null 轉換 (~10.6.1)。
10.2.7 Null 常值轉換
從常值到任何參考型別或可為 Null 的實值型別,都存在 null
隱含轉換。 如果目標類型是參考型別,或指定可為 Null 的實值型別的 Null 值(~8.3.12),則此轉換會產生 Null 參考。
10.2.8 隱含參考轉換
隱含參考轉換如下:
- 從任何 reference_type 到
object
和dynamic
。 - 從任何class_type
S
到任何class_typeT
,提供的S
衍生自T
。 - 從任何class_type
S
到任何interface_typeT
,提供的S
會實作T
。 - 從任何interface_type
S
到任何interface_typeT
,提供的S
衍生自T
。 - 從具有專案類型的array_type
S
到具有元素類型的Sᵢ
Tᵢ
array_typeT
,前提是下列所有專案都成立:S
和T
只有在項目類型中才不同。 換句話說,S
和T
的維度數目相同。- 隱含參考轉換從 到
Tᵢ
。Sᵢ
- 從單一維度陣列類型
S[]
到System.Collections.Generic.IList<T>
、System.Collections.Generic.IReadOnlyList<T>
及其基底介面,前提是有從S
到T
的隱含識別或參考轉換。 - 從任何 array_type 到
System.Array
,以及其實作的介面。 - 從任何 delegate_type 到
System.Delegate
,以及其實作的介面。 - 從 Null 常值 (~6.4.5.7) 到任何引用類型。
- 如果reference_type具有隱含的身分識別或參考轉換至reference_type,且識別轉換為
T
,則從任何reference_type到reference_type。T
T₀
T₀
- 如果介面或委派型別具有對接口或委派類型的隱含識別或參考轉換,且可轉換變異數(18.2.3.3.3),則從任何reference_type到介面或委派類型
T
T₀
。T₀
T
- 隱含轉換涉及已知為參考型別的類型參數。 如需涉及類型參數之隱含轉換的詳細資訊,請參閱 <10.2.12 >。
隱含參考轉換是reference_type之間的轉換,可證明一律成功,因此在運行時間不需要檢查。
參考轉換、隱含或明確,絕不會變更所轉換物件的引用識別。
注意:換句話說,雖然參考轉換可以變更參考的類型,但絕不會變更所參考對象的類型或值。 end note
10.2.9 Boxing 轉換
Boxing 轉換允許 將value_type 隱含轉換成 reference_type。 下列 Boxing 轉換存在:
- 從任何 value_type 到 型別
object
。 - 從任何 value_type 到 型別
System.ValueType
。 - 從任何 enum_type 到型別
System.Enum
。 - 從任何non_nullable_value_type到non_nullable_value_type所實作的任何interface_type。
- 從任何non_nullable_value_type轉換成任何interface_type
I
,讓boxing從non_nullable_value_type轉換成另一個interface_typeI₀
,並且I₀
具有身分識別轉換至I
。 - 從任何non_nullable_value_type到任何interface_type
I
,使拳擊從non_nullable_value_type轉換成另一個interface_typeI₀
,並且I₀
可轉換變異數 (~18.2.3.3.3) 到I
。 - 從任何nullable_value_type到任何reference_type,其中從nullable_value_type的基礎類型轉換成reference_type。
- 從已知為參考型別的類型參數到任何型別,讓 \10.2.12 允許轉換。
Boxing 非 Nullable-value-type 的值是由配置對象實例和將值複製到該實例所組成。
將值 boxing nullable_value_type如果它是 null 值(HasValue
false),或解除包裝和 Boxing 基礎值的結果,則會產生 Null 參考。
注意:針對每個實值型別,可以想像Boxing類別的存在過程。 例如,請考慮實
struct S
作 介面I
,其 Boxing 類別稱為S_Boxing
。interface I { void M(); } struct S : I { public void M() { ... } } sealed class S_Boxing : I { S value; public S_Boxing(S value) { this.value = value; } public void M() { value.M(); } }
Boxing 類型
S
的值v
現在包含執行表達式new S_Boxing(v)
,並將產生的實例當做轉換目標型別的值傳回。 因此,語句S s = new S(); object box = s;
可以視為類似:
S s = new S(); object box = new S_Boxing(s);
上述想像中的Boxing類型實際上不存在。 相反地,類型的
S
Boxed 值具有運行時間類型,而運行時間類型會使用is
運算元搭配實值類型S
檢查,因為右操作數會測試左操作數是否為右操作數的 Boxed 版本。 例如,int i = 123; object box = i; if (box is int) { Console.Write("Box contains an int"); }
將會輸出下列內容:
Box contains an int
Boxing 轉換表示製作已 Boxed 值的複本。 這與將reference_type轉換成 型
object
別不同,在此轉換中,值會繼續參考相同的實例,而且只會被視為衍生型別的較少object
。 例如,下列專案struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { void M() { Point p = new Point(10, 10); object box = p; p.x = 20; Console.Write(((Point)box).x); } }
將會在控制台上輸出值 10,因為 指派
p
box
中發生的隱含 Boxing 作業會導致複製 的值p
。 若已Point
改為宣告class
,則值為 20 會輸出,因為p
會box
參考相同的實例。拳擊類別的類比不應做為用來描述拳擊在概念上運作方式的實用工具。 此規格所描述的行為與以這種方式實作 Boxing 所產生的行為之間有許多細微的差異。
end note
10.2.10 隱含動態轉換
隱含動態轉換存在從動態型別的運算式到任何型別 T
。 轉換會動態系結 至 \12.3.3,這表示在運行時間將隱含轉換從表達式的運行時間類型搜尋到 T
。 如果找不到轉換,則會擲回運行時例外狀況。
這個隱含轉換似乎違反 10.2 開頭的建議,即隱含轉換絕對不會造成例外狀況。 不過,這不是轉換本身,而是 導致 例外狀況的轉換結果。 運行時間例外狀況的風險固有於使用動態系結。 如果不需要轉換的動態系結,表達式可以先轉換成 ,然後再轉換成 object
所需的類型。
範例:下列說明隱含動態轉換:
object o = "object"; dynamic d = "dynamic"; string s1 = o; // Fails at compile-time – no conversion exists string s2 = d; // Compiles and succeeds at run-time int i = d; // Compiles but fails at run-time – no conversion exists
和
i
的指派s2
都會採用隱含動態轉換,其中作業的系結會暫停,直到運行時間為止。 在運行時間,會從 (string
) 的d
運行時間類型到目標類型,搜尋隱含轉換。 找到的轉換,string
但無法int
轉換成 。end 範例
10.2.11 隱含常數表達式轉換
隱含常數表示式轉換允許下列轉換:
- 型
int
別的constant_expression(~12.23)可以轉換成類型sbyte
、、byte
、ushort
short
、uint
、 或ulong
,前提是constant_expression的值在目的地類型的範圍內。 - 型
long
別的constant_expression可以轉換成 類型ulong
,前提是constant_expression的值不是負數。
10.2.12 涉及類型參數的隱含轉換
對於已知為參考類型的type_parameterT
(~15.2.5),有下列隱含參考轉換(~10.2.8) 存在:
- 從
T
到其有效的基類C
、從T
到的任何基類C
,以及 從T
到 由 實作C
的任何介面。 - 從
T
到I
interface_type 的有效T
介面集,以及 從T
到的任何基底介面。I
- 從
T
到 提供的型別參數U
,T
取決於U
(~15.2.5)。注意:由於
T
已知是參考型別,因此 在的範圍內T
,的U
運行時間類型一律會是參考型別,即使在編譯時期不知道是參考型別也一樣U
。 end note - 從 Null 常值 (~6.4.5.7) 到 T。
對於未知為參考型別 ≦15.2.5 的type_parameter T
,在編譯時期,涉及T
的下列轉換會被視為Boxing轉換 (~10.2.9)。 在運行時間,如果 T
是實值型別,則會以 Boxing 轉換的形式執行轉換。 在運行時間,如果 T
是參考型別,則會將轉換當做隱含參考轉換或識別轉換來執行。
- 從
T
到其有效的基類C
、從T
到的任何基類C
,以及 從T
到 由 實作C
的任何介面。注意:
C
將會是 、 或System.Enum
的其中一種類型System.Object
System.ValueType
,否則T
會是參考型別。 end note - 從
T
到I
interface_type 的有效T
介面集,以及 從T
到的任何基底介面。I
對於未知為參考型別的type_parameterT
,提供的T
型別參數U
有隱含的轉換T
取決於 。 U
在運行時間,如果 T
是實值型別,而且 U
是參考型別,則會執行轉換做為Boxing轉換。 在運行時間,如果 和 都是T
實值型別,則 T
和 U
一定是相同的型別,而且不會執行U
轉換。 在運行時間,如果 T
是參考型別,則 U
一定也是參考型別,而轉換會當做隱含參考轉換或識別轉換執行(~15.2.5)。
指定型別參數 T
有下列進一步的隱含轉換:
T
如果參考型別有隱含轉換至參考型S
別,且S₀
識別轉換至 ,則從S
到 參考型S₀
別。 在運行時間,轉換的執行方式與轉換成S₀
的方式相同。T
如果介面類型具有對接口類型的隱含轉換,則從 到介面類型I₀
I
,且I₀
可轉換成I
變異數 (~18.2.3.3.3)。 在運行時間,如果T
是實值型別,則會以 Boxing 轉換的形式執行轉換。 否則,轉換會以隱含參考轉換或身分識別轉換的形式執行。
在所有情況下,規則可確保只有在運行時間將轉換從實值型別轉換成參考型別時,才會執行轉換作為 Boxing 轉換。
10.2.13 隱含 Tuple 轉換
如果的arity與相同,而且E
中的每個元素E
都有隱含轉換,則從Tuple表達式E
隱含轉換成 Tuple 類型T
T
。T
轉換是藉由建立的對應System.ValueTuple<...>
型別實例T
,並藉由評估的對應 Tuple 元素表達式E
,依序初始化其每個欄位,並使用找到的隱含轉換,將它轉換成對應的元素類型T
,並以結果初始化字段。
如果 Tuple 運算式中的專案名稱不符合 Tuple 類型的對應專案名稱,則應該發出警告。
範例:
(int, string) t1 = (1, "One"); (byte, string) t2 = (2, null); (int, string) t3 = (null, null); // Error: No conversion (int i, string s) t4 = (i: 4, "Four"); (int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored
、
t2
t4
和t5
的t1
宣告都是有效的,因為隱含轉換會從項目表達式到對應的項目類型。 的宣告t3
無效,因為沒有從null
轉換成int
。 的t5
宣告會造成警告,因為 Tuple 運算式中的專案名稱與 Tuple 類型中的專案名稱不同。end 範例
10.2.14 用戶定義的隱含轉換
使用者定義的隱含轉換是由選擇性的標準隱含轉換所組成,後面接著執行使用者定義的隱含轉換運算符,後面接著另一個選擇性的標準隱含轉換。 評估使用者定義隱含轉換的確切規則會在 \10.5.4 中描述。
10.2.15 匿名函式轉換和方法群組轉換
匿名函式和方法群組本身沒有 型別,但可能會隱含地轉換成委派型別。 此外,某些 Lambda 運算式可能會隱含地轉換成表達式樹狀結構類型。 在 \10.7 和 \10.8 中的方法群組轉換中,會更詳細地描述匿名函式轉換。
10.2.16 預設常值轉換
從default_literal(~12.8.21)到任何類型的隱含轉換存在。 此轉換會產生推斷型別的預設值 (~9.3)。
10.2.17 隱含擲回轉換
雖然擲回表達式沒有型別,但可能會隱含地轉換成任何類型。
10.3 明確轉換
10.3.1 一般
下列轉換會分類為明確轉換:
- 所有隱含轉換 (~10.2)
- 明確數值轉換(~10.3.2)
- 明確列舉轉換 (•10.3.3)
- 明確可為 Null 的轉換 (~10.3.4)
- 明確元組轉換 (~10.3.6)
- 明確參考轉換 (\10.3.5)
- 明確介面轉換
- 取消收件匣轉換 (~10.3.7)
- 明確類型參數轉換 (\10.3.8)
- 使用者定義的明確轉換 (\10.3.9)
轉換可以在轉換表達式中發生(~12.9.7)。
明確轉換集合包含所有隱含轉換。
注意:例如,這允許在隱含識別轉換存在時使用明確轉換,以強制選取特定方法多載。 end note
非隱含轉換的明確轉換是無法一律證明為成功的轉換、已知可能遺失資訊的轉換,以及跨類型網域的轉換,足以值得明確表示法。
10.3.2 明確數值轉換
明確的數值轉換是從 numeric_type 轉換成另一個 numeric_type ,隱含數值轉換 (10.2.3) 不存在:
- 從
sbyte
到byte
、ushort
、uint
、ulong
或char
。 - 從
byte
到sbyte
或char
。 - 從
short
到sbyte
、、ushort
byte
、uint
、ulong
或char
。 - 從
ushort
到sbyte
、byte
、short
或char
。 - 從
int
到sbyte
、byte
、short
、ushort
、uint
、ulong
或char
。 - 從
uint
到sbyte
、、short
byte
、ushort
、int
或char
。 - 從
long
到sbyte
、byte
、short
、ushort
、int
、uint
、ulong
或char
。 - 從
ulong
到sbyte
、byte
、short
、ushort
、int
、uint
、long
或char
。 - 從
char
到sbyte
、byte
或short
。 - 從
float
到sbyte
、byte
、short
、ushort
ulong
uint
int
long
、char
、 或 。decimal
- 從
double
到sbyte
、byte
、short
、ushort
char
ulong
long
uint
int
、float
或 。decimal
- 從
decimal
到sbyte
、byte
、short
、ushort
char
ulong
long
uint
int
、float
或 。double
由於明確轉換包含所有隱含和明確的數值轉換,因此一律可以使用轉換表達式(~12.9.7),從任何numeric_type轉換成任何其他numeric_type。
明確的數值轉換可能會遺失資訊,或可能導致擲回例外狀況。 明確數值轉換的處理方式如下:
- 對於從整數型別轉換成另一個整數型別的轉換,處理取決於轉換的溢位檢查內容 (~12.8.20) :
checked
在內容中,如果來源操作數的值在目的類型範圍內,則轉換會成功,但如果來源操作數的值超出目的型別的範圍,則會擲回System.OverflowException
。unchecked
在內容中,轉換一律會成功,然後繼續進行,如下所示。- 如果來源類型大於目的地類型,則會捨棄其「額外」最重要的位來截斷來源值。 然後,結果將被視為目的地類型的值。
- 如果來源類型的大小與目的地類型相同,則來源值會視為目的地類型的值
- 針對從
decimal
轉換成整數型別的轉換,來源值會四捨五入為零到最接近的整數值,而這個整數值會變成轉換的結果。 如果產生的整數值超出目的型別的範圍,System.OverflowException
則會擲回 。 - 針對從
float
或double
轉換成整數型別的轉換,此處理取決於轉換的溢位檢查內容 (~12.8.20) :- 在核取的內容中,轉換會繼續進行,如下所示:
- 如果操作數的值是 NaN 或無限,
System.OverflowException
則會擲回 。 - 否則,來源操作數會四捨五入為零到最接近的整數值。 如果這個整數值在目的型別的範圍內,這個值就是轉換的結果。
- 否則會擲回
System.OverflowException
。
- 如果操作數的值是 NaN 或無限,
- 在未核取的內容中,轉換一律會成功,然後繼續進行,如下所示。
- 如果操作數的值是 NaN 或無限,則轉換的結果是目的地類型的未指定值。
- 否則,來源操作數會四捨五入為零到最接近的整數值。 如果這個整數值在目的型別的範圍內,這個值就是轉換的結果。
- 否則,轉換的結果是目的地類型的未指定值。
- 在核取的內容中,轉換會繼續進行,如下所示:
- 針對從
double
轉換為float
的轉換,值double
會四捨五入為最float
接近的值。double
如果值太小而無法表示為float
,則結果會變成零,且其符號與值相同。 如果值的大小double
太大而無法表示為float
,則結果會變成無限大,其正負號與值相同。double
如果值為 NaN,則結果也會是 NaN。 - 針對從 或轉換成 的轉換,來源值會轉換成
decimal
表示,並視需要四捨五入為最接近的數位(~8.3.8)。decimal
double
float
- 如果來源值太小而無法表示為
decimal
,則結果會變成零,如果decimal
支援帶正負號的零值,則會保留原始值的正負號。 - 如果來源值的大小太大而無法表示為
decimal
,或該值為無限大,則如果小數表示法支援無限值,則結果會保留原始值的正負號,否則會擲回 System.OverflowException。 - 如果來源值為 NaN,則如果十進位表示法支援 NaN,則結果會是 NaN;否則會擲回 System.OverflowException。
- 如果來源值太小而無法表示為
- 對於從
decimal
轉換為float
或double
的轉換,值decimal
會四捨五入為最double
接近或float
值。 如果來源值的大小太大而無法在目標類型中表示,或該值為無限大,則結果會保留原始值的正負號。 如果來源值為 NaN,則結果為 NaN。 雖然此轉換可能會失去精確度,但絕不會導致擲回例外狀況。
注意:類型
decimal
不需要支持無限或 NaN 值,但可能這樣做;其範圍可能小於 和double
的範圍float
,但不保證為 。 若decimal
為沒有無限值或 NaN 值的表示法,且範圍小於float
,則從decimal
轉換成float
或double
的結果永遠不會是無限或 NaN。 end note
10.3.3 明確列舉轉換
明確的列舉轉換如下:
- 從
sbyte
、byte
、short
、、int
long
double
ulong
char
float
ushort
uint
或decimal
到任何enum_type。 - 從任何enum_type到、、、
short
ushort
int
、、uint
、、ulong
char
long
、、float
、double
或 。decimal
byte
sbyte
- 從任何 enum_type 到任何其他 enum_type。
將任何參與enum_type視為該enum_type的基礎型別,然後在產生的型別之間執行隱含或明確的數值轉換,即可處理兩種型別之間的明確列舉轉換。
範例:假設有 具有 和 基礎類型的
int
enum_typeE
,從 到 的轉換byte
E
會當做從 到 的明確數值轉換處理(~10.3.2),int
byte
而從byte
byte
int
E
到 的轉換會當做隱含數值轉換處理(~10.2.3)。 end 範例
10.3.4 明確可為 Null 的轉換
明確可為 Null 的轉換是從明確和隱含預先定義的轉換衍生而來的可為 Null 轉換(~10.6.1)。
10.3.5 明確參考轉換
明確的參考轉換如下:
- 從物件到任何其他 reference_type。
- 從任何class_type
S
到任何class_typeT
,提供的S
是的T
基類。 - 從任何class_type到任何interface_type
S
T
,提供的S
不是密封的,而且未實S
作 。T
- 從任何interface_type到任何class_type
T
,提供的T
不是密封或提供的T
實作S
。S
- 從任何 interface_type
S
到任何 interface_typeT
,提供的S
不是衍生自T
。 - 從具有專案類型的array_type
S
到具有元素類型的Sᵢ
Tᵢ
array_typeT
,前提是下列所有專案都成立:S
和T
只有在項目類型中才不同。 換句話說,S
和T
的維度數目相同。- 從到
Tᵢ
的明確參考轉換存在Sᵢ
。
- 從
System.Array
和其實作的介面,到任何 array_type。 - 從單一維度array_type
S[]
到System.Collections.Generic.IList<T>
、System.Collections.Generic.IReadOnlyList<T>
及其基底介面,前提是有識別轉換或從S
到T
的明確參考轉換。 - 從
System.Collections.Generic.IList<S>
、System.Collections.Generic.IReadOnlyList<S>
和其基底介面到單一維度陣列類型T[]
,前提是有從S
到 T 的識別轉換或明確參考轉換。 - 從
System.Delegate
和 它實作至任何 delegate_type的介面。 - 如果參考型別的參考型
S
別有從 到參考型別的明確參考轉換S
,而且T₀
有從 到的身分識別轉換,則從T₀
參考型T
別到T
參考型T₀
別。 - 如果有從 到介面或委派型別的明確參考轉換,且可轉換成變異數或可轉換成
T
T₀
T
\18.2.3.3,則從參考型S
別到介面或委派型T
T₀
別。S
T₀
- From
D<S₁...Sᵥ>
toD<T₁...Tᵥ>
whereD<X₁...Xᵥ>
是泛型委派類型、D<S₁...Sᵥ>
與 不相容或與 相同D<T₁...Tᵥ>
,而且針對下列每個類型參數Xᵢ
D
保留:- 如果
Xᵢ
是不變的,則Sᵢ
與Tᵢ
相同。 - 如果
Xᵢ
為 covariant,則會有識別轉換、隱含參考轉換或從 到Sᵢ
Tᵢ
的明確參考轉換。 - 如果
Xᵢ
為反變數,則Sᵢ
和Tᵢ
是相同的或兩個參考型別。
- 如果
- 明確轉換涉及已知為參考型別的類型參數。 如需有關類型參數之明確轉換的詳細資訊,請參閱 <10.3.8> 。
明確的參考轉換是reference_type之間的轉換,需要運行時間檢查以確保它們正確無誤。
若要在運行時間成功進行明確參考轉換,來源操作數的值應為 null
,或者來源操作數所參考的物件類型應為可透過隱含參考轉換轉換成目的型別的類型(~10.2.8)。 如果明確參考轉換失敗, System.InvalidCastException
則會擲回 。
注意:參考轉換、隱含或明確,絕不會變更參考本身的值(\8.2.1),只有其類型;也不會變更所參考對象的類型或值。 end note
10.3.6 明確元組轉換
如果的arity與 T
相同,而且中的每個元素E
都有隱含或明確轉換,則E
從Tuple運算式E
到 Tuple 類型的T
T
明確轉換存在。 轉換是藉由建立的對應System.ValueTuple<...>
型別實例T
,並藉由評估的對應 Tuple 元素表達式E
,以從左至右初始化其每個字段,並使用找到的明確轉換將其轉換為對應的項目類型T
,以及初始化具有結果的欄位。
10.3.7 Unboxing 轉換
Unboxing 轉換允許明確轉換成value_type reference_type。 下列未收件匣轉換存在:
- 從類型
object
到任何 value_type。 - 從類型
System.ValueType
到任何 value_type。 - 從類型
System.Enum
到任何 enum_type。 - 從任何interface_type到任何實作interface_type的任何non_nullable_value_type。
- 從任何interface_type
I
到任何non_nullable_value_type,其中從interface_typeI₀
到non_nullable_value類型,以及從I
轉換成I₀
的身分識別轉換。 - 從任何interface_type
I
轉換成任何non_nullable_value_type,其中從interface_type到non_nullable_value_typeI₀
I₀
,或是I
variance_convertibleI
或可轉換為變異數I₀
(18.2.3.3.3)。 - 從任何reference_type到從reference_type到nullable_value_type基礎non_nullable_value_type的復原轉換的任何nullable_value_type。
- 從已知為實值型別的類型到任何型別的型別參數,讓 \10.3.8 允許轉換。
non_nullable_value_type的 unboxing 作業包含先檢查物件實例是否為指定non_nullable_value_type的 Boxed 值,然後將值複製到實例外。
如果來源操作數為 ,或將對象實例解壓縮至nullable_value_type的基礎類型,則取消收件nullable_value_type會產生nullable_value_type的 null 值。null
注意:參照到 {10.2.9 中所述的虛方塊處理類別,將物件方塊的 unboxing 轉換至value_type
S
包含執行表達式((S_Boxing)box).value
。 因此,語句object box = new S(); S s = (S)box;
概念上對應至
object box = new S_Boxing(new S()); S s = ((S_Boxing)box).value;
end note
若要在運行時間成功轉換為指定的 non_nullable_value_type ,來源操作數的值應該是該 non_nullable_value_type之 Boxed 值的參考。 如果擲回來源操作數為 null
System.NullReferenceException
。 如果來源操作數是不相容物件的參考, System.InvalidCastException
則會擲回 。
若要在運行時間成功轉換為指定的nullable_value_type,來源操作數的值應該是 null 或nullable_value_type基礎non_nullable_value_type的 boxed 值參考。 如果來源操作數是不相容物件的參考, System.InvalidCastException
則會擲回 。
10.3.8 涉及類型參數的明確轉換
對於已知為參考類型的type_parameterT
(~15.2.5),有下列明確的參考轉換(~10.3.5) 存在:
- 從 的有效基類
C
T
到T
,以及從的任何基類C
到T
。 - 從任何 interface_type 到
T
。 - 從
T
到提供的任何interface_typeI
,還沒有從 到的隱含參考轉換T
。I
- 從type_parameter
U
提供T
T
,取決於U
(~15.2.5)。注意:由於已知是參考型別,因此,在
T
的範圍內T
,您運行時間類型一律會是參考型別,即使在編譯時期不知道是參考型別也一樣U
。 end note
對於未知為參考型別(~15.2.5)的type_parameter T
,在編譯時期,涉及的下列轉換T
會被視為非箱式轉換(~10.3.7)。 在運行時間,如果 T
是實值型別,則轉換會以 Unboxing 轉換的形式執行。 在運行時間,如果 T
是參考型別,則會將轉換當做明確的參考轉換或身分識別轉換來執行。
- 從 的有效基類
C
T
到T
,以及從的任何基類C
到T
。注意:C 會是 、 或
System.Enum
類型的其中System.Object
System.ValueType
一種,否則T
會已知為參考型別。 end note - 從任何 interface_type 到
T
。
對於未知為參考型別 (~15.2.5) 的type_parameter T
,存在下列明確轉換:
- 從
T
到提供的任何interface_typeI
,還沒有從 隱含轉換成T
I
。 此轉換包含從 到的隱含 Boxing 轉換 (~10.2.9),T
後面接著從 到 的明確參考轉換。object
object
I
在運行時間,如果T
是實值型別,則會執行轉換做為 Boxing 轉換,後面接著明確參考轉換。 在運行時間,如果T
是參考型別,則會將轉換當做明確的參考轉換來執行。 - 從型別參數
U
提供T
T
,取決於U
(~15.2.5)。 在運行時間,如果T
是實值型別,而且U
是參考型別,則會將轉換執行為 Unboxing 轉換。 在運行時間,如果 和 都是T
實值型別,則T
和U
一定是相同的型別,而且不會執行U
轉換。 在運行時間,如果T
是參考型別,則U
一定也是參考型別,而且轉換會以明確的參考轉換或身分識別轉換的形式執行。
在所有情況下,規則可確保只有在運行時間轉換是從參考型別轉換成實值型別時,才會將轉換執行為 Unboxing 轉換。
上述規則不允許從不受限制的類型參數直接轉換成非介面類型,這可能令人吃驚。 此規則的原因是要防止混淆,並清楚說明這類轉換的語意。
範例:請考慮下列宣告:
class X<T> { public static long F(T t) { return (long)t; // Error } }
如果允許直接明確轉換
t
至long
,則可能會輕易預期會X<int>.F(7)
傳回7L
。 不過,它不會,因為只有在已知型別在系結時為數值時,才會考慮標準數值轉換。 為了清楚表達語意,必須改為撰寫上述範例:class X<T> { public static long F(T t) { return (long)(object)t; // Ok, but will only work when T is long } }
此程式代碼現在會編譯,但執行
X<int>.F(7)
會在執行時間擲回例外狀況,因為 Boxedint
無法直接long
轉換成 。end 範例
10.3.9 用戶定義的明確轉換
使用者定義的明確轉換是由選擇性的標準明確轉換所組成,後面接著執行使用者定義的隱含或明確轉換運算符,後面接著另一個選擇性的標準明確轉換。 評估使用者定義明確轉換的確切規則會在 \10.5.5 中說明。
10.4 標準轉換
10.4.1 一般
標準轉換是可在使用者定義的轉換中發生的預先定義轉換。
10.4.2 標準隱含轉換
下列隱含轉換會分類為標準隱含轉換:
- 身分識別轉換 (~10.2.2)
- 隱含數值轉換(~10.2.3)
- 隱含可為 Null 的轉換 (~10.2.6)
- Null 常值轉換 (~10.2.7)
- 隱含參考轉換(~10.2.8)
- 拳擊轉換 (\10.2.9)
- 隱含常數表示式轉換(•10.2.11)
- 涉及類型參數的隱含轉換(\10.2.12)
標準隱含轉換會特別排除使用者定義的隱含轉換。
10.4.3 標準明確轉換
標準明確轉換全都是標準隱含轉換,再加上有相反標準隱含轉換的明確轉換子集。
注意:換句話說,如果標準隱含轉換從類型到類型
B
存在,則標準明確轉換會從A
類型A
轉換成類型B
,從類型轉換成類型B
A
。 end note
10.5 使用者定義的轉換
10.5.1 一般
C# 允許使用者定義的轉換來增強預先定義的隱含和明確轉換。 使用者定義轉換是藉由在類別和結構類型中宣告轉換運算符 (~15.10.4) 來引進。
10.5.2 允許的使用者定義轉換
C# 只允許宣告特定使用者定義的轉換。 特別是,無法重新定義已經存在的隱含或明確轉換。
對於指定的來源類型和S
目標型別,如果 S
或 T
為可為 Null 的實值型T
別,則讓 S₀
和 T₀
參考其基礎類型,否則S₀
和 T₀
分別等於 S
和 T
。 只有在下列所有專案都成立時,才允許類別或結構宣告從來源類型 S
轉換成目標類型 T
:
S₀
和T₀
是不同的類型。S₀
或T₀
是運算符宣告所在的類別或結構類型。- 也不是
S₀
T₀
interface_type。 - 排除使用者定義的轉換,轉換不存在從
S
到T
或從T
到。S
套用至使用者定義轉換的限制是在 \15.10.4 中指定。
10.5.3 使用者定義轉換的評估
使用者定義的轉換會將來源表達式,其可能具有來源類型,轉換為另一個稱為目標類型的類型。 使用者定義轉換的評估是以尋找 來源表達式和目標類型最特定的 使用者定義轉換運算元為中心。 此判斷分成數個步驟:
- 尋找將考慮使用者定義轉換運算子的類別和結構集合。 如果來源類型存在,則此集合包含來源類型及其基類,以及目標型別及其基類。 基於這個目的,假設只有類別和結構可以宣告使用者定義運算符,而且非類別類型沒有基類。 此外,如果來源或目標類型是 Nullable-value-type,則會改用其基礎類型。
- 從該類型的集合中,判斷哪些使用者定義和增益轉換運算符適用。 若要適用轉換運算符,可以執行從來源運算式到運算元操作數類型的標準轉換 (^10.4),而且可以執行從運算符結果型別到目標型別的標準轉換。
- 從一組適用的使用者定義運算符,判斷哪一個運算符最明確。 一般而言,最特定的運算符是操作數類型「最接近」來源表達式的運算元,其結果類型與目標類型「最接近」。 使用者定義轉換運算元優先於提升轉換運算元。 建立最特定使用者定義轉換運算符的確切規則定義於下列子集內。
一旦識別出最特定的使用者定義轉換運算符之後,使用者定義轉換的實際執行最多需要三個步驟:
- 首先,如果需要,執行從來源表達式到使用者定義或提升轉換運算符的操作數類型的標準轉換。
- 接下來,叫用使用者定義的或提升轉換運算符來執行轉換。
- 最後,如有需要,請執行從使用者定義轉換運算子的結果類型到目標類型的標準轉換。
使用者定義轉換的評估絕不牽涉到一個以上的使用者定義或提升轉換運算符。 換句話說,從型S
別到型T
別的轉換永遠不會先執行從 到 X
的使用者定義轉換S
,然後從 執行使用者定義的轉換X
至 T
。
- 下列子集會提供使用者定義隱含或明確轉換評估的確切定義。 定義會使用下列詞彙:
- 如果標準隱含轉換(~10.4.2)從類型存在到類型
A
B
,而且如果兩者A
B
都沒有或interface_types
,則A
據說會包含B
,據說B
會包含 。A
- 如果標準隱含轉換(~10.4.2)從表示式
E
存在到類型B
,而且如果 既B
E
沒有或類型(如果有一個)則interface_types
,則E
據說會包含B
,並B
稱為包含E
。 - 一 組類型中最包含的類型 是一種類型,其中包含集合中的所有其他類型。 如果沒有單一類型包含所有其他類型,則集合沒有最包含的類型。 在更直覺的詞彙中,最包含的類型是集合中的「最大」類型,也就是可以隱含地轉換其他類型之一種類型。
- 一 組類型中最包含的類型 是集合中所有其他類型所包含的一種類型。 如果所有其他類型未包含任何單一類型,則集合沒有最包含的類型。 在更直覺的詞彙中,最包含的類型是集合中的「最小」類型,也就是可以隱含轉換成其他每一種類型的類型。
10.5.4 用戶定義的隱含轉換
使用者定義從表達式 E
到型 T
別的隱含轉換,如下所示:
判斷型
S
別、S₀
與T₀
。- 如果
E
具有類型,請讓S
成為該類型。 - 如果
S
或T
為可為 Null 的實值型別,則 letSᵢ
和Tᵢ
是其基礎類型,否則分別讓 和Tᵢ
S
T
和 。Sᵢ
- 如果
Sᵢ
或Tᵢ
是型別參數,請讓S₀
和T₀
成為其有效的基類,否則分別讓 和Sₓ
T₀
Tᵢ
和 和S₀
。
- 如果
尋找將考慮使用者定義轉換運算子的類型
D
集合。 這個集合包含S₀
(如果S₀
存在 且 是類別或結構)、基類S₀
(如果存在且是類別),以及T₀
(如果S₀
T₀
是類別或結構)。 只有當身分識別轉換至集合中已經包含的另一個類型不存在時,才會將類型新增至D
集合。尋找一組適用的使用者定義和提升轉換運算子,
U
。 這個集合是由類別或結構D
所宣告的使用者定義和提升隱含轉換運算子所組成,這些運算符會從E
包含的類型轉換成 所包含T
的類型。 如果U
是空的,則轉換未定義,而且會發生編譯時期錯誤。- 如果
S
存在,且任何從S
轉換的U
運算子,則Sₓ
為S
。 - 否則,
Sₓ
是 中運算符U
的結合來源型別集合中最包含的類型。 如果找不到一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果
在 中
U
尋找運算子最特定的目標類型Tₓ
:- 如果轉換成
U
T
的任何運算子,則Tₓ
為T
。 - 否則,
Tₓ
是 中運算符U
之合併目標型別集合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果轉換成
尋找最特定的轉換運算子:
- 如果
U
只包含一個從 轉換成Sₓ
Tₓ
的使用者定義轉換運算符,則這是最特定的轉換運算元。 - 否則,如果
U
只包含一個從Sₓ
轉換為Tₓ
的增益轉換運算符,則這是最特定的轉換運算符。 - 否則,轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果
最後,套用轉換:
- 如果 E 還沒有類型
Sₓ
,則會執行從E
到Sₓ
的標準隱含轉換。 - 叫用最特定的轉換運算元,以從
Sₓ
轉換成Tₓ
。 - 如果
Tₓ
不是T
,則會執行從Tₓ
到T
的標準隱含轉換。
- 如果 E 還沒有類型
如果使用者定義的隱含轉換從 型S
別的變數存在,則存在從型S
別到 T
型T
別的使用者定義隱含轉換。
10.5.5 使用者定義的明確轉換
使用者定義從表達式 E
到型 T
別的明確轉換,如下所示:
- 判斷型
S
別、S₀
與T₀
。- 如果
E
具有類型,請讓S
成為該類型。 - 如果
S
或T
為可為 Null 的實值型別,則 letSᵢ
和Tᵢ
是其基礎類型,否則分別讓 和Tᵢ
S
T
和 。Sᵢ
- 如果
Sᵢ
或Tᵢ
是型別參數,請讓S₀
和T₀
成為其有效的基類,否則分別讓 和Sᵢ
T₀
Tᵢ
和 和S₀
。
- 如果
- 尋找將考慮使用者定義轉換運算子的類型
D
集合。 這個集合包含S₀
(如果S₀
存在 且 是類別或結構)、基類S₀
(如果S₀
存在 且 是類別)、T₀
(如果T₀
是類別或結構),以及的基類T₀
(如果T₀
是類別)。A
只有在已包含在集合D
中的另一個型別的身分識別轉換不存在時,類型才會新增至集合。 - 尋找一組適用的使用者定義和提升轉換運算子,
U
。 這個集合包含使用者定義和解除的隱含或明確轉換運算符,這些運算元是由 類別或結構D
所宣告,而該運算元會從E
包含或包含S
的類型轉換成包含或包含的T
型別。 如果U
是空的,則轉換未定義,而且會發生編譯時期錯誤。 - 在 中
U
尋找運算子最特定的來源類型Sₓ
:- 如果 S 存在,且任何從
S
轉換的U
運算子,則Sₓ
為S
。 - 否則,如果轉換自
U
包含E
之型別的任何運算符,則Sₓ
為這些運算符之組合來源型別中最包含的類型。 如果找不到最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。 - 否則,
Sₓ
是 中運算符U
的結合來源型別集合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果 S 存在,且任何從
- 在 中
U
尋找運算子最特定的目標類型Tₓ
:- 如果轉換成
U
T
的任何運算子,則Tₓ
為T
。 - 否則,如果轉換
U
至 所包含T
之型別的任何運算符,則Tₓ
為這些運算子組合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。 - 否則,
Tₓ
是 中運算符U
之合併目標型別集合中最包含的類型。 如果找不到最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果轉換成
- 尋找最特定的轉換運算子:
- 如果 U 只包含一個從 轉換成
Sₓ
Tₓ
的使用者定義轉換運算子,則這是最特定的轉換運算符。 - 否則,如果
U
只包含一個從Sₓ
轉換為Tₓ
的增益轉換運算符,則這是最特定的轉換運算符。 - 否則,轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果 U 只包含一個從 轉換成
- 最後,套用轉換:
- 如果
E
還沒有 型Sₓ
別 ,則會執行從 E 到Sₓ
的標準明確轉換。 - 叫用最特定的使用者定義轉換運算元,以從
Sₓ
Tₓ
轉換成 。 - 如果
Tₓ
不是T
,則會執行從Tₓ
到T
的標準明確轉換。
- 如果
如果使用者定義的明確轉換從 型S
別的變數存在,則存在從型S
別到 T
型T
別的使用者定義明確轉換。
10.6 涉及可為 Null 類型的轉換
10.6.1 可為 Null 的轉換
可為 Null 的 轉換允許在不可為 Null 的實值型別上運作的預先定義轉換,以搭配這些類型的可為 Null 形式使用。 對於從不可為 Null 實值型別轉換成不可為 Null 實值型S
T
別的每個預先定義隱含或明確轉換(\10.2.2、\10.2.3、\10.2.4、\10.2.11、\10.3.2 和 \10.3.3),下列可為 Null 的轉換存在:
- 從
S?
到的隱含或明確轉換T?
- 從
S
到的隱含或明確轉換T?
- 從
S?
到T
的明確轉換。
可為 Null 的轉換本身會分類為隱含或明確轉換。
某些可為 Null 的轉換會分類為標準轉換,而且可能會作為使用者定義轉換的一部分發生。 具體來說,所有隱含可為 Null 的轉換都會分類為標準隱含轉換(~10.4.2),而滿足 ~10.4.3 需求的明確可為 Null 轉換則會分類為標準明確轉換。
根據基礎轉換評估可為 Null 的轉換 S
,以 T
繼續進行,如下所示:
- 如果可為 Null 的轉換是從
S?
到T?
:- 如果來源值為 null (
HasValue
屬性為false
),則結果為 類型的T?
Null 值。 - 否則,轉換會評估為從 取消包裝
S?
S
,後面接著從T
S
到的基礎轉換,後面接著從 換T
行。T?
- 如果來源值為 null (
- 如果可為 Null 的轉換是從
S
轉換為T?
,則會將轉換評估為基礎轉換S
,T
後面接著從 換T
行至T?
。 - 如果可為 Null 的轉換是從
S?
到T
,則會將轉換評估為解除包裝S?
,S
後面接著從S
T
基礎轉換成 。
10.6.2 隨即轉換
假設使用者定義轉換運算符會從不可為 Null 的實值型S
別轉換成不可為 Null 的實值型T
別,則會有從轉換為 T?
S?
的提升轉換運算元。 這個提升轉換運算符會執行從 到 的解除包裝S?
,後面接著使用者定義轉換S
,T
後面接著換T
T?
行至 ,不同之處在於 Null 值S?
會直接轉換成 Null 值。T?
S
提升轉換運算元具有與其基礎使用者定義轉換運算元相同的隱含或明確分類。
10.7 匿名函式轉換
10.7.1 一般
anonymous_method_expression或lambda_expression會分類為匿名函式(~12.19)。 表達式沒有類型,但可以隱含轉換成相容的委派類型。 某些 Lambda 運算式也可能隱含轉換成相容的表達式樹狀結構類型。
具體而言,匿名函 F
式與提供的委派類型 D
相容:
- 如果
F
包含 anonymous_function_signature,則D
和F
具有相同數目的參數。 - 如果
F
不包含anonymous_function_signature,則只要 沒有任何 參數D
是輸出參數,則D
可能會有任何類型的零或多個參數。 - 如果
F
具有明確類型的參數清單,中的D
每個參數都有與 中F
對應參數相同的修飾詞,且 中的F
對應參數之間存在識別轉換。 - 如果
F
具有隱含類型的參數清單,D
則沒有參考或輸出參數。 - 如果 的主體
F
是表達式,而且D
具有 void 傳回型別或F
異步,且D
具有«TaskType»
傳回型別 (\15.15.1),則 當 中的每個參數都得到 中D
對應參數F
的類型時,主體是有效的表達式 (w.r.t \12),該表達式的主體F
會允許為statement_expression (\13.7)。 - 如果 的主體
F
是區塊,而且D
具有 void 傳回型別或F
異步且D
具有«TaskType»
傳回型別,則當 中的每個參數都得到 中D
對應參數F
的類型時,F
主體是有效的區塊 (w.r.t \13.3),其中沒有return
語句指定表達式。 - 如果的主體
F
是表達式,而且F
是非異步且D
具有非void
傳回型別 ,或F
是異步,且D
具有«TaskType»<T>
傳回型T
別 (~15.15.1),則當 中的每個參數都得到 中D
F
對應參數F
的類型時,主體是可隱含轉換成T
的有效表達式 (w.r.t .t .12)。 - 如果的主體
F
是區塊,而且F
是非異步且D
具有非 void 傳回型別,或是F
是 async 且D
具有«TaskType»<T>
傳回型T
別,則當 中的每個參數F
都得到 中對應參數F
D
的類型時,主體是有效的語句區塊 (w.r.t \13.3),其中每個 return 語句都會指定可隱含轉換成T
的表達式。
範例:下列範例說明這些規則:
delegate void D(int x); D d1 = delegate { }; // Ok D d2 = delegate() { }; // Error, signature mismatch D d3 = delegate(long x) { }; // Error, signature mismatch D d4 = delegate(int x) { }; // Ok D d5 = delegate(int x) { return; }; // Ok D d6 = delegate(int x) { return x; }; // Error, return type mismatch delegate void E(out int x); E e1 = delegate { }; // Error, E has an output parameter E e2 = delegate(out int x) { x = 1; }; // Ok E e3 = delegate(ref int x) { x = 1; }; // Error, signature mismatch delegate int P(params int[] a); P p1 = delegate { }; // Error, end of block reachable P p2 = delegate { return; }; // Error, return type mismatch P p3 = delegate { return 1; }; // Ok P p4 = delegate { return "Hello"; }; // Error, return type mismatch P p5 = delegate(int[] a) // Ok { return a[0]; }; P p6 = delegate(params int[] a) // Error, params modifier { return a[0]; }; P p7 = delegate(int[] a) // Error, return type mismatch { if (a.Length > 0) return a[0]; return "Hello"; }; delegate object Q(params int[] a); Q q1 = delegate(int[] a) // Ok { if (a.Length > 0) return a[0]; return "Hello"; };
end 範例
範例:下列範例會使用泛型委派類型
Func<A,R>
,代表接受 型A
別自變數並傳回 型R
別值的函式:delegate R Func<A,R>(A arg);
在指派中
Func<int,int> f1 = x => x + 1; // Ok Func<int,double> f2 = x => x + 1; // Ok Func<double,int> f3 = x => x + 1; // Error Func<int, Task<int>> f4 = async x => x + 1; // Ok
每個匿名函式的參數和傳回型別都是從指派匿名函式的變數類型決定。
第一個指派成功將匿名函式轉換成委派類型,因為當 指定型
Func<int,int>
int
別 時x
,x + 1
是可隱含轉換成 型別int
的有效表達式。同樣地,第二個指派成功將匿名函式轉換成委派類型 Func int,double>,因為 (of 類型
int
) 的結果x + 1
會隱含轉換成 類型double
。<不過,第三個指派是編譯時期錯誤,因為當 指定 類型時
x
,類型double
的結果x + 1
無法隱含轉換成 類型int
。double
第四個指派成功將匿名異步函式轉換成委派類型,因為 (屬於 類型
Func<int, Task<int>>
int
) 的結果x + 1
會隱含轉換成異步 Lambda 的有效傳回型int
別,其具有傳回型Task<int>
別 。end 範例
如果F
與委派類型 相容,則 Lambda 運算式F
與表達式樹狀結構類型Expression<D>
D
相容。 這不適用於匿名方法,僅適用於 Lambda 運算式。
匿名函式可能會影響多載解析,並參與類型推斷。 如需詳細資訊,請參閱 <12.6 >。
10.7.2 匿名函式轉換成委派類型的評估
將匿名函式轉換成委派類型會產生委派實例,該實例會參考匿名函式和評估時作用中之擷取的外部變數集(可能空白)。 叫用委派時,會執行匿名函式的主體。 主體中的程式代碼是使用委派所參考的擷取外部變數集合來執行。 delegate_creation_expression(~12.8.17.6)可作為將匿名方法轉換成委派類型的替代語法。
從匿名函式產生的委派調用清單包含單一專案。 未指定委派的確切目標對象和目標方法。 特別是,不指定委派的目標物件是 null
、 this
封入函式成員的值,還是一些其他物件。
允許將同一組擷取的外部變數實例轉換成相同委派類型的語意相同匿名函式,但不允許傳回相同的委派實例。 在此使用語意完全相同的詞彙,表示在所有情況下,匿名函式的執行都會產生相同的效果,因為有相同的自變數。 此規則允許將下列程式代碼優化。
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));
...
}
}
由於兩個匿名函式委派具有相同的已擷取外部變數集合,而且由於匿名函式在語意上相同,因此編譯程式可以讓委派參考相同的目標方法。 事實上,編譯程式允許從這兩個匿名函式表達式傳回完全相同的委派實例。
10.7.3 Lambda 運算式轉換為表達式樹狀結構類型的評估
將 Lambda 運算式轉換成表達式樹狀結構類型會產生表達式樹狀結構(~8.6)。 更精確地說,Lambda 表達式轉換的評估會產生代表 Lambda 運算式本身結構的對象結構。
並非所有 Lambda 運算式都可以轉換成運算式樹狀結構類型。 轉換至相容的委派類型一律 存在,但在編譯階段可能會因為實作定義的原因而失敗。
注意:Lambda 運算式無法轉換成運算式樹狀結構類型的常見原因包括:
- 它有區塊主體
async
它有修飾詞- 其中包含指派運算符
- 其中包含輸出或參考參數
- 它包含動態系結表達式
end note
10.8 方法群組轉換
從方法群組 (~12.2) 到兼容的委派類型 (~20.4) 存在隱含轉換。 如果 D
是委派類型,而且E
是分類為方法群組的表達式,則D
只有在E
E
包含至少一個適用於其一般形式的方法時,才與 至少包含一個適用於其一般形式的方法(\12.6.4.2)相容,且具有符合 的參數類型和修飾D
詞的任何自變數清單 (\12.6.2),如下列所述。
下列說明從方法群組 E
轉換成委派類型的 D
編譯時間應用程式。
- 選取單一方法
M
會對應至窗體E(A)
的方法調用({12.8.10.2),並進行了下列修改:- 自變數清單是表達式清單
A
,每個運算式都會分類為變數,且在的parameter_listD
中,具有對應參數的類型和修飾詞(in
、out
或ref
),但類型的參數除外dynamic
,其中對應的表達式具有 類型object
,而不是dynamic
。 - 考慮的候選方法只是那些以一般形式適用的方法,而且不會省略任何選擇性參數(~12.6.4.2)。 因此,如果候選方法僅適用於展開格式,或是其中一或多個選擇性參數在 中
D
沒有對應的參數,則會忽略候選方法。
- 自變數清單是表達式清單
- 如果 12.8.10.2 的演算法產生與 相容的單一最佳方法
M
,D
則會將轉換視為存在。 - 如果選取的方法是實例方法
M
,則與E
相關聯的實例表達式會決定委派的目標物件。 - 如果選取的方法
M
是以實例表達式的成員存取方式表示的擴充方法,該實例表達式會決定委派的目標物件。 - 轉換的結果是 類型的
D
值,也就是參考所選方法和目標物件的委派。
範例:下列示範方法群組轉換:
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 } }
要隱含地將方法群組
F
轉換成 型別D1
值的指派d1
。
d2
要示範如何建立方法的委派,該方法具有較少衍生的 (contravariant) 參數類型和更衍生的 (covariant) 傳回型別。要顯示方法不適用時,沒有轉換的指派
d3
。要
d4
示範方法如何以一般形式套用的指派。用來顯示委派和方法的參數和傳回型別如何只針對參考型別而有所不同的指派
d5
。end 範例
如同所有其他隱含和明確轉換,轉換運算元可以用來明確執行特定轉換。
範例:因此,範例
object obj = new EventHandler(myDialog.OkClick);
可以改為寫入
object obj = (EventHandler)myDialog.OkClick;
end 範例
方法群組轉換可以藉由在 內 E
明確指定類型自變數,或透過類型推斷 (~12.6.3) 來參考泛型方法。 如果使用型別推斷,委派的參數類型會當做推斷程式中的自變數類型使用。 委派的傳回型別不會用於推斷。 無論是指定或推斷類型自變數,都是方法群組轉換程式的一部分;這些是叫用產生的委派時,用來叫用目標方法的類型自變數。
範例:
delegate int D(string s, int i); delegate int E(); class X { public static T F<T>(string s, T t) {...} public static T G<T>() {...} static void Main() { D d1 = F<int>; // Ok, type argument given explicitly D d2 = F; // Ok, int inferred as type argument E e1 = G<int>; // Ok, type argument given explicitly E e2 = G; // Error, cannot infer from return type } }
end 範例
方法群組可能會影響多載解析,並參與類型推斷。 如需詳細資訊,請參閱 <12.6 >。
方法群組轉換的運行時間評估會繼續進行,如下所示:
- 如果在編譯階段選取的方法是實例方法,或是做為實例方法存取的擴充方法,則會從與
E
相關聯的實例表達式判斷委派的目標物件:- 會評估實例表達式。 如果此評估造成例外狀況,則不會執行任何進一步的步驟。
- 如果實例表達式是 reference_type,則實例表達式所計算的值會變成目標物件。 如果選取的方法是實例方法,而目標物件是
null
,System.NullReferenceException
則會擲回 ,而且不會執行任何進一步的步驟。 - 如果實例表達式是 value_type,則會執行 Boxing 作業(~10.2.9)將值轉換成物件,而這個物件會變成目標物件。
- 否則,選取的方法是靜態方法呼叫的一部分,而委派的目標物件是
null
。 - 委派類型的
D
委派實例是使用編譯時期所決定之方法的參考,以及上述計算之目標對象的參考,如下所示:- 允許轉換 (但非必要) 使用已經包含這些參考的現有委派執行個體。
- 如果未重複使用現有的實例,則會建立新的實例 (~20.5)。 如果沒有足夠的記憶體可供設定新的實體,
System.OutOfMemoryException
則會擲回 。 否則,實例會使用指定的參考初始化。