型拡張

型拡張 ("拡張" とも呼ばれます) は、以前に定義したオブジェクト型に新しいメンバーを追加するための機能ファミリです。 次の 3 つの機能があります。

  • 組み込みの型拡張
  • 省略可能な型拡張
  • 拡張メソッド

それぞれをさまざまなシナリオで使用でき、さまざまなトレードオフがあります。

構文

// 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.Summember 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

使用する場合、このコードでは、Extensions が開かれているか、スコープ内にある限り、SumIEnumerable<T> に対して定義されているように見えます。

この拡張を VB.NET コードで使用できるようにするには、アセンブリ レベルで追加の ExtensionAttribute が必要です。

module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()

その他の備考

型拡張には、次の属性もあります。

  • アクセス可能な任意の型を拡張できます。
  • 組み込みと省略可能な型拡張により、メソッドだけでなく、"任意の" メンバー型を定義できます。 そのため、たとえば、拡張プロパティも可能です。
  • 構文内の self-identifier トークンは、通常のメンバーと同様に、呼び出される型のインスタンスを表します。
  • 拡張メンバーは、静的メンバーまたはインスタンス メンバーにすることができます。
  • 型拡張の型変数は、宣言された型の制約と一致する必要があります。

型拡張には、次の制限もあります。

  • 型拡張は、仮想メソッドまたは抽象メソッドをサポートしていません。
  • 型拡張は、拡張としてオーバーライド メソッドをサポートしていません。
  • 型拡張は、静的に解決された型パラメーターをサポートしていません。
  • 省略可能な型拡張は、拡張としてのコンストラクターをサポートしていません。
  • 型の省略形に対して型拡張を定義することはできません。
  • 型拡張は byref<'T> には有効ではありません (ただし、宣言することはできます)。
  • 型拡張は属性には有効ではありません (ただし、宣言することはできます)。
  • 同じ名前の他のメソッドをオーバーロードする拡張を定義できますが、あいまいな呼び出しがある場合、F# コンパイラにより、非拡張メソッドが優先されます。

最後に、1 つの型に対して組み込みの型拡張が複数存在する場合、すべてのメンバーが一意である必要があります。 オプション型拡張の場合は、1 つの型に対する複数の型拡張が存在する場合でも、各メンバーに同じ名前を付けることができます。 クライアント コードで同じメンバー名が定義されている 2 つの異なるスコープを開いている場合にのみ、あいまいさに対するエラーが発生します。

関連項目