轉型和轉換 (F#)
本文描述 F# 中對型別轉換的支援。
算術型別
F# 提供各種簡單型別之間的算術轉換運算子,例如整數和浮點數型別之間的算術轉換。 整數和 char 轉換運算子具有已檢查和未檢查表單;浮點數運算子和 enum
轉換運算子則不包含。 未檢查表單定義於 FSharp.Core.Operators
中,已檢查表單定義於 FSharp.Core.Operators.Checked
中。 已檢查表單會檢查溢位,如果產生的值超過目標型別的限制,則會產生執行階段例外狀況。
每個運算子的名稱都與目的地型別的名稱相同。 例如,在下列程式碼中明確標註型別,byte
出現兩個不同的意義。 第一個出現項目是型別,而第二個則是轉換運算子。
let x : int = 5
let b : byte = byte x
下表顯示 F# 中定義的轉換運算子。
運算子 | 描述 |
---|---|
byte |
轉換為位元組,8 位元不帶正負號的型別。 |
sbyte |
轉換為帶正負號的位元組。 |
int16 |
轉換為 16 位元帶正負號的整數。 |
uint16 |
轉換為 16 位元不帶正負號的整數。 |
int32, int |
轉換為 32 位元帶正負號的整數。 |
uint32 |
轉換為 32 位元不帶正負號的整數。 |
int64 |
轉換為 64 位元帶正負號的整數。 |
uint64 |
轉換為 64 位元不帶正負號的整數。 |
nativeint |
轉換為原生整數。 |
unativeint |
轉換為不帶正負號的原生整數。 |
float, double |
轉換為 64 位元雙精確度 IEEE 浮點數。 |
float32, single |
轉換為 32 位元單精確度 IEEE 浮點數。 |
decimal |
轉換為 System.Decimal 。 |
char |
轉換為 System.Char ,這是 Unicode 字元。 |
enum |
轉換為列舉型別。 |
除了內建簡單型別之外,您還可以將這些運算子與實作 op_Explicit
的型別搭配使用,或具有適當特徵標記的 op_Implicit
方法。 例如,int
轉換運算子會與提供靜態方法 op_Explicit
的任何型別搭配運作,該型別接受型別作為參數並傳回 int
。 對於方法無法由傳回型別多載的一般規則,您可以針對 op_Explicit
和 op_Implicit
執行此動作。
列舉型別
enum
運算子是一個泛型運算子,採用一個型別參數,代表要轉換的目標 enum
型別。 當轉換成列舉型別時,型別推斷會嘗試判斷您要轉換的 enum
型別。 在下列範例中,變數 col1
不會明確標註,但會從稍後的相等測試推斷其型別。 因此,編譯器可以推算您要轉換成 Color
列舉。 或者,您也可以提供型別註釋,如下列範例所示的 col2
。
type Color =
| Red = 1
| Green = 2
| Blue = 3
// The target type of the conversion cannot be determined by type inference, so the type parameter must be explicit.
let col1 = enum<Color> 1
// The target type is supplied by a type annotation.
let col2 : Color = enum 2
您也可以明確地將目標列舉型別指定為型別參數,如下列程式碼所示:
let col3 = enum<Color> 3
請注意,只有在列舉的基礎型別與要轉換的型別相容時,列舉轉換才會運作。 在下列程式碼中,轉換因為 int32
與 uint32
之間的不符而無法編譯。
// Error: types are incompatible
let col4 : Color = enum 2u
如需詳細資訊,請參閱列舉。
轉換物件型別
物件階層中型別之間的轉換是物件導向程式設計的基礎。 轉換有兩種基本型別:向上轉換 (向上轉型) 以及向下轉換 (向下轉型)。 向上轉型階層表示從衍生物件參考轉換成基底物件參考。 只要基底類別位於衍生類別的繼承階層中,這類轉換就保證能夠運作。 只有在物件實際上是正確目的地 (衍生) 型別的執行個體時,或從目的地型別所衍生型別的執行個體時,從基底物件參考將階層向下轉型為衍生物件參考才會成功。
F# 提供這些轉換型別的運算子。 :>
運算子會向上轉換階層,而 :?>
運算子則會向下轉換階層。
向上轉型
在許多物件導向語言中,向上轉型是隱含的;在 F# 中規則稍有不同。 當您將引數傳遞至物件型別上的方法時,會自動套用向上轉型。 不過,對於模組中的 let-bound 函式,除非參數型別宣告為彈性型別,否則不會自動進行向上轉型。 如需詳細資訊,請參閱彈性型別。
:>
運算子會執行靜態轉換,這表示轉換成功與否取決於編譯時間。 如果使用 :>
編譯的轉換成功,則是有效的轉換,而且在執行階段不會失敗。
您也可以使用 upcast
運算子來執行這類轉換。 下列運算式會指定階層的向上轉換:
upcast expression
當您使用 upcast 運算子時,編譯器會嘗試推斷您要從內容轉換的目標型別。 如果編譯器無法判斷目標型別,編譯器會報告錯誤。 可能需要型別註釋。
向下轉型
:?>
運算子會執行動態轉換,這表示轉換成功與否取決於執行階段。 在編譯時間不會檢查使用 :?>
運算子的轉換;但在執行階段,會嘗試轉換成指定型別。 如果物件與目標型別相容,轉換就會成功。 如果物件與目標型別不相容,執行階段就會引發 InvalidCastException
。
您也可以使用 downcast
運算子來執行動態型別轉換。 下列運算式會指定將階層向下轉換成從程式內容推斷的型別:
downcast expression
對於 upcast
運算子,如果編譯器無法從內容推斷特定目標型別,則會報告錯誤。 可能需要型別註釋。
下列程式碼說明如何使用 :>
和 :?>
運算子。 此程式碼說明當您知道轉換將會成功時,:?>
是最好使用的運算子,因為其會在轉換失敗時擲回 InvalidCastException
。 如果您不知道轉換將會成功,使用 match
運算式的型別測試會比較好,因為其可避免產生例外狀況的額外負荷。
type Base1() =
abstract member F : unit -> unit
default u.F() =
printfn "F Base1"
type Derived1() =
inherit Base1()
override u.F() =
printfn "F Derived1"
let d1 : Derived1 = Derived1()
// Upcast to Base1.
let base1 = d1 :> Base1
// This might throw an exception, unless
// you are sure that base1 is really a Derived1 object, as
// is the case here.
let derived1 = base1 :?> Derived1
// If you cannot be sure that b1 is a Derived1 object,
// use a type test, as follows:
let downcastBase1 (b1 : Base1) =
match b1 with
| :? Derived1 as derived1 -> derived1.F()
| _ -> ()
downcastBase1 base1
因為泛型運算子 downcast
和 upcast
依賴型別推斷來判斷引數和傳回型別,所以您可以將先前程式碼範例中的 let base1 = d1 :> Base1
取代為 let base1: Base1 = upcast d1
。
需要型別註釋,因為 upcast
本身無法判斷基底類別。
隱含向上轉型轉換
隱含向上轉型會在下列情況下插入:
提供參數給具有已知具名型別的函式或方法時。 這包括當計算運算式或配量之類的建構變成方法呼叫時。
指派給具有已知具名型別的記錄欄位或屬性或是變動時。
當
if/then/else
或match
運算式的分支具有另一個分支所引發已知目標型別或整體已知型別時。當清單、陣列或序列運算式的元素具有已知目標型別時。
例如,請考慮下列程式碼:
open System
open System.IO
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt")
在這裡,條件式的分支會分別計算 TextReader
和 StreamReader
。 在第二個分支上,已知目標型別是來自方法上型別註釋以及來自第一個分支的 TextReader
。 這表示第二個分支上不需要任何向上轉型。
若要在每次使用其他隱含向上轉型時顯示警告,您可以啟用警告 3388 (/warnon:3388
或屬性 <WarnOn>3388</WarnOn>
)。
隱含數值轉換
在大部分情況下,F# 會透過轉換運算子使用數值型別的明確擴展。 例如,當來源或目的地型別未知時,大部分數值型別都需要明確擴展,例如 int8
或 int16
,或從 float32
到 float64
。
不過,在與隱含向上轉型相同的情況下,允許 32 位元整數擴展為 64 位元整數的隱含擴展。 例如,請考慮典型的 API 圖形:
type Tensor(…) =
static member Create(sizes: seq<int64>) = Tensor(…)
可以使用 int64 的整數常值:
Tensor.Create([100L; 10L; 10L])
或 int32 的整數常值:
Tensor.Create([int64 100; int64 10; int64 10])
當來源和目的地型別在型別推斷期間已知時,就會自動進行 int32
到 int64
、int32
到 nativeint
,以及 int32
到 double
的擴展。 因此在上述範例的情況下,可以使用 int32
常值:
Tensor.Create([100; 10; 10])
您也可以選擇性地啟用警告 3389 (/warnon:3389
或屬性 <WarnOn>3389</WarnOn>
),在每次使用隱含數值擴展時顯示警告。
.NET 樣式隱含轉換
.NET API 可讓 op_Implicit
靜態方法的定義在型別之間提供隱含轉換。 將引數傳遞至方法時,會在 F# 程式碼中自動套用這些項目。 例如,請考慮下列程式碼對 op_Implicit
方法進行明確呼叫:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")
當來源運算式和目標型別可以使用型別時,會針對引數運算式自動套用 .NET 樣式 op_Implicit
轉換:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")
您也可以選擇性地啟用警告 3395 (/warnon:3395
或屬性 <WarnOn>3395</WarnOn>
),在每次使用 .NET 樣式隱含轉換時顯示警告。
在與隱含向上轉型相同的情況下,也會針對非方法引數運算式自動套用 .NET 樣式 op_Implicit
轉換。 不過,當廣泛使用或不當使用時,隱含轉換可能會與型別推斷互動不佳,並導致難以理解的程式碼。 基於這個理由,一律會在非引數位置中使用時產生警告。
若要在每次針對非方法引數使用 .NET 樣式隱含轉換時顯示警告,您可以啟用警告 3391 (/warnon:3391
或屬性 <WarnOn>3391</WarnOn>
)。
與轉換相關的警告摘要
為使用隱含轉換提供下列選擇性警告:
/warnon:3388
(其他隱含向上轉型)/warnon:3389
(隱含數值擴展)/warnon:3391
(非方法引數上的op_Implicit
,預設為開啟)/warnon:3395
(方法引數上的op_Implicit
)