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 intend 範例
某些轉換是由語言所定義。 程式也可以定義自己的轉換(~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.8) 和指派 (§12.23) 。
預先定義的隱含轉換一律會成功,而且永遠不會擲回例外狀況。
注意:適當設計的使用者定義隱含轉換也應該顯示這些特性。 結尾註釋
為了轉換的目的,型 object 別和 dynamic 是可轉換的身分識別 (~10.2.2)。
不過,動態轉換 (~10.2.10) 僅適用於類型 dynamic 為 (~8.2.4) 的運算式。
10.2.2 身分識別轉換
身分識別轉換會從任何類型轉換成相同類型,或運行時間相等的類型。 此轉換存在的其中一個原因是類型 T 或類型的 T 表達式可以說可以轉換成 T 本身。 下列身分識別轉換存在:
- 在與
T之間T,針對任何類型T。 - 在與
T之間T?,用於任何參考型別T。 - 在和
object之間dynamic。 - 在具有相同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後面接著 。stringTuple 元素類型本身可能由 Tuple,如 、t4和t5中t6所示。 每個對應的元素類型之間都有識別轉換,包括巢狀元組,因此元組t4t5、 和t6的類型之間存在識別轉換。end 範例
所有身分識別轉換都是對稱的。 如果身分識別轉換存在 至 T₁T₂,則識別轉換會從 T₂ 到 T₁。 當識別轉換存在於兩種類型之間時,兩種類型都是 可轉換 的身分識別。
在大部分情況下,身分識別轉換在運行時間沒有作用。 然而,由於浮點運算的執行精度可能高於其類型規定的精度 (§8.3.7),因此分配其結果可能會導致精度損失,並且明確轉換保證將精度降低到類型規定的精度 (§12.9.8)。
10.2.3 隱含數值轉換
隱含數值轉換如下:
- 從
sbyte到short、、intlong、float、double或decimal。 - 從
byte到short、ushort、int、、uint、longulong、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、 uintlong或 ulong 到 float 或 longulongdouble 的轉換可能會導致精確度遺失,但永遠不會造成大小損失。 其他隱含數值轉換永遠不會遺失任何資訊。
沒有預先定義的隱含轉換類型 char ,因此其他整數型別的值不會自動轉換成型別 char 。
10.2.4 隱含列舉轉換
隱含列舉轉換可讓具有任何整數類型且值為零的constant_expression (§12.25) 轉換成任何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。 -
到
SSᵢ,前提是下列所有專案都成立:-
S和T只有在項目類型中才不同。 換句話說,S和T的維度數目相同。 - 隱含參考轉換從 到
Sᵢ。Tᵢ
-
- 從單一維度陣列類型
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,且識別轉換為,則從任何
- 如果任何reference_type
T具有隱含身分識別或介面或委派類型的T₀T₀參考轉換,且變異可轉換 (§19.2.3.3)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,以便從 non_nullable_value_type 到另一個 interface_typeI₀進行裝箱轉換,並且I₀變異可轉換為 (§19.2.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 值(HasValuefalse),或解除包裝和 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 類型
v的值S現在包含執行表達式new S_Boxing(v),並將產生的實例當做轉換目標型別的值傳回。 因此,語句S s = new S(); object box = s;可以視為類似:
S s = new S(); object box = new S_Boxing(s);上述想像中的Boxing類型實際上不存在。 相反地,類型的
SBoxed 值具有運行時間類型,而運行時間類型會使用S運算元搭配實值類型is檢查,因為右操作數會測試左操作數是否為右操作數的 Boxed 版本。 例如,int i = 123; object box = i; if (box is int) { Console.Write("Box contains an int"); }將會輸出下列內容:
Box contains an intBoxing 轉換表示製作已 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,因為 指派
pbox中發生的隱含 Boxing 作業會導致複製 的值p。 若已Point改為宣告class,則值為 20 會輸出,因為p會box參考相同的實例。拳擊類別的類比不應做為用來描述拳擊在概念上運作方式的實用工具。 此規格所描述的行為與以這種方式實作 Boxing 所產生的行為之間有許多細微的差異。
結尾註釋
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和
s2的指派i都會採用隱含動態轉換,其中作業的系結會暫停,直到運行時間為止。 在運行時間,會從 (d) 的string運行時間類型到目標類型,搜尋隱含轉換。 找到的轉換,string但無法int轉換成 。end 範例
10.2.11 隱含常數表達式轉換
隱含常數表示式轉換允許下列轉換:
- 類型的constant_expression (
int) 可以轉換為類型sbyte、byte、shortushort、uint或ulong,前提是constant_expression的值在目的地類型的範圍內。 -
可以轉換成 類型
long,前提是constant_expressionulong不是負數。
10.2.12 涉及類型參數的隱含轉換
對於已知為參考類型的type_parameterT(~15.2.5),有下列隱含參考轉換(~10.2.8) 存在:
- 從
T到其有效的基類C、從T到的任何基類C,以及 從T到 由 實作C的任何介面。 - 從
T到 interface_type 的有效I介面集,以及 從T到的任何基底介面。T - 從
T到 提供的型別參數U,T取決於U(~15.2.5)。注意:由於
T已知是參考型別,因此 在的範圍內T,的U運行時間類型一律會是參考型別,即使在編譯時期不知道是參考型別也一樣U。 結尾註釋 - 從 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.Object的其中一種類型System.ValueTypeSystem.Enum,否則T會是參考型別。 結尾註釋 - 從
T到 interface_type 的有效I介面集,以及 從T到的任何基底介面。T
對於未知為參考型別的type_parameterT,提供的T型別參數U有隱含的轉換T取決於 。 U 在運行時間,如果 T 是實值型別,而且 U 是參考型別,則會執行轉換做為Boxing轉換。 在運行時間,如果 和 都是T實值型別,則 U 和 T 一定是相同的型別,而且不會執行U轉換。 在運行時間,如果 T 是參考型別,則 U 一定也是參考型別,而轉換會當做隱含參考轉換或識別轉換執行(~15.2.5)。
指定型別參數 T有下列進一步的隱含轉換:
-
T如果參考型別有隱含轉換至參考型S別,且S₀識別轉換至 ,則從S₀到 參考型S別。 在運行時間,轉換的執行方式與轉換成S₀的方式相同。 - 如果 From
T具有隱含轉換至介面類型I,且I₀變異可I₀轉換為 (I) ,則會轉換至介面類型。 在運行時間,如果T是實值型別,則會以 Boxing 轉換的形式執行轉換。 否則,轉換會以隱含參考轉換或身分識別轉換的形式執行。
在所有情況下,規則可確保只有在運行時間將轉換從實值型別轉換成參考型別時,才會執行轉換作為 Boxing 轉換。
10.2.13 隱含 Tuple 轉換
如果的arity與相同,而且E中的每個元素T都有隱含轉換,則從Tuple表達式E隱含轉換成 Tuple 類型TE。T 轉換是藉由建立的對應T型別實例System.ValueTuple<...>,並藉由評估的對應 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、
t1t2和t4的t5宣告都是有效的,因為隱含轉換會從項目表達式到對應的項目類型。 的宣告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.2.18 Switch表達式轉換
有從 switch_expression (§12.11) 隱含轉換至每個類型 T ,其中存在從每個 switch_expression_arm的 switch_expression_arm_expression隱含轉換到 T。
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.8) 。
明確轉換集合包含所有隱含轉換。
注意:例如,這允許在隱含識別轉換存在時使用明確轉換,以強制選取特定方法多載。 結尾註釋
非隱含轉換的明確轉換是無法一律證明為成功的轉換、已知可能遺失資訊的轉換,以及跨類型網域的轉換,足以值得明確表示法。
10.3.2 明確數值轉換
明確的數值轉換是從 numeric_type 轉換成另一個 numeric_type ,隱含數值轉換 (10.2.3) 不存在:
- 從
sbyte到byte、ushort、uint、ulong或char。 - 從
byte到sbyte或char。 - 從
short到sbyte、、byteushort、uint、ulong或char。 - 從
ushort到sbyte、byte、short或char。 - 從
int到sbyte、byte、short、ushort、uint、ulong或char。 - 從
uint到sbyte、、byteshort、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、ushortintuintlongulong、char、 或 。decimal - 從
double到sbyte、byte、short、ushortintuintlongulongchar、float或 。decimal - 從
decimal到sbyte、byte、short、ushortintuintlongulongchar、float或 。double
因為明確轉換包含所有隱含和明確數值轉換,所以一律可以使用強制轉換運算式從任何 numeric_type 轉換成任何其他 numeric_type (§12.9.8) 。
明確的數值轉換可能會遺失資訊,或可能導致擲回例外狀況。 明確數值轉換的處理方式如下:
- 對於從整數型別轉換成另一個整數型別的轉換,處理取決於轉換的溢位檢查內容 (~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。 - 針對從 或轉換成 的轉換,來源值會轉換成
float表示,並視需要四捨五入為最接近的數位(double)。decimaldecimal- 如果來源值太小而無法表示為
decimal,則結果會變成零,如果decimal支援帶正負號的零值,則會保留原始值的正負號。 - 如果來源值的大小太大而無法表示為
decimal,或該值為無限大,則如果小數表示法支援無限值,則結果會保留原始值的正負號,否則會擲回 System.OverflowException。 - 如果來源值為 NaN,則如果十進位表示法支援 NaN,則結果會是 NaN;否則會擲回 System.OverflowException。
- 如果來源值太小而無法表示為
- 對於從
decimal轉換為float或double的轉換,值decimal會四捨五入為最double接近或float值。 如果來源值的大小太大而無法在目標類型中表示,或該值為無限大,則結果會保留原始值的正負號。 如果來源值為 NaN,則結果為 NaN。 雖然此轉換可能會失去精確度,但絕不會導致擲回例外狀況。
注意:類型
decimal不需要支持無限或 NaN 值,但可能這樣做;其範圍可能小於 和float的範圍double,但不保證為 。 若decimal為沒有無限值或 NaN 值的表示法,且範圍小於float,則從decimal轉換成float或double的結果永遠不會是無限或 NaN。 結尾註釋
10.3.3 明確列舉轉換
明確的列舉轉換如下:
- 從
sbyte、byte、short、、ushortintuintlongulongcharfloatdouble或decimal到任何enum_type。 - 從任何enum_type到、、、
sbytebyte、、short、、ushortintuint、、long、ulong或 。charfloatdoubledecimal - 從任何 enum_type 到任何其他 enum_type。
將任何參與enum_type視為該enum_type的基礎型別,然後在產生的型別之間執行隱含或明確的數值轉換,即可處理兩種型別之間的明確列舉轉換。
範例:假設基礎類型為 的
Eint,E到byte的轉換會當做明確的數值轉換處理(10.3.2) 從int到byte,從byte轉換成E會以隱含數值轉換(10.2.3)從byte轉換成int。 end 範例
10.3.4 明確可為 Null 的轉換
明確可為 Null 的轉換是從明確和隱含預先定義的轉換衍生而來的可為 Null 轉換(~10.6.1)。
10.3.5 明確參考轉換
明確的參考轉換如下:
- 從物件到任何其他 reference_type。
- 從任何class_type
S到任何class_typeT,提供的S是的T基類。 - 從任何class_type到任何 ,提供的
S不是密封的,而且未實作 。T - 從任何interface_type到任何class_type
S,提供的T不是密封或提供的T實作T。S - 從任何 interface_type
S到任何 interface_typeT,提供的S不是衍生自T。 -
到
SSᵢ,前提是下列所有專案都成立:-
S和T只有在項目類型中才不同。 換句話說,S和T的維度數目相同。 - 從到
Sᵢ的明確參考轉換存在Tᵢ。
-
- 從
System.Array和其實作的介面,到任何 array_type。 - 從單一維度到
S[]、System.Collections.Generic.IList<T>及其基底介面,前提是有識別轉換或從System.Collections.Generic.IReadOnlyList<T>到S的明確參考轉換。 - 從
System.Collections.Generic.IList<S>、System.Collections.Generic.IReadOnlyList<S>和其基底介面到單一維度陣列類型T[],前提是有從S到 T 的識別轉換或明確參考轉換。 - 從
System.Delegate和 它實作至任何 delegate_type的介面。 - 如果參考型別的參考型
S別有從 到參考型別的明確參考轉換T,而且S有從 到的身分識別轉換,則從T₀參考型T₀別到T₀參考型T別。 - 從參考類型
S到介面或委派類型T,如果有明確的參考轉換S為介面或委派類型T₀,而且T₀可以變異轉換為TTT₀,也可以變異轉換為 §19.2.3.3。 - 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),只有其類型;也不會變更所參考對象的類型或值。 結尾註釋
10.3.6 明確元組轉換
如果的arity與 E 相同,而且中的每個元素T都有隱含或明確轉換,則E從Tuple運算式T到 Tuple 類型的ET明確轉換存在。 轉換是藉由建立的對應T型別實例System.ValueTuple<...>,並藉由評估的對應 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的身分識別轉換。 - 從任何 interface_type
I到任何 non_nullable_value_type ,其中有從 interface_typeI₀到 non_nullable_value_type 的解除裝箱轉換,而且要麼I₀variance_convertibleI到或I變異可轉換為I₀(§19.2.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;結尾註釋
若要在運行時間成功轉換為指定的 non_nullable_value_type ,來源操作數的值應該是該 non_nullable_value_type之 Boxed 值的參考。 如果擲回來源操作數為 nullSystem.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) 存在:
- 從 的有效基類
CT到T,以及從的任何基類C到T。 - 從任何 interface_type 到
T。 - 從
T到提供的任何interface_typeI,還沒有從 到的隱含參考轉換T。I -
從type_parameter
U提供TT,取決於U(~15.2.5)。注意:由於已知是參考型別,因此,在
T的範圍內T,您運行時間類型一律會是參考型別,即使在編譯時期不知道是參考型別也一樣U。 結尾註釋
T ,在編譯時期,涉及的下列轉換會被視為非箱式轉換()。 在運行時間,如果 T 是實值型別,則轉換會以 Unboxing 轉換的形式執行。 在運行時間,如果 T 是參考型別,則會將轉換當做明確的參考轉換或身分識別轉換來執行。
- 從 的有效基類
CT到T,以及從的任何基類C到T。注意:C 會是 、 或
System.Object類型的其中System.ValueTypeSystem.Enum一種,否則T會已知為參考型別。 結尾註釋 - 從任何 interface_type 到
T。
對於未知T,存在下列明確轉換:
- 從
T到提供的任何interface_typeI,還沒有從 隱含轉換成TI。 此轉換包含從 到的隱含 Boxing 轉換 (~10.2.9 在運行時間,如果T是實值型別,則會執行轉換做為 Boxing 轉換,後面接著明確參考轉換。 在運行時間,如果T是參考型別,則會將轉換當做明確的參考轉換來執行。 - 從型別參數
U提供TT,取決於U(~15.2.5)。 在運行時間,如果T是實值型別,而且U是參考型別,則會將轉換執行為 Unboxing 轉換。 在運行時間,如果 和 都是T實值型別,則U和T一定是相同的型別,而且不會執行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 標準明確轉換
標準明確轉換全都是標準隱含轉換,再加上有相反標準隱含轉換的明確轉換子集。
注意:換句話說,如果標準隱含轉換從類型到類型
A存在,則標準明確轉換會從B類型A轉換成類型B,從類型轉換成類型BA。 結尾註釋
10.5 使用者定義的轉換
10.5.1 一般
C# 允許使用者定義的轉換來增強預先定義的隱含和明確轉換。 使用者定義轉換是藉由在類別和結構類型中宣告轉換運算符 (~15.10.4) 來引進。
10.5.2 允許的使用者定義轉換
C# 只允許宣告特定使用者定義的轉換。 特別是,無法重新定義已經存在的隱含或明確轉換。
對於指定的來源類型和S目標型別,如果 T 或 S 為可為 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別的轉換永遠不會先執行從 到 S 的使用者定義轉換X,然後從 執行使用者定義的轉換X至 T。
- 下列子集會提供使用者定義隱含或明確轉換評估的確切定義。 定義會使用下列詞彙:
- 如果標準隱含轉換(§10.4.2)從類型
A到類型B存在,且A和B均不是 interface_type,則認為A被 包含在B之中,而B則被認為 包含A。 - 如果標準隱含轉換 (10.4.2) 從表達式
E到類型B存在,而且如果B和E類型(如果有一個)都 interface_type,則據說E會 由B所包含,而B據說 包含E。 - 一 組類型中最包含的類型 是一種類型,其中包含集合中的所有其他類型。 如果沒有單一類型包含所有其他類型,則集合沒有最包含的類型。 在更直覺的詞彙中,最包含的類型是集合中的「最大」類型,也就是可以隱含地轉換其他類型之一種類型。
- 一 組類型中最包含的類型 是集合中所有其他類型所包含的一種類型。 如果所有其他類型未包含任何單一類型,則集合沒有最包含的類型。 在更直覺的詞彙中,最包含的類型是集合中的「最小」類型,也就是可以隱含轉換成其他每一種類型的類型。
10.5.4 用戶定義的隱含轉換
使用者定義從表達式 E 到型 T 別的隱含轉換,如下所示:
判斷型
S別、S₀與T₀。- 如果
E具有類型,請讓S成為該類型。 - 如果
S或T為可為 Null 的實值型別,則 letSᵢ和Tᵢ是其基礎類型,否則分別讓 和SᵢTᵢS和 。T - 如果
Sᵢ或Tᵢ是型別參數,請讓S₀和T₀成為其有效的基類,否則分別讓 和S₀T₀Sᵢ和 和Tᵢ。
- 如果
尋找將考慮使用者定義轉換運算子的類型
D集合。 這個集合包含S₀(如果S₀存在 且 是類別或結構)、基類S₀(如果存在且是類別),以及S₀(如果T₀T₀是類別或結構)。 只有當身分識別轉換至集合中已經包含的另一個類型不存在時,才會將類型新增至D集合。尋找一組適用的使用者定義和提升轉換運算子,
U。 這個集合是由類別或結構D所宣告的使用者定義和提升隱含轉換運算子所組成,這些運算符會從E包含的類型轉換成 所包含T的類型。 如果U是空的,則轉換未定義,而且會發生編譯時期錯誤。尋找 中
U最特定的來源類型Sₓ,運算子:- 如果
S存在,且任何從U轉換的S運算子,則Sₓ為S。 - 否則,
Sₓ是 中運算符U的結合來源型別集合中最包含的類型。 如果找不到一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果
在 中
Tₓ尋找運算子最特定的目標類型U:- 如果轉換成
UT的任何運算子,則Tₓ為T。 - 否則,
Tₓ是 中運算符U之合併目標型別集合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果轉換成
尋找最特定的轉換運算子:
- 如果
U只包含一個從 轉換成SₓTₓ的使用者定義轉換運算符,則這是最特定的轉換運算元。 - 否則,如果
U只包含一個從Sₓ轉換為Tₓ的增益轉換運算符,則這是最特定的轉換運算符。 - 否則,轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果
最後,套用轉換:
- 如果 E 還沒有類型
Sₓ,則會執行從E到Sₓ的標準隱含轉換。 - 叫用最特定的轉換運算元,以從
Sₓ轉換成Tₓ。 - 如果
Tₓ不是T,則會執行從Tₓ到T的標準隱含轉換。
- 如果 E 還沒有類型
如果使用者定義的隱含轉換從 型S別的變數存在,則存在從型T別到 S型T別的使用者定義隱含轉換。
10.5.5 使用者定義的明確轉換
使用者定義從表達式 E 到型 T 別的明確轉換,如下所示:
- 判斷型
S別、S₀與T₀。- 如果
E具有類型,請讓S成為該類型。 - 如果
S或T為可為 Null 的實值型別,則 letSᵢ和Tᵢ是其基礎類型,否則分別讓 和SᵢTᵢS和 。T - 如果
Sᵢ或Tᵢ是型別參數,請讓S₀和T₀成為其有效的基類,否則分別讓 和S₀T₀Sᵢ和 和Tᵢ。
- 如果
- 尋找將考慮使用者定義轉換運算子的類型
D集合。 這個集合包含S₀(如果S₀存在 且 是類別或結構)、基類S₀(如果S₀存在 且 是類別)、T₀(如果T₀是類別或結構),以及的基類T₀(如果T₀是類別)。 只有當身分識別轉換至集合中已經包含的另一個類型不存在時,才會將類型新增至D集合。 - 尋找一組適用的使用者定義和提升轉換運算子,
U。 這個集合包含使用者定義和解除的隱含或明確轉換運算符,這些運算元是由 類別或結構D所宣告,而該運算元會從E包含或包含S的類型轉換成包含或包含的T型別。 如果U是空的,則轉換未定義,而且會發生編譯時期錯誤。 - 在 中
Sₓ尋找運算子最特定的來源類型U:- 如果 S 存在,且任何從
U轉換的S運算子,則Sₓ為S。 - 否則,如果轉換自
U包含E之型別的任何運算符,則Sₓ為這些運算符之組合來源型別中最包含的類型。 如果找不到最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。 - 否則,
Sₓ是 中運算符U的結合來源型別集合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
- 如果 S 存在,且任何從
- 在 中
Tₓ尋找運算子最特定的目標類型U:- 如果轉換成
UT的任何運算子,則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別的變數存在,則存在從型T別到 S型T別的使用者定義明確轉換。
10.6 涉及可為 Null 類型的轉換
10.6.1 可為 Null 的轉換
可為 Null 的轉換允許在不可為 Null 值類型上運作的預先定義轉換也與該類型的可為 Null 形式搭配使用。 對於從不可為 Null 實值型別轉換成不可為 Null 實值型ST 別的每個預先定義隱含或明確轉換(\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,後面接著從ST到的基礎轉換,後面接著從 換T行。T?
- 如果來源值為 null (
- 如果可為 Null 的轉換是從
S轉換為T?,則會將轉換評估為基礎轉換S,T後面接著從 換T行至T?。 - 如果可為 Null 的轉換是從
S?到T,則會將轉換評估為解除包裝S?,S後面接著從ST基礎轉換成 。
10.6.2 隨即轉換
假設使用者定義轉換運算符會從不可為 Null 的實值型S別轉換成不可為 Null 的實值型T別,。 這個提升轉換運算符會執行從 到 的解除包裝S?,後面接著使用者定義轉換S,S後面接著換TT行至 ,不同之處在於 Null 值T?會直接轉換成 Null 值。S?T? 提升轉換運算元具有與其基礎使用者定義轉換運算元相同的隱含或明確分類。
10.7 匿名函式轉換
10.7.1 一般
anonymous_method_expression或lambda_expression被歸類為匿名函數 (§12.21)。 表達式沒有類型,但可以隱含轉換成相容的委派類型。 某些 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.14.1),則當F的每個參數被賦予D中的對應參數的類型時,F的主體是有效的表達式(w.r.t §12),允許作為statement_expression(§13.7)。 - 如果 的主體
F是區塊,而且D具有 void 傳回型別或F別,則當 中的每個參數都得到 中D對應參數«TaskType»的類型時,F主體是有效的區塊 (w.r.tD),其中沒有F語句指定表達式。 - 如果
F的主體是表達式,且不是異步並且F具有非D傳回型別,或void是異步且T具有傳回型別(§15.14.1),則當F的每個參數都被賦予D中對應參數的類型時,«TaskType»<T>的主體是可隱含轉換為的有效表達式(相對於§12)。 - 如果的主體
F是區塊,而且F是非異步且D具有非 void 傳回型別,或是T是 async 且F具有D傳回型«TaskType»<T>別,則當 中的每個參數F都得到 中對應參數DF的類型時,主體是有效的語句區塊 (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>x別 時int,x + 1是可隱含轉換成 型別int的有效表達式。同樣地,第二個指派成功將匿名函式轉換成委派類型 Func int,double<,因為 (of 類型>) 的結果
x + 1會隱含轉換成 類型int。double不過,第三個指派是編譯時期錯誤,因為當 指定 類型時
x,類型double的結果x + 1無法隱含轉換成 類型double。int第四個指派成功將匿名異步函式轉換成委派類型,因為 (屬於 類型
Func<int, Task<int>>x + 1) 的結果int會隱含轉換成異步 Lambda 的有效傳回型int別,其具有傳回型Task<int>別 。end 範例
如果F與委派類型 相容,則 Lambda 運算式Expression<D>與表達式樹狀結構類型FD相容。 這不適用於匿名方法,僅適用於 Lambda 運算式。
匿名函式可能會影響多載解析,並參與類型推斷。 如需詳細資訊,請參閱 <12.6 >。
10.7.2 匿名函式轉換成委派類型的評估
將匿名函式轉換成委派類型會產生委派實例,該實例會參考匿名函式和評估時作用中之擷取的外部變數集(可能空白)。 叫用委派時,會執行匿名函式的主體。 主體中的程式代碼是使用委派所參考的擷取外部變數集合來執行。 delegate_creation_expression(~12.8.17.5)可作為將匿名方法轉換成委派類型的替代語法。
從匿名函式產生的委派調用清單包含單一專案。 未指定委派的確切目標對象和目標方法。 特別是,不指定委派的目標物件是 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它有修飾詞- 其中包含指派運算符
- 其中包含輸出或參考參數
- 它包含動態系結表達式
結尾註釋
10.8 方法群組轉換
隱含轉換存在從方法群組 (§12.2) 到相容委派類型 (§21.4) 的隱含轉換。 如果 D 是委派類型,而且E是分類為方法群組的表達式,則D只有在EE包含至少一個適用於其一般形式的方法時,才與 至少包含一個適用於其一般形式的方法(\12.6.4.2)相容,且具有符合 的參數類型和修飾詞的任何自變數清單 (),如下列所述。
下列說明從方法群組 E 轉換成委派類型的 D 編譯時間應用程式。
- 選取單一方法
M會對應至窗體的方法調用(E(A)),並進行了下列修改:- 自變數清單是表達式清單
A,每個運算式都會分類為變數,且在的parameter_listin,具有對應參數的類型和修飾詞(out、ref或 ),但類型的參數除外D,其中對應的表達式具有 類型dynamic,而不是object。 - 考慮的候選方法只是那些以一般形式適用的方法,而且不會省略任何選擇性參數(~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 } }要隱含地將方法群組
d1轉換成 型別F值的指派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委派實例是使用編譯時期所決定之方法的參考,以及上述計算之目標對象的參考,如下所示:- 允許轉換 (但非必要) 使用已經包含這些參考的現有委派執行個體。
- 如果未重複使用現有實例,則會建立新的實例 (§21.5)。 如果沒有足夠的記憶體可供設定新的實體,
System.OutOfMemoryException則會擲回 。 否則,實例會使用指定的參考初始化。