型別延伸模組

型別擴充 (也稱為 增強) 是一系列的功能,可讓您將新成員新增至先前定義的物件型別。 這三個功能包括:

  • 內建型別擴充
  • 選擇性型別擴充
  • 擴充方法

每個擴充都可以在不同的案例中使用,而且有不同的優缺點。

語法

// 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_Zerostatic 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# 編譯器會偏好非擴充方法。

最後,如果一個型別有多個內建型別擴充,則所有成員都必須是唯一的。 針對選擇性型別擴充,相同型別之不同型別擴充中的成員可以具有相同的名稱。 只有在用戶端程式代碼開啟兩個定義相同成員名稱的不同範圍時,才會發生模棱兩可的錯誤。

另請參閱