構造体

"構造体" はコンパクトなオブジェクト型であり、データが少量で動作が単純な型のクラスよりも効率が良い場合があります。

構文

[ attributes ]
type [accessibility-modifier] type-name =
    struct
        type-definition-elements-and-members
    end
// or
[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
    type-definition-elements-and-members

解説

構造体は "値型" です。つまり、スタック上に直接格納されるか、またはフィールドあるいは配列の要素として使用されている場合は親の型にインラインで格納されます。 クラスやレコードとは異なり、構造体のセマンティクスは値渡しです。 これは、主にアクセスおよびコピーが頻繁に行われるデータの小規模な集約に有用であることを意味します。

上記の構文では、2 つの形式が示されています。 1 番目のものは軽量構文ではないものの、頻繁に使用されます。これは、struct キーワードと end キーワードを使用する場合に、2 番目のものに出現する StructAttribute 属性を省略できるためです。 StructAttribute は省略して単に Struct とすることができます。

上記の構文の type-definition-elements-and-members は、メンバーの宣言と定義を表しています。 構造体にはコンス トラクター、可変フィールド、および不変フィールドを含めることができ、メンバーとインターフェイス実装を宣言できます。 詳細については、「メンバー」を参照してください。

構造体は、継承に参加することも、let または do バインディングを含めることも、それ自身の型のフィールドを再帰的に含めることもできません (ただし、それ自身の型を参照する参照セルを含めることができます)。

構造体では let バインディングは使用できないため、val キーワードを使用して構造体のフィールドを宣言する必要があります。 val キーワードではフィールドとその型が定義されますが、初期化は実行できません。 代わりに、val 宣言がゼロまたは null に初期化されます。 このため、暗黙のコンストラクター (宣言で構造体名の直後に指定されるパラメーター) を含む構造体では、val 宣言に DefaultValue 属性で注釈を付ける必要があります。 定義されたコンストラクターを含む構造体でも、ゼロ初期化がサポートされます。 したがって、DefaultValue 属性は、このようなゼロ値がフィールドに対して有効であることの宣言になります。 構造体の暗黙的なコンストラクターでは動作は実行されません。これは、let バインディングと do バインディングがその型では許可されていないためですが、渡された暗黙のコンストラクターのパラメーター値はプライベート フィールドとして使用できます。

明示的なコンストラクターにフィールド値の初期化が含まれる場合があります。 明示的なコンストラクターを含む構造体がある場合も、ゼロ初期化がサポートされます。ただし、明示的なコンストラクターと競合するため、DefaultValue 宣言では val 属性を使用しません。 val 宣言の詳細については、「明示的なフィールド: val キーワード」を参照してください。

構造体では属性およびアクセシビリティ修飾子が許可されており、その他の型と同じ規則に従います。 詳細については、「属性」と「アクセス制御」を参照してください。

次のコード例は構造体の定義を示しています。

// In Point3D, three immutable values are defined.
// x, y, and z will be initialized to 0.0.
type Point3D =
    struct
        val x: float
        val y: float
        val z: float
    end

// In Point2D, two immutable values are defined.
// It also has a member which computes a distance between itself and another Point2D.
// Point2D has an explicit constructor.
// You can create zero-initialized instances of Point2D, or you can
// pass in arguments to initialize the values.
type Point2D =
    struct
        val X: float
        val Y: float
        new(x: float, y: float) = { X = x; Y = y }

        member this.GetDistanceFrom(p: Point2D) =
            let dX = (p.X - this.X) ** 2.0
            let dY = (p.Y - this.Y) ** 2.0
            
            dX + dY
            |> sqrt
    end

ByRefLike 構造体

byref に似たセマンティクスに従うことができる独自の構造体を定義することができます。詳細については、「byref」を参照してください。 これは IsByRefLikeAttribute 属性を使用して行われます。

open System
open System.Runtime.CompilerServices

[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
    member x.Count1 = count1
    member x.Count2 = count2

IsByRefLikeStruct を意味するものではありません。 どちらも型に存在する必要があります。

F# の "byref に似た" 構造体は、スタックバインド値の型です。 これがマネージド ヒープに割り当てられることはありません。 byref に似た構造体は、有効期間と非キャプチャに関する厳密なチェック セットを使用して適用されるため、ハイパフォーマンス プログラミングに役立ちます。 規則は以下のとおりです。

  • これらは、関数パラメーター、メソッド パラメーター、ローカル変数、メソッドの戻り値として使用できます。
  • これらは、クラスまたは通常構造体の静的またはインスタンス メンバーにすることはできません。
  • これらは、クロージャ コンストラクト (async メソッドまたはラムダ式) によってキャプチャすることはできません。
  • これらは、ジェネリック パラメーターとして使用することはできません。

これらの規則によって使用法がかなり厳密に制限されますが、そうするのはハイパフォーマンス コンピューティングの約束事を安全な方法で実現するためです。

ReadOnly 構造体

IsReadOnlyAttribute 属性を使用して構造体に注釈を付けることができます。 次に例を示します。

[<IsReadOnly; Struct>]
type S(count1: int, count2: int) =
    member x.Count1 = count1
    member x.Count2 = count2

IsReadOnlyStruct を意味するものではありません。 IsReadOnly 構造体を使用するには、どちらも追加する必要があります。

この属性を使用するとメタデータが生成され、F# と C# に対して、それぞれ inref<'T> および in ref として処理すればよいことが伝えられます。

読み取り専用構造体の内部で変更可能な値を定義すると、エラーが発生します。

構造体のレコードと判別共用体

[<Struct>] 属性を使用して、レコード判別共用体を構造体として表すことができます。 詳細については、各記事を参照してください。

関連項目