クラス (F#)

"クラス" は、オブジェクトを表す型であり、プロパティ、メソッド、イベントを持つことができます。

構文

// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...

解説

クラスは、.NET オブジェクト型の基本的な記述を表します。クラスは、F# でのオブジェクト指向プログラミングをサポートする主要な型の概念です。

上記の構文で、type-name は任意の有効な識別子です。 type-params では、省略可能なジェネリック型パラメーターを記述します。 これは、型パラメーター名と、山かっこ (<>) で囲まれた制約で構成されます。 詳細については、「ジェネリック」と「制約」をご覧ください。 parameter-list では、コンストラクターのパラメーターを記述します。 最初のアクセス修飾子は、型に関連しています。2 つ目は、プライマリ コンストラクターに関連しています。 どちらの場合も、既定値は public です。

クラスの基底クラスは、inherit キーワードを使用して指定します。 基底クラスのコンストラクターには、かっこで囲んだ引数を指定する必要があります。

let バインドを使用して、クラスに対してローカルなフィールドまたは関数値を宣言します。これは、let バインドの一般的な規則に従う必要があります。 do-bindings セクションには、オブジェクトの構築時に実行するコードを含めます。

member-list は、追加のコンストラクター、インスタンスと静的メソッドの宣言、インターフェイス宣言、抽象バインド、プロパティとイベントの宣言で構成されます。 これらについては、「メンバー」に説明があります。

省略可能な as キーワードと共に使用する identifier では、インスタンス変数 (自己識別子) の名前を指定します。これは、型定義の中で型のインスタンスを参照するために使用できます。 詳細については、このトピックで後述する「自己識別子」を参照してください。

定義の開始と終了をマークするキーワード classend は省略可能です。

相互に参照する型である相互再帰型は、相互再帰関数と同様に、and キーワードで結合します。 例については、セクション「相互再帰型」をご覧ください。

コンストラクター

コンストラクターは、クラス型のインスタンスを作成するコードです。 F# におけるクラスのコンストラクターの動作は、他の .NET 言語とは少し異なります。 F# のクラスには、常にプライマリ コンストラクターがあります。その引数を型名の後の parameter-list に記述し、その本体は、クラス宣言の先頭にある let (および let rec) バインドと、その後に続く do バインドで構成されます。 プライマリ コンストラクターの引数は、クラス宣言全体がスコープになります。

追加のコンストラクターは、次のように new キーワードを使用してメンバーを追加することにより追加できます。

new(argument-list) = constructor-body

新しいコンストラクターの本体では、クラス宣言の先頭で指定されているプライマリ コンストラクターを呼び出す必要があります。

この概念を説明する例を次に示します。 次のコードでは、MyClass に 2 つのコンストラクターがあります。2 つの引数を受け取るプライマリ コンストラクターと、引数を受け取らないもう 1 つのコンストラクターです。

type MyClass1(x: int, y: int) =
   do printfn "%d %d" x y
   new() = MyClass1(0, 0)

let および do バインド

クラス定義内の let および do バインドは、プライマリ クラス コンストラクターの本体を形成し、したがってクラスのインスタンスが作成されるたびに実行されます。 let バインドが関数の場合は、メンバーにコンパイルされます。 let バインドが、どの関数またはメンバーでも使用されていない値である場合は、コンストラクターに対してローカルな変数にコンパイルされます。 それ以外の場合は、クラスのフィールドにコンパイルされます。 これに続く do 式は、プライマリ コンストラクターにコンパイルされ、すべてのインスタンスに対して初期化コードを実行します。 追加のコンストラクターでは常にプライマリ コンストラクターが呼び出されるため、let バインドと do バインドは、どのコンストラクターが呼び出されるかに関係なく、常に実行されます。

let バインドによって作成されるフィールドには、そのクラスのメソッドとプロパティを通じてアクセスできます。ただし、静的メソッドがインスタンス変数をパラメーターとして受け取る場合でも、静的メソッドからそれらにアクセスすることはできません。 また、自己識別子 (存在する場合) を使用してアクセスすることはできません。

自己識別子

"自己識別子" は、現在のインスタンスを表す名前です。 自己識別子は、C# または C++ の this キーワード、または Visual Basic の Me に似ています。 自己識別子は、クラス定義全体を自己識別子のスコープにするか、または個別のメソッドのみにするかに応じて、2 つの異なる方法で定義できます。

クラス全体のための自己識別子を定義するには、コンストラクターのパラメーター リストの終わりかっこの後に as キーワードを使用し、識別子の名前を指定します。

1 つのメソッドのためだけの自己識別子を定義するには、自己識別子を、メンバー宣言内のメソッド名の直前にピリオド (.) を区切り記号として指定します。

次のコード例は、自己識別子を作成する 2 つの方法を示しています。 最初の行では、as キーワードを使用して自己識別子を定義しています。 5 行目では、識別子 this を使用して、スコープがメソッド PrintMessage に限定された自己識別子を定義しています。

type MyClass2(dataIn) as self =
    let data = dataIn
    do
        self.PrintMessage()
    member this.PrintMessage() =
        printf "Creating MyClass2 with Data %d" data

他の .NET 言語とは異なり、自己識別子には自由に名前を付けることができます。selfMethis などの名前に限定されません。

as キーワードを使用して宣言した自己識別子は、基底コンストラクターの後まで初期化されません。 したがって、基底コンストラクターの前または内部で使用すると、実行時に System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized. が発生します。 自己識別子は、let バインドや do バインド内など、基底コンストラクターの後であれば自由に使用できます。

ジェネリック型の型パラメーター

ジェネリック型パラメーターは、山かっこ (<>) で囲み、単一引用符の後に識別子を続ける形式で指定します。 複数のジェネリック型パラメーターはコンマで区切ります。 ジェネリック型パラメーターは、宣言全体がスコープになります。 ジェネリック型パラメーターを指定する方法を次のコード例に示します。

type MyGenericClass<'a> (x: 'a) =
   do printfn "%A" x

型引数は、型が使用されるときに推論されます。 次のコードでは、推論される型はタプルのシーケンスです。

let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )

継承の指定

inherit 句では、直接基底クラス (存在する場合) を指定します。 F# では、直接基底クラスを 1 つだけ使用できます。 クラスによって実装されるインターフェイスは、基底クラスとは見なされません。 インターフェイスについては、トピック「インターフェイス」をご覧ください。

派生クラスから基底クラスのメソッドとプロパティにアクセスするには、識別子として言語キーワード base を使用し、その後にピリオド (.) とメンバーの名前を指定します。

詳細については、「継承」を参照してください。

メンバー セクション

このセクションでは、静的またはインスタンス メソッド、プロパティ、インターフェイスの実装、抽象メンバー、イベント宣言、追加のコンストラクターを定義できます。 let と do のバインドは、このセクションには記述できません。 メンバーとしてはクラスに加えてさまざまな F# 型を追加できるため、別個のトピック「メンバー」で説明します。

相互再帰型

相互に参照する型を循環的な方法で定義する場合は、and キーワードを使用して型定義を連結します。 次のように、最初の定義以外のすべてで type キーワードを and キーワードに置き換えます。

open System.IO

type Folder(pathIn: string) =
  let path = pathIn
  let filenameArray : string array = Directory.GetFiles(path)
  member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray

and File(filename: string, containingFolder: Folder) =
   member this.Name = filename
   member this.ContainingFolder = containingFolder

let folder1 = new Folder(".")
for file in folder1.FileArray do
   printfn "%s" file.Name

出力は、現在のディレクトリにあるすべてのファイルの一覧です。

クラス、共用体、レコード、構造体を使用するべき状況

選択可能なさまざまな種類の型があるため、それぞれの型の設計目的を十分に理解し、特定の状況に適した型を選択する必要があります。 クラスは、オブジェクト指向プログラミングのコンテキストで使用するように設計されています。 オブジェクト指向プログラミングは、.NET Framework 用に記述されたアプリケーションで使用される最も重要なパラダイムです。 F# コードを .NET Framework または別のオブジェクト指向ライブラリと密接に連携させる必要がある場合、特に UI ライブラリなどのオブジェクト指向の型システムから拡張する必要がある場合は、おそらくクラスが適切です。

オブジェクト指向のコードと密接に相互運用していない場合、または自己完結型のコードを記述していて、オブジェクト指向のコードと頻繁にやり取りする必要がない場合は、クラス、レコード、判別共用体の組み合わせの使用を検討する必要があります。 十分に考え抜かれた 1 つの判別共用体を適切なパターン マッチング コードと併用する方法は、オブジェクト階層のシンプルな代替手段としてよく使用されます。 判別共用体の詳細については、「判別共用体」をご覧ください。

レコードにはクラスよりもシンプルであるという利点がありますが、型に対する要求がそのシンプルな機能で実現できることを超える場合、レコードは適切ではありません。 レコードは、基本的には値の単純な集合体であり、カスタム アクションを実行できる個別のコンストラクターや、非表示フィールドや、継承またはインターフェイスの実装はありません。 レコードにプロパティやメソッドなどのメンバーを追加して、より複雑な動作をさせることはできますが、レコードに格納されるフィールドが値の単純な集合体であることに変わりはありません。 レコードの詳細については、「レコード」をご覧ください。

構造体は、データの小さな集合体に使用する場合に役立ちますが、.NET 値型であるという点でクラスおよびレコードと異なります。 クラスとレコードは .NET 参照型です。 値型と参照型のセマンティクスは、値型が値渡しされるという点で異なります。 これは、パラメーターとして渡された場合、または関数から返された場合に、ビットごとにコピーされることを意味します。 また、これらはスタックに格納されるか、親オブジェクトの内部に埋め込まれます (フィールドで使用される場合)。ヒープ上にある専用の別個の場所に格納されるわけではありません。 したがって、ヒープにアクセスするオーバーヘッドが問題になる場合、頻繁にアクセスされるデータには構造体が適しています。 構造体の詳細については、「構造体」をご覧ください。

関連項目