型拡張 (F#)
型拡張を使用すると、定義済みのオブジェクト型に新しいメンバーを追加できます。
// Intrinsic extension.
type typename with
member self-identifier.member-name =
body
...
[ end ]
// Optional extension.
type typename with
member self-identifier.member-name =
body
...
[ end ]
解説
型拡張には 2 つの形式があり、それぞれで構文と動作が若干異なります。 固有拡張は、拡張対象の型と同じ名前空間またはモジュール、同じソース ファイル、および同じアセンブリ (DLL または実行可能ファイル) 内で使用する拡張です。 任意拡張は、拡張対象の型の元のモジュール、名前空間、またはアセンブリの外部で使用する拡張です。 リフレクションで型をチェックするときは、その型に対して固有拡張が使用されますが、任意拡張は使用されません。 任意拡張はモジュール内で使用する必要があり、拡張を含むモジュールが開いている場合のスコープ内でのみ使用できます。
前の構文の typename は、拡張対象の型を表しています。 アクセスできる型はいずれも拡張できますが、型の名前が、型略称ではなく、実際の型の名前である必要があります。 1 つの型拡張に複数のメンバーを定義できます。 self-identifier は、通常のメンバーの場合と同様に、呼び出し先オブジェクトのインスタンスを表します。
軽量構文では、end キーワードを省略できます。
型拡張で定義されたメンバーは、クラス型の他のメンバーと同じように使用できます。 他のメンバーと同様に、これらのメンバーは静的メンバーまたはインスタンス メンバーになることができます。 型拡張で定義されたメソッドは拡張メソッド、型拡張で定義されたプロパティは拡張プロパティなどのように呼ばれます。 任意拡張のメンバーはコンパイルされると、静的メンバーになります。このメンバーに対する最初のパラメーターとして、オブジェクト インスタンスが暗黙で渡されます。 ただし、これらのメンバーは、宣言された方法に応じてインスタンス メンバーまたは静的メンバーと同じように動作します。 暗黙の拡張メンバーは型のメンバーとして含まれるため、無制限に使用できます。
拡張メソッドは、仮想メソッドまたは抽象メソッドになることができません。 拡張メソッドで同じ名前の他のメソッドをオーバーロードできますが、コンパイラは、あいまいな呼び出しの場合は非拡張メソッドを優先します。
1 つの型に対して複数の組み込み型拡張が存在する場合、すべてのメンバーが一意である必要があります。 オプション型拡張の場合は、1 つの型に対する複数の型拡張が存在する場合でも、各メンバーに同じ名前を付けることができます。 クライアント コードで同じメンバー名が定義されている 2 つの異なるスコープを開いている場合にのみ、あいまいさに対するエラーが発生します。
次の例では、モジュール内の型に対して組み込み型拡張を使用しています。 モジュールの外部のクライアント コードでは、型の通常のメンバーとまったく同様に型拡張が使用されます。
module MyModule1 =
// Define a type.
type MyClass() =
member this.F() = 100
// Define type extension.
type MyClass with
member this.G() = 200
module MyModule2 =
let function1 (obj1: MyModule1.MyClass) =
// Call an ordinary method.
printfn "%d" (obj1.F())
// Call the extension method.
printfn "%d" (obj1.G())
組み込み型拡張を使用すると、型の定義を複数のセクションに分割できます。 この機能は、コンパイラが生成したコードとユーザーが作成したコードを別にしておく場合、複数のユーザーが作成したコードをグループ化する場合、複数の機能に関連付けられているコードをグループ化する場合など、大規模な型定義を管理する場合に便利です。
次の例では、オプション型拡張を使用して System.Int32 型を拡張し、FromString 拡張メソッドで静的メンバーの Parse を呼び出します。 testFromString メソッドで、新しいメンバーがインスタンス メンバーと同じように呼び出されているのがわかります。
// Define a new member method FromString on the type Int32.
type System.Int32 with
member this.FromString( s : string ) =
System.Int32.Parse(s)
let testFromString str =
let mutable i = 0
// Use the extension method.
i <- i.FromString(str)
printfn "%d" i
testFromString "500"
IntelliSense で新しいインスタンス メンバーを Int32 型の他のメソッドと同じように使用できるのは、拡張を含むモジュールが開いている場合、または、拡張を含むモジュールは開いていないがスコープ内にある場合だけです。
ジェネリック拡張メソッド
F# 3.1 以前では、F# コンパイラはジェネリック型変数、配列型、タプル型、または "この" パラメーターとしての F# 関数型を伴う、C# スタイル拡張メソッドの使用をサポートしていませんでした。 F# 3.1 ではこれらの拡張メンバーの使用をサポートします。
たとえば、F# 3.1 コードでは、次の C# の構文に似ているシグネチャを使用する拡張メソッドを使用できます。
static member Method<T>(this T input, T other)
この方法はジェネリック型パラメーターが制約される場合に特に役立ちます。 さらに、F# コードでこのような拡張メンバーを宣言し、追加の、意味的に豊富な一連の拡張メソッドを定義できます。 F# では、通常、次の例に示すように拡張メンバーを定義します。
type seq<’T> with
/// Repeat each element of the sequence n times
member xs.RepeatElements(n: int) =
seq { for x in xs do for i in 1 .. n do yield x }
ただし、ジェネリック型の場合、型変数は、制約されない可能性があります。 この制限を回避するために、F# で C# スタイル拡張メンバーを宣言できます。 この種の宣言と F# のインライン機能を組み合わせると、拡張機能メンバーとしてジェネリックなアルゴリズムを示すことができます。
次の宣言を検討します。
[<Extension>]
type ExtraCSharpStyleExtensionMethodsInFSharp () =
[<Extension>]
static member inline Sum(xs: seq<’T>) = Seq.sum xs
この宣言を使用すると、次の例のようなコードを記述できます。
let listOfIntegers = [ 1 .. 100 ]
let listOfBigIntegers = [ 1I to 100I ]
let sum1 = listOfIntegers.Sum()
let sum2 = listOfBigIntegers.Sum()
このコードでは、同じジェネリックな算術コードが単一の拡張メンバーを定義することによって、オーバーロードすることなく 2 種類のリストに適用されます。