共用方式為


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 一般

下列轉換會分類為隱含轉換:

隱含轉換可能發生在各種情況中,包括函式成員調用(•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,如 、 t5t6t4所示。 每個對應的元素類型之間都有識別轉換,包括巢狀元組,因此元組t4t5、 和t6的類型之間存在識別轉換。

end 範例

所有身分識別轉換都是對稱的。 如果身分識別轉換存在 至 T₁ T₂,則識別轉換會從 T₂T₁。 當識別轉換存在於兩種類型之間時,兩種類型都是 可轉換 的身分識別。

在大部分情況下,身分識別轉換在運行時間沒有作用。 不過,由於浮點運算的精確度可能高於其類型所指定的有效位數(~8.3.7),因此其結果指派可能會導致精確度喪失,而且保證明確轉換能夠將精確度降低到類型所規定的內容(12.9.7)。

10.2.3 隱含數值轉換

隱含數值轉換如下:

  • sbyteshort、、longintfloatdoubledecimal
  • byteshortushortint、、uintulonglongfloatdouble、 或 decimal
  • shortintlongfloatdoubledecimal
  • ushortintuintlongulongfloatdoubledecimal
  • intlongfloatdoubledecimal
  • uintlongulongfloatdoubledecimal
  • longfloatdoubledecimal
  • ulongfloatdoubledecimal
  • charushortintuintlongulongfloatdoubledecimal
  • float 變更為 double

intlong uintulongfloatlong ulong double 的轉換可能會導致精確度遺失,但永遠不會造成大小損失。 其他隱含數值轉換永遠不會遺失任何資訊。

沒有預先定義的隱含轉換類型 char ,因此其他整數型別的值不會自動轉換成型別 char

10.2.4 隱含列舉轉換

隱含列舉轉換允許任何整數型別的constant_expression12.23)和值零轉換成任何enum_type,以及任何基礎類型為enum_type的nullable_value_type。 在後一種情況下,轉換會透過轉換成基礎 enum_type 並包裝結果來評估轉換(~8.3.12)。

10.2.5 隱含插補字串轉換

隱含插補字串轉換允許 將 interpolated_string_expression12.8.3) 轉換成 System.IFormattableSystem.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_typeobjectdynamic
  • 從任何class_typeS到任何class_type T,提供的 S 衍生自 T
  • 從任何class_typeS到任何interface_type T,提供的 S 會實作 T
  • 從任何interface_typeS到任何interface_type T,提供的 S 衍生自 T
  • 從具有專案類型的array_typeS具有元素類型的SᵢTᵢarray_typeT,前提是下列所有專案都成立:
    • ST 只有在項目類型中才不同。 換句話說, ST 的維度數目相同。
    • 隱含參考轉換從 到 TᵢSᵢ
  • 從單一維度陣列類型 S[]System.Collections.Generic.IList<T>System.Collections.Generic.IReadOnlyList<T>及其基底介面,前提是有從 ST的隱含識別或參考轉換。
  • 從任何 array_typeSystem.Array ,以及其實作的介面。
  • 從任何 delegate_typeSystem.Delegate ,以及其實作的介面。
  • 從 Null 常值 (~6.4.5.7) 到任何引用類型。
  • 如果reference_type具有隱含的身分識別或參考轉換至reference_type,且識別轉換為T,則從任何reference_type到reference_type。TT₀T₀
  • 如果介面或委派型別具有對接口或委派類型的隱含識別或參考轉換,且可轉換變異數(18.2.3.3.3),則從任何reference_type到介面或委派類型TT₀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_typeI,讓boxing從non_nullable_value_type轉換成另一個interface_typeI₀,並且I₀具有身分識別轉換至 I
  • 從任何non_nullable_value_type到任何interface_typeI,使拳擊從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 值(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 類型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,因為 指派pbox中發生的隱含 Boxing 作業會導致複製 的值p。 若已 Point 改為宣告 class ,則值為 20 會輸出,因為 pbox 參考相同的實例。

拳擊類別的類比不應做為用來描述拳擊在概念上運作方式的實用工具。 此規格所描述的行為與以這種方式實作 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、、byteushortshortuint、 或 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的任何介面。
  • TI interface_type 的有效T介面集,以及 從 T 到的任何基底介面。I
  • T 到 提供的型別參數 UT 取決於 U~15.2.5)。

    注意:由於 T 已知是參考型別,因此 在的範圍內 T,的 U 運行時間類型一律會是參考型別,即使在編譯時期不知道是參考型別也一樣 Uend 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.ObjectSystem.ValueType,否則T會是參考型別。 end note

  • TI interface_type 的有效T介面集,以及 從 T 到的任何基底介面。I

對於未知為參考型別的type_parameterT,提供的T型別參數U有隱含的轉換T取決於 。 U 在運行時間,如果 T 是實值型別,而且 U 是參考型別,則會執行轉換做為Boxing轉換。 在運行時間,如果 和 都是T實值型別,則 TU 一定是相同的型別,而且不會執行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 類型TTT 轉換是藉由建立的對應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

t2t4t5t1宣告都是有效的,因為隱含轉換會從項目表達式到對應的項目類型。 的宣告 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) 不存在:

  • sbytebyteushortuintulongchar
  • bytesbytechar
  • shortsbyte、、ushortbyteuintulongchar
  • ushortsbytebyteshortchar
  • intsbytebyteshortushortuintulongchar
  • uintsbyte、、shortbyteushortintchar
  • longsbytebyteshortushortintuintulongchar
  • ulongsbytebyteshortushortintuintlongchar
  • charsbytebyteshort
  • floatsbytebyteshortushortulonguintintlongchar、 或 。decimal
  • doublesbytebyteshortushortcharulonglonguintintfloat或 。decimal
  • decimalsbytebyteshortushortcharulonglonguintintfloat或 。double

由於明確轉換包含所有隱含和明確的數值轉換,因此一律可以使用轉換表達式(~12.9.7),從任何numeric_type轉換成任何其他numeric_type

明確的數值轉換可能會遺失資訊,或可能導致擲回例外狀況。 明確數值轉換的處理方式如下:

  • 對於從整數型別轉換成另一個整數型別的轉換,處理取決於轉換的溢位檢查內容 (~12.8.20) :
    • checked在內容中,如果來源操作數的值在目的類型範圍內,則轉換會成功,但如果來源操作數的值超出目的型別的範圍,則會擲回 System.OverflowException
    • unchecked在內容中,轉換一律會成功,然後繼續進行,如下所示。
      • 如果來源類型大於目的地類型,則會捨棄其「額外」最重要的位來截斷來源值。 然後,結果將被視為目的地類型的值。
      • 如果來源類型的大小與目的地類型相同,則來源值會視為目的地類型的值
  • 針對從 decimal 轉換成整數型別的轉換,來源值會四捨五入為零到最接近的整數值,而這個整數值會變成轉換的結果。 如果產生的整數值超出目的型別的範圍, System.OverflowException 則會擲回 。
  • 針對從 floatdouble 轉換成整數型別的轉換,此處理取決於轉換的溢位檢查內容 (~12.8.20) :
    • 在核取的內容中,轉換會繼續進行,如下所示:
      • 如果操作數的值是 NaN 或無限, System.OverflowException 則會擲回 。
      • 否則,來源操作數會四捨五入為零到最接近的整數值。 如果這個整數值在目的型別的範圍內,這個值就是轉換的結果。
      • 否則會擲回 System.OverflowException
    • 在未核取的內容中,轉換一律會成功,然後繼續進行,如下所示。
      • 如果操作數的值是 NaN 或無限,則轉換的結果是目的地類型的未指定值。
      • 否則,來源操作數會四捨五入為零到最接近的整數值。 如果這個整數值在目的型別的範圍內,這個值就是轉換的結果。
      • 否則,轉換的結果是目的地類型的未指定值。
  • 針對從 double 轉換為 float的轉換,值 double 會四捨五入為最 float 接近的值。 double如果值太小而無法表示為 float,則結果會變成零,且其符號與值相同。 如果值的大小 double 太大而無法表示為 float,則結果會變成無限大,其正負號與值相同。 double如果值為 NaN,則結果也會是 NaN。
  • 針對從 或轉換成 的轉換,來源值會轉換成decimal表示,並視需要四捨五入為最接近的數位(~8.3.8)。decimaldouble float
    • 如果來源值太小而無法表示為 decimal,則結果會變成零,如果 decimal 支援帶正負號的零值,則會保留原始值的正負號。
    • 如果來源值的大小太大而無法表示為 decimal,或該值為無限大,則如果小數表示法支援無限值,則結果會保留原始值的正負號,否則會擲回 System.OverflowException。
    • 如果來源值為 NaN,則如果十進位表示法支援 NaN,則結果會是 NaN;否則會擲回 System.OverflowException。
  • 對於從 decimal 轉換為 floatdouble的轉換,值 decimal 會四捨五入為最 double 接近或 float 值。 如果來源值的大小太大而無法在目標類型中表示,或該值為無限大,則結果會保留原始值的正負號。 如果來源值為 NaN,則結果為 NaN。 雖然此轉換可能會失去精確度,但絕不會導致擲回例外狀況。

注意:類型decimal不需要支持無限或 NaN 值,但可能這樣做;其範圍可能小於 和 double的範圍float,但不保證為 。 若 decimal 為沒有無限值或 NaN 值的表示法,且範圍小於 float,則從 decimal 轉換成 floatdouble 的結果永遠不會是無限或 NaN。 end note

10.3.3 明確列舉轉換

明確的列舉轉換如下:

  • sbytebyteshort、、intlongdoubleulongcharfloatushortuintdecimal 到任何enum_type。
  • 從任何enum_type到、、、shortushortint、、uint、、ulongcharlong、、floatdouble或 。 decimalbytesbyte
  • 從任何 enum_type 到任何其他 enum_type

將任何參與enum_type視為該enum_type的基礎型別,然後在產生的型別之間執行隱含或明確的數值轉換,即可處理兩種型別之間的明確列舉轉換。

範例:假設有 具有 和 基礎類型的 intenum_typeE,從 到 的轉換byteE會當做從 到 的明確數值轉換處理(~10.3.2),intbyte而從byte byte intE 到 的轉換會當做隱含數值轉換處理~10.2.3)。 end 範例

10.3.4 明確可為 Null 的轉換

明確可為 Null 的轉換是從明確和隱含預先定義的轉換衍生而來的可為 Null 轉換(~10.6.1)。

10.3.5 明確參考轉換

明確的參考轉換如下:

  • 從物件到任何其他 reference_type
  • 從任何class_typeS到任何class_type T,提供的 S 是的T基類。
  • 從任何class_type到任何interface_typeS T ,提供的 S 不是密封的,而且未實S作 。T
  • 從任何interface_type到任何class_type T ,提供的 T 不是密封或提供的T實作SS
  • 從任何 interface_typeS 到任何 interface_type T,提供的 S 不是衍生自 T
  • 從具有專案類型的array_typeS具有元素類型的SᵢTᵢarray_typeT,前提是下列所有專案都成立:
    • ST 只有在項目類型中才不同。 換句話說, ST 的維度數目相同。
    • 從到Tᵢ的明確參考轉換存在Sᵢ
  • System.Array 和其實作的介面,到任何 array_type
  • 從單一維度array_typeS[]System.Collections.Generic.IList<T>System.Collections.Generic.IReadOnlyList<T>及其基底介面,前提是有識別轉換或從 ST的明確參考轉換。
  • System.Collections.Generic.IList<S>System.Collections.Generic.IReadOnlyList<S>和其基底介面到單一維度陣列類型 T[],前提是有從 S 到 T 的識別轉換或明確參考轉換。
  • System.Delegate 和 它實作至任何 delegate_type的介面。
  • 如果參考型別的參考型S別有從 到參考型別的明確參考轉換S,而且T₀有從 到的身分識別轉換,則從 T₀ 參考型T別到T參考型T₀別。
  • 如果有從 到介面或委派型別的明確參考轉換,且可轉換成變異數或可轉換成 TT₀ T \18.2.3.3,則從參考型S別到介面或委派型TT₀別。S T₀
  • From D<S₁...Sᵥ> to D<T₁...Tᵥ> where D<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 類型的TT明確轉換存在。 轉換是藉由建立的對應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_typeI到任何non_nullable_value_type,其中從interface_typeI₀到non_nullable_value類型,以及從 I 轉換成I₀的身分識別轉換。
  • 從任何interface_typeI轉換成任何non_nullable_value_type,其中從interface_type到non_nullable_value_type I₀I₀ ,或是Ivariance_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_typeS包含執行表達式 ((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_typeboxed 值參考。 如果來源操作數是不相容物件的參考, System.InvalidCastException 則會擲回 。

10.3.8 涉及類型參數的明確轉換

對於已知為參考類型的type_parameterT~15.2.5),有下列明確的參考轉換(~10.3.5) 存在:

  • 從 的有效基類CTT ,以及從的任何基類CT
  • 從任何 interface_typeT
  • T 到提供的任何interface_typeI,還沒有從 到的隱含參考轉換TI
  • 從type_parameterU提供TT,取決於 U~15.2.5)。

    注意:由於已知是參考型別,因此,在 T 的範圍內 T,您運行時間類型一律會是參考型別,即使在編譯時期不知道是參考型別也一樣 Uend note

對於未知為參考型別(~15.2.5)的type_parameter T ,在編譯時期,涉及的下列轉換T會被視為非箱式轉換(~10.3.7)。 在運行時間,如果 T 是實值型別,則轉換會以 Unboxing 轉換的形式執行。 在運行時間,如果 T 是參考型別,則會將轉換當做明確的參考轉換或身分識別轉換來執行。

  • 從 的有效基類CTT ,以及從的任何基類CT

    注意:C 會是 、 或 System.Enum 類型的其中System.ObjectSystem.ValueType一種,否則T會已知為參考型別。 end note

  • 從任何 interface_typeT

對於未知為參考型別 (~15.2.5) 的type_parameter T ,存在下列明確轉換:

  • T 到提供的任何interface_typeI,還沒有從 隱含轉換成 T I。 此轉換包含從 到的隱含 Boxing 轉換 (~10.2.9),T後面接著從 到 的明確參考轉換。objectobjectI 在運行時間,如果 T 是實值型別,則會執行轉換做為 Boxing 轉換,後面接著明確參考轉換。 在運行時間,如果 T 是參考型別,則會將轉換當做明確的參考轉換來執行。
  • 從型別參數U提供TT,取決於 U~15.2.5)。 在運行時間,如果 T 是實值型別,而且 U 是參考型別,則會將轉換執行為 Unboxing 轉換。 在運行時間,如果 和 都是T實值型別,則 TU 一定是相同的型別,而且不會執行U轉換。 在運行時間,如果 T 是參考型別,則 U 一定也是參考型別,而且轉換會以明確的參考轉換或身分識別轉換的形式執行。

在所有情況下,規則可確保只有在運行時間轉換是從參考型別轉換成實值型別時,才會將轉換執行為 Unboxing 轉換。

上述規則不允許從不受限制的類型參數直接轉換成非介面類型,這可能令人吃驚。 此規則的原因是要防止混淆,並清楚說明這類轉換的語意。

範例:請考慮下列宣告:

class X<T>
{
    public static long F(T t)
    {
        return (long)t;         // Error
    }
}

如果允許直接明確轉換 tlong ,則可能會輕易預期會 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) 會在執行時間擲回例外狀況,因為 Boxed int 無法直接 long轉換成 。

end 範例

10.3.9 用戶定義的明確轉換

使用者定義的明確轉換是由選擇性的標準明確轉換所組成,後面接著執行使用者定義的隱含或明確轉換運算符,後面接著另一個選擇性的標準明確轉換。 評估使用者定義明確轉換的確切規則會在 \10.5.5說明。

10.4 標準轉換

10.4.1 一般

標準轉換是可在使用者定義的轉換中發生的預先定義轉換。

10.4.2 標準隱含轉換

下列隱含轉換會分類為標準隱含轉換:

標準隱含轉換會特別排除使用者定義的隱含轉換。

10.4.3 標準明確轉換

標準明確轉換全都是標準隱含轉換,再加上有相反標準隱含轉換的明確轉換子集。

注意:換句話說,如果標準隱含轉換從類型到類型B存在,則標準明確轉換會從A類型A轉換成類型B,從類型轉換成類型BAend note

10.5 使用者定義的轉換

10.5.1 一般

C# 允許使用者定義的轉換來增強預先定義的隱含和明確轉換。 使用者定義轉換是藉由在類別和結構類型中宣告轉換運算符 (~15.10.4) 來引進。

10.5.2 允許的使用者定義轉換

C# 只允許宣告特定使用者定義的轉換。 特別是,無法重新定義已經存在的隱含或明確轉換。

對於指定的來源類型和S目標型別,如果 ST 為可為 Null 的實值型T別,則讓 S₀T₀ 參考其基礎類型,否則S₀T₀ 分別等於 ST 。 只有在下列所有專案都成立時,才允許類別或結構宣告從來源類型 S 轉換成目標類型 T

  • S₀T₀ 是不同的類型。
  • S₀T₀ 是運算符宣告所在的類別或結構類型。
  • 也不是S₀ T₀ interface_type。
  • 排除使用者定義的轉換,轉換不存在從 ST 或從 T 到。S

套用至使用者定義轉換的限制是在 \15.10.4指定。

10.5.3 使用者定義轉換的評估

使用者定義的轉換會將來源表達式,其可能具有來源類型,轉換為另一個稱為目標類型的類型 使用者定義轉換的評估是以尋找 來源表達式和目標類型最特定的 使用者定義轉換運算元為中心。 此判斷分成數個步驟:

  • 尋找將考慮使用者定義轉換運算子的類別和結構集合。 如果來源類型存在,則此集合包含來源類型及其基類,以及目標型別及其基類。 基於這個目的,假設只有類別和結構可以宣告使用者定義運算符,而且非類別類型沒有基類。 此外,如果來源或目標類型是 Nullable-value-type,則會改用其基礎類型。
  • 從該類型的集合中,判斷哪些使用者定義和增益轉換運算符適用。 若要適用轉換運算符,可以執行從來源運算式到運算元操作數類型的標準轉換 (^10.4),而且可以執行從運算符結果型別到目標型別的標準轉換。
  • 從一組適用的使用者定義運算符,判斷哪一個運算符最明確。 一般而言,最特定的運算符是操作數類型「最接近」來源表達式的運算元,其結果類型與目標類型「最接近」。 使用者定義轉換運算元優先於提升轉換運算元。 建立最特定使用者定義轉換運算符的確切規則定義於下列子集內。

一旦識別出最特定的使用者定義轉換運算符之後,使用者定義轉換的實際執行最多需要三個步驟:

  • 首先,如果需要,執行從來源表達式到使用者定義或提升轉換運算符的操作數類型的標準轉換。
  • 接下來,叫用使用者定義的或提升轉換運算符來執行轉換。
  • 最後,如有需要,請執行從使用者定義轉換運算子的結果類型到目標類型的標準轉換。

使用者定義轉換的評估絕不牽涉到一個以上的使用者定義或提升轉換運算符。 換句話說,從型S別到型T別的轉換永遠不會先執行從 到 X 的使用者定義轉換S,然後從 執行使用者定義的轉換XT

  • 下列子集會提供使用者定義隱含或明確轉換評估的確切定義。 定義會使用下列詞彙:
  • 如果標準隱含轉換(~10.4.2)從類型存在到類型AB,而且如果兩者AB都沒有或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 成為該類型。
    • 如果 ST 為可為 Null 的實值型別,則 let Sᵢ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ₓ,則會執行從 ESₓ 的標準隱含轉換。
    • 叫用最特定的轉換運算元,以從 Sₓ 轉換成 Tₓ
    • 如果 Tₓ 不是 T,則會執行從 TₓT 的標準隱含轉換。

如果使用者定義的隱含轉換從 型S別的變數存在,則存在從型S別到 TT別的使用者定義隱含轉換。

10.5.5 使用者定義的明確轉換

使用者定義從表達式 E 到型 T 別的明確轉換,如下所示:

  • 判斷型 S別、 S₀T₀
    • 如果 E 具有類型,請讓 S 成為該類型。
    • 如果 ST 為可為 Null 的實值型別,則 let Sᵢ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的結合來源型別集合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
  • 在 中U尋找運算子最特定的目標類型Tₓ
    • 如果轉換成 U T的任何運算子,則 TₓT
    • 否則,如果轉換 U 至 所包含 T之型別的任何運算符,則 Tₓ 為這些運算子組合中最包含的類型。 如果找不到其中一個最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
    • 否則, Tₓ 是 中運算符 U之合併目標型別集合中最包含的類型。 如果找不到最包含的類型,則轉換模棱兩可,而且會發生編譯時期錯誤。
  • 尋找最特定的轉換運算子:
    • 如果 U 只包含一個從 轉換成 Sₓ Tₓ的使用者定義轉換運算子,則這是最特定的轉換運算符。
    • 否則,如果 U 只包含一個從 Sₓ 轉換為 Tₓ的增益轉換運算符,則這是最特定的轉換運算符。
    • 否則,轉換模棱兩可,而且會發生編譯時期錯誤。
  • 最後,套用轉換:
    • 如果 E 還沒有 型 Sₓ別 ,則會執行從 E 到 Sₓ 的標準明確轉換。
    • 叫用最特定的使用者定義轉換運算元,以從 Sₓ Tₓ轉換成 。
    • 如果 Tₓ 不是 T,則會執行從 TₓT 的標準明確轉換。

如果使用者定義的明確轉換從 型S別的變數存在,則存在從型S別到 TT別的使用者定義明確轉換。

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,後面接著從 TS 到的基礎轉換,後面接著從 換T行。T?
  • 如果可為 Null 的轉換是從 S 轉換為 T?,則會將轉換評估為基礎轉換 ST 後面接著從 換 T 行至 T?
  • 如果可為 Null 的轉換是從 S?T,則會將轉換評估為解除包裝 S?S 後面接著從 S T基礎轉換成 。

10.6.2 隨即轉換

假設使用者定義轉換運算符會從不可為 Null 的實值型S別轉換成不可為 Null 的實值型T別,則會有從轉換為 T?S? 的提升轉換運算元。 這個提升轉換運算符會執行從 到 的解除包裝S?,後面接著使用者定義轉換ST後面接著換TT?行至 ,不同之處在於 Null 值S?會直接轉換成 Null 值。T?S 提升轉換運算元具有與其基礎使用者定義轉換運算元相同的隱含或明確分類。

10.7 匿名函式轉換

10.7.1 一般

anonymous_method_expression或lambda_expression會分類為匿名函式(~12.19)。 表達式沒有類型,但可以隱含轉換成相容的委派類型。 某些 Lambda 運算式也可能隱含轉換成相容的表達式樹狀結構類型。

具體而言,匿名函 F 式與提供的委派類型 D 相容:

  • 如果 F 包含 anonymous_function_signature,則 DF 具有相同數目的參數。
  • 如果 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),則當 中的每個參數都得到 中DF對應參數F的類型時,主體是可隱含轉換成 T的有效表達式 (w.r.t .t .12)。
  • 如果的主體F是區塊,而且F是非異步且D具有非 void 傳回型別,或是F是 async 且D具有«TaskType»<T>傳回型T別,則當 中的每個參數F都得到 中對應參數FD的類型時,主體是有效的語句區塊 (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別 時xx + 1是可隱含轉換成 型別int的有效表達式。

同樣地,第二個指派成功將匿名函式轉換成委派類型 Func int,double>,因為 (of 類型int) 的結果x + 1會隱含轉換成 類型 double。<

不過,第三個指派是編譯時期錯誤,因為當 指定 類型時x,類型double的結果x + 1無法隱含轉換成 類型intdouble

第四個指派成功將匿名異步函式轉換成委派類型,因為 (屬於 類型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)可作為將匿名方法轉換成委派類型的替代語法。

從匿名函式產生的委派調用清單包含單一專案。 未指定委派的確切目標對象和目標方法。 特別是,不指定委派的目標物件是 nullthis 封入函式成員的值,還是一些其他物件。

允許將同一組擷取的外部變數實例轉換成相同委派類型的語意相同匿名函式,但不允許傳回相同的委派實例。 在此使用語意完全相同的詞彙,表示在所有情況下,匿名函式的執行都會產生相同的效果,因為有相同的自變數。 此規則允許將下列程式代碼優化。

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只有在EE包含至少一個適用於其一般形式的方法時,才與 至少包含一個適用於其一般形式的方法(\12.6.4.2)相容,且具有符合 的參數類型和修飾D詞的任何自變數清單 (\12.6.2),如下列所述。

下列說明從方法群組 E 轉換成委派類型的 D 編譯時間應用程式。

  • 選取單一方法M會對應至窗體E(A)的方法調用({12.8.10.2),並進行了下列修改:
    • 自變數清單是表達式清單A,每個運算式都會分類為變數,且在的parameter_listD,具有對應參數的類型和修飾詞(inoutref),但類型的參數除外dynamic,其中對應的表達式具有 類型object,而不是 dynamic
    • 考慮的候選方法只是那些以一般形式適用的方法,而且不會省略任何選擇性參數(~12.6.4.2)。 因此,如果候選方法僅適用於展開格式,或是其中一或多個選擇性參數在 中 D沒有對應的參數,則會忽略候選方法。
  • 如果 12.8.10.2 的演算法產生與 相容的單一最佳方法MD則會將轉換視為存在。
  • 如果選取的方法是實例方法 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,則實例表達式所計算的值會變成目標物件。 如果選取的方法是實例方法,而目標物件是 nullSystem.NullReferenceException 則會擲回 ,而且不會執行任何進一步的步驟。
    • 如果實例表達式是 value_type,則會執行 Boxing 作業(~10.2.9)將值轉換成物件,而這個物件會變成目標物件。
  • 否則,選取的方法是靜態方法呼叫的一部分,而委派的目標物件是 null
  • 委派類型的 D 委派實例是使用編譯時期所決定之方法的參考,以及上述計算之目標對象的參考,如下所示:
    • 允許轉換 (但非必要) 使用已經包含這些參考的現有委派執行個體。
    • 如果未重複使用現有的實例,則會建立新的實例 (~20.5)。 如果沒有足夠的記憶體可供設定新的實體, System.OutOfMemoryException 則會擲回 。 否則,實例會使用指定的參考初始化。