型別延伸模組
型別擴充 (也稱為 增強) 是一系列的功能,可讓您將新成員新增至先前定義的物件型別。 這三個功能包括:
- 內建型別擴充
- 選擇性型別擴充
- 擴充方法
每個擴充都可以在不同的案例中使用,而且有不同的優缺點。
語法
// Intrinsic and optional extensions
type typename with
member self-identifier.member-name =
body
...
// Extension methods
open System.Runtime.CompilerServices
[<Extension>]
type Extensions() =
[<Extension>]
static member extension-name (ty: typename, [args]) =
body
...
內建型別擴充
內建型別擴充是擴充使用者定義型別的型別擴充。
內建型別擴充必須定義在相同檔案中,以及在與其擴充的型別相同的命名空間或模組中。 任何其他定義都會導致其成為選擇性型別擴充。
內建型別擴充有時是將功能與型別宣告分開的更簡潔方式。 下列範例示範如何定義內建型別擴充:
namespace Example
type Variant =
| Num of int
| Str of string
module Variant =
let print v =
match v with
| Num n -> printf "Num %d" n
| Str s -> printf "Str %s" s
// Add a member to Variant as an extension
type Variant with
member x.Print() = Variant.print x
使用型別擴充可讓您分隔下列各項:
Variant
型別的宣告- 根據其「圖形」列印
Variant
類別的功能 - 使用物件樣式
.
標記法存取列印功能的方法
這是將所有項目定義為 Variant
上的成員的替代方式。 雖然這原本不是更好的方法,但是在某些情況下,可能是更簡潔的功能表示法。
內建型別擴充會編譯為其增強型別的成員,並在反映檢查型別時出現在型別上。
選擇性型別擴充
選擇性型別擴充是出現在所延伸型別的原始模組、命名空間或元件外部的擴充。
選擇性型別擴充對於擴充您尚未自行定義的型別很有用。 例如:
module Extensions
type IEnumerable<'T> with
/// Repeat each element of the sequence n times
member xs.RepeatElements(n: int) =
seq {
for x in xs do
for _ in 1 .. n -> x
}
只要 Extensions
模組是在您正在使用的範圍中開啟,您現在可以如同其為 IEnumerable<T> 的成員一般存取 RepeatElements
。
透過反映檢查時,選擇性擴充不會出現在擴充型別上。 選擇性擴充必須位於模組中,而且只有在包含擴充的模組已開啟或位於範圍內時,才會位於範圍內。
選擇性擴充成員會編譯為靜態成員,其物件執行個體會隱含傳遞為第一個參數。 不過,根據其宣告方式,其作用就如同是執行個體成員或靜態成員一樣。
C# 或 Visual Basic 取用者也看不到選擇性擴充成員。 只能在其他 F# 程式碼中取用。
內建和選擇性型別擴充的泛型限制
您可以在型別變數受限制的泛型型別上宣告型別擴充。 需求是擴充宣告的條件約束符合宣告型別的條件約束。
不過,即使宣告型別與型別擴充之間的條件約束相符,還是可由擴充成員主體推斷條件約束,而該條件約束會對型別參數施加與宣告型別不同的需求。 例如:
open System.Collections.Generic
// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
member this.Sum() = Seq.sum this
沒有方法可讓此程式碼與選擇性型別擴充搭配使用:
- 同樣地,
Sum
成員在'T
(static member get_Zero
和static member (+)
) 上具有與型別擴充所定義條件約束不同的條件約束。 - 將型別擴充修改為具有與
Sum
相同的條件約束,不再符合IEnumerable<'T>
上定義的條件約束。 - 將
member this.Sum
變更為member inline this.Sum
會產生型別條件約束不相符的錯誤。
所需的靜態方法是「空間中的浮點數」,而且可以呈現為擴充型別的方式。 這是需要擴充方法的地方。
擴充方法
最後,擴充方法 (有時稱為「C# 樣式擴充成員」) 可以在 F# 中宣告為類別上的靜態成員方法。
當您想要在將會限制型別變數的泛型型別上定義擴充時,擴充方法很有用。 例如:
namespace Extensions
open System.Collections.Generic
open System.Runtime.CompilerServices
[<Extension>]
type IEnumerableExtensions =
[<Extension>]
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs
使用時,此程式碼會使其看起來像 Sum
是在 IEnumerable<T> 上定義,只要 Extensions
已開啟或位於範圍中即可。
若要讓 VB.NET 程式碼可以使用擴充,組建層級需要額外的 ExtensionAttribute
:
module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()
其他備註
型別擴充也有下列屬性:
- 任何可以存取的型別都可以擴充。
- 內建和選擇性型別擴充可以定義 任何 成員類型,而不只是方法。 因此舉例來說,您也可以使用擴充屬性。
- 語法中的
self-identifier
語彙基元代表所叫用型別的執行個體,就像一般成員一樣。 - 擴充成員可以是靜態或執行個體成員。
- 型別擴充上的型別變數必須符合宣告型別的條件約束。
型別擴充也有下列限制:
- 型別擴充不支援虛擬或抽象方法。
- 型別擴充不支援覆寫方法作為增強。
- 型別擴充不支援靜態解析的型別參數。
- 選擇性型別擴充不支援建構函式作為增強。
- 型別擴充無法在型別縮寫上定義。
- 型別擴充對
byref<'T>
無效 (雖然可以宣告)。 - 型別擴充對屬性無效 (雖然可以宣告)。
- 您可以定義擴充,以多載相同名稱的其他方法,但如果有模棱兩可的呼叫,F# 編譯器會偏好非擴充方法。
最後,如果一個型別有多個內建型別擴充,則所有成員都必須是唯一的。 針對選擇性型別擴充,相同型別之不同型別擴充中的成員可以具有相同的名稱。 只有在用戶端程式代碼開啟兩個定義相同成員名稱的不同範圍時,才會發生模棱兩可的錯誤。