16 構造体

16.1 全般

構造体は、データ メンバーと関数メンバーを含むことができるデータ構造を表すという点でクラスに似ています。 ただし、クラスとは異なり、構造体は値型であり、ヒープ割り当てを必要としません。 struct型の変数にはstructのデータが直接含まれますが、クラス型の変数にはデータへの参照が含まれます。後者はオブジェクトと呼ばれます。

: 構造体は、値セマンティクスを持つ小さなデータ構造に特に役立ちます。 構造体の主な例としては、複素数、座標系のポイント、ディクショナリのキーと値のペアなどがあります。 これらのデータ構造の重要な点は、データ メンバーが少なく、継承や参照セマンティクスを使用する必要がないため、代入が参照ではなく値をコピーする値セマンティクスを使用して便利に実装できることです。 注釈

§8.3.5 で説明されているように、intdoubleboolなど、C# によって提供される単純型は、実際にはすべての構造体型です。

16.2 構造体宣言

16.2.1 全般

struct_declarationは、新しい構造体を宣言するtype_declaration (§14.8) です。

struct_declaration
    : non_record_struct_declaration
    | record_struct_declaration
    ;

non_record_struct_declaration
    : attributes? struct_modifier* 'ref'? 'partial'? 'struct'
      identifier type_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* struct_body ';'?
    ;

record_struct_declaration
    : attributes? struct_modifier* 'partial'? 'record' 'struct'
      identifier type_parameter_list? delimited_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* record_struct_body
    ;

record_struct_body
    : struct_body ';'?
    | ';'
    ;

struct_declarationは、非レコード構造体またはレコード構造体用です。

non_record_struct_declarationは、オプションの属性セット (§23) と、オプションの連のstruct_modifier (§16.2.2)、オプションのref修飾子 (§16.2.3)、その後にオプションの部分修飾子 (§15.2.7)、キーワード struct、構造体を名前付けする識別子で構成されます。 オプションのtype_parameter_list仕様 (§15.2.3) が続く その後に省略可能なstruct_interfaces仕様 (§16.2.5)、オプションのtype_parameter_constraints句仕様 (§15.2.5)、続いてstruct_body (§16.2.6)、必要に応じてセミコロンが続きます。

record_struct_declarationは、オプションの属性セット (§23) の後に、オプションの連の struct_modifier (§16.2.2) が続き、その後にオプションの部分修飾子 (§15.2.7) が続き、キーワード record、 その後にキーワード struct、構造体に名前を付けた識別子、省略可能なtype_parameter_list仕様 (§15.2.3) が続き、その後に省略可能なdelimited_parameter_list specification (§15.2.1)、その後に省略可能なstruct_interfaces仕様 (§16.2.5)、オプションのtype_parameter_constraints句仕様 (§15.2.5)、その後にrecord_struct_bodyが続きます。

struct_declaration、type_parameter_listを供給しない限り、type_parameter_constraints_clauseを供給してはならない。

type_parameter_listを提供するstruct_declarationは、ジェネリック構造体宣言です。 さらに、ジェネリック クラス宣言またはジェネリック構造体宣言内に入れ子になった構造体は、それ自体がジェネリック構造体宣言です。これは、包含型の型引数を指定して構築型 (§8.4) を作成する必要があるためです。

ref 修飾子を含むnon_record_struct_declarationには、struct_interfacesパーツは含まれません。

delimited_parameter_listを持つrecord_struct_declarationは、位置指定レコード構造体を宣言します。

delimited_parameter_listを提供できるのはpartialを含むrecord_struct_declarationが 1 つだけです。

delimited_parameter_listのパラメーターには、refout、またはthis修飾子は含まれませんが、inおよびparams修飾子は許可されます。 record_struct_declarationの場合、record_struct_body{}{};、および;は同等です。 これらはすべて、コンパイラによって合成されたメンバーのみであることを示します (§16.4)。

16.2.2 構造体修飾子

struct_declarationには、必要に応じて一連のstruct_modifierを含めることができます。

struct_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'readonly'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§24.2) は安全でないコード (§24) でのみ使用できます。

構造体宣言で同じ修飾子が複数回出現するのはコンパイル時エラーです。

readonlyを除き、構造体宣言の修飾子はクラス宣言の修飾子と同じ意味を持ちます (§15.2.2)。

readonly修飾子は、インスタンスが変更できない型をstruct_declarationが宣言することを示します。

読み取り専用構造体には、次の制約があります。

  • 各インスタンス フィールドも readonly宣言する必要があります。
  • フィールドに似たイベント (§15.8.2) を宣言することはできません。

読み取り専用構造体のインスタンスがメソッドに渡されると、その this は入力引数/パラメーターのように扱われます。これは、インスタンス フィールドへの書き込みアクセスを禁止します (コンストラクターを除く)。

16.2.3 Ref 修飾子

ref修飾子は、non_record_struct_declarationが実行スタックにインスタンスが割り当てられている型を宣言することを示します。 これらの型は、 ref 構造体 型と呼ばれます。 ref修飾子は、インスタンスに ref のようなフィールドが含まれていることを宣言し、安全なコンテキスト (§16.5.15) からコピーすることはできません。 ref 構造体の安全なコンテキストを決定するための規則については、 §16.5.15 を参照してください。

ref 構造体型が次のいずれかのコンテキストで使用されている場合、コンパイル時エラーになります。

  • 配列の要素型として。
  • ref修飾子を持たないクラスまたは構造体のフィールドの宣言された型。
  • 型引数として。
  • タプル要素の型として。
  • 非同期メソッド内。
  • 反復子内。
  • インスタンス メソッドからデリゲート型へのメソッド グループ変換の受信側の型。
  • ラムダ式またはローカル関数でキャプチャされた変数として。

さらに、 ref struct の種類には次の制限が適用されます。

  • ref struct型は、System.ValueTypeまたはSystem.Objectにボックス化することはできません。
  • ref struct型は、インターフェイスを実装するために宣言することはできません。
  • objectまたはSystem.ValueTypeで宣言されているが、ref struct型ではオーバーライドされないインスタンス メソッドは、そのref struct型のレシーバーでは呼び出されません。

:暗黙的なref struct パラメーターはそれらのコンテキストで使用できないため、asyncはインスタンス メソッドyield return宣言したり、インスタンス メソッド内でyield breakまたはthisステートメントを使用したりすることはできません。 注釈

これらの制約により、 ref struct 型の変数が、無効になったスタック メモリまたは無効になった変数を参照しないようにします。

16.2.4 部分修飾子

partial修飾子は、このstruct_declarationが部分型宣言であることを示します。 外側の名前空間または型宣言内で同じ名前の複数の部分構造体宣言が結合され、 §15.2.7 で指定された規則に従って 1 つの構造体宣言が形成

16.2.5 構造体インターフェイス

構造体宣言には struct_interfaces 仕様を含めることができます。その場合、構造体は指定されたインターフェイス型を直接実装すると言われます。 ジェネリック型宣言 (§15.3.9.7) 内で宣言された入れ子になった型を含む、構築された構造体型の場合、実装された各インターフェイス型は、指定されたインターフェイス内の各 type_parameter に対して、構築された型の対応する type_argument を置き換えることによって取得されます。

struct_interfaces
    : ':' interface_type_list
    ;

部分構造体宣言 (§15.2.7) の複数の部分でのインターフェイスの処理については、 §15.2.4.3 で詳しく説明します。

インターフェイスの実装については、 §19.6 で詳しく説明します。

16.2.6 構造体本体

構造体の struct_body は、構造体のメンバーを定義します。

struct_body
    : '{' struct_member_declaration* '}'
    ;

16.3 構造体メンバー

16.3.1 全般

構造体のメンバーは、その struct_member_declarationによって導入されたメンバーと、型 System.ValueTypeから継承されたメンバーで構成されます。 レコード構造体の場合、メンバー セットには、コンパイラによって生成された合成メンバー (§synth-members) も含まれます。

struct_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | static_constructor_declaration
    | type_declaration
    | fixed_size_buffer_declaration   // unsafe code support
    ;

fixed_size_buffer_declaration (§24.8.2) は安全でないコード (§24) でのみ使用できます。

: finalizer_declarationを除くすべての種類のclass_member_declarationstruct_member_declarationです。 注釈

§16.5 に記載されている相違点を除き、§15.3 から §15.12 で提供されるクラス メンバーの説明も構造体メンバーにも適用されます。

レコード構造体のインスタンス フィールドが安全でない型を持つことはエラーです。

16.3.2 Readonly メンバー

readonly修飾子を含むインスタンス プロパティ、インデクサー、またはイベントのインスタンス メンバー定義またはアクセサーには、次の制限があります。

  • this パラメーターはref readonly参照です。
  • メンバーは、受信側の this またはインスタンス フィールドの値を再割り当てすることはできません。
  • メンバーは、受信側のインスタンス フィールドに似たイベント (§15.8.2) の値を再割り当てすることはできません。
  • 読み取り専用メンバーが読み取り専用でないメンバーを呼び出す場合は、 this によって参照される構造体をコピーして、 this 引数の書き込み可能な参照を使用する必要があります。

メモ: インスタンス フィールドには、自動的に実装されるプロパティ (§15.7.4) に使用される非表示のバッキング フィールドが含まれます。 注釈

: 読み取り専用メンバーは、読み取り専用メンバーがそのインスタンス メンバーを再割り当てできない場合でも、インスタンス フィールドによって参照されるオブジェクトの状態を変更できます。 次のコードは、インスタンス フィールドの再割り当てと変更を示しています。

public struct S
{
    private List<string> messages;

    public S(IEnumerable<string> messages) =>
        this.messages = new List<string>(messages);

    public void InitializeMessages() =>
        messages = new List<string>();

    public readonly void AddMessage(string message)
    {
        if (messages == null)
        {
            throw new InvalidOperationException("Messages collection is not initialized.");
        }
        messages.Add(message);
    }
}

readonlyメソッドAddMessageは、メッセージ リストの状態を変更できます。 InitializeMessages メンバーは、メッセージの一覧をクリアして再初期化できます。 AddMessageの場合、readonly修飾子は有効です。 InitializeMessagesの場合、readonly修飾子の追加は無効です。 end の例

16.4 合成レコード構造体メンバー

16.4.1 全般

レコード構造体の場合、"照合" 署名を持つメンバーが record_struct_body で宣言されていない限り、メンバーは合成されます。または、"照合" 署名を持つアクセス可能な具象の非仮想メンバーが継承されます。 2 つのメンバーが同じシグネチャを持っている場合、または継承シナリオで "非表示" と見なされる場合、2 つのメンバーは一致と見なされます。 (シグネチャとオーバーロード §7.6 を参照)。

合成されたメンバーについては、以下のサブクラウスで説明する。

16.4.2 等値メンバー

合成された等値メンバーは、EqualityContract、null チェック、または継承がない点を除き、レコード クラス (§15.16.2) のメンバーと似ています。

レコード構造体 R は、 System.IEquatable<R> を実装し、次のようにパブリックである Equals(R other)の厳密に型指定されたオーバーロードを合成します。

public readonly bool Equals(R other);

このメソッドは、明示的に宣言できます。 ただし、明示的な宣言が想定されるシグネチャまたはアクセシビリティと一致しない場合は、エラーになります。

Equals(R other)がユーザー定義である (つまり、合成されていない) がGetHashCode場合は、警告が生成されます。

合成されたEquals(R)は、レコード内の各インスタンス フィールドfieldNSystem.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)の値 (TNはフィールド型) がtrue場合にのみ、trueを返します。

レコード構造体には、次のように宣言された演算子と同等の合成 == 演算子と != 演算子が含まれます。

public static bool operator==(R r1, R r2) => r1.Equals(r2);
public static bool operator!=(R r1, R r2) => !(r1 == r2);

Equals演算子によって呼び出される== メソッドは、上記で指定したEquals(R other) メソッドです。 !=演算子は、==演算子にデリゲートします。 演算子が明示的に宣言されている場合はエラーです。

レコード構造体には、次のように宣言されたメソッドと同等の合成オーバーライドが含まれています。

public override readonly bool Equals(object? obj);

オーバーライドが明示的に宣言されている場合はエラーです。 合成されたオーバーライドは、Rがレコード構造体であるother is R temp && Equals(temp)を返す必要があります。

レコード構造体には、次のように宣言されたメソッドと同等の合成オーバーライドが含まれています。

public override readonly int GetHashCode();

このメソッドは明示的に宣言できます。

いずれかの Equals(R)GetHashCode() が明示的に宣言されているが、他のメソッドが宣言されていない場合は、警告が報告されます。

GetHashCode()の合成されたオーバーライドは、各インスタンス フィールドのSystem.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)の値をfieldNの型であるTNと組み合わせたfieldNint結果を返します。

: 次のレコード構造体を考えてみましょう。

record struct R1(T1 P1, T2 P2);

このために、合成された等値メンバーは次のようになります。

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }
    public override bool Equals(object? obj) => obj is R1 temp && Equals(temp);
    public bool Equals(R1 other)
    {
        return
            EqualityComparer<T1>.Default.Equals(P1, other.P1) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R1 r1, R1 r2) => r1.Equals(r2);
    public static bool operator!=(R1 r1, R1 r2) => !(r1 == r2);    
    public override int GetHashCode()
    {
        return HashCode.Combine(
            EqualityComparer<T1>.Default.GetHashCode(P1),
            EqualityComparer<T2>.Default.GetHashCode(P2));

end の例

16.4.3 印刷メンバー

レコード構造体には、次のような合成メソッドが含まれています。

private bool PrintMembers(System.Text.StringBuilder builder);

このメソッドは、次のタスクを実行します。

  1. レコード構造体の印刷可能なメンバー (非静的パブリック フィールドと読み取り可能なプロパティ メンバー) ごとに、そのメンバーの名前の後に "=" が続き、そのメンバーの値が ", “
  2. レコード構造体に印刷可能なメンバーがある場合は true を返します。

値型を持つメンバーの場合、その値は文字列形式に変換されます。

レコードの印刷可能なメンバーに、readonlyget 以外のアクセサーを持つ読み取り可能なプロパティが含まれていない場合、合成された PrintMembersreadonlyPrintMembers メソッドをreadonlyするために、レコードのフィールドをreadonlyする必要はありません。

PrintMembers メソッドは明示的に宣言できます。 ただし、明示的な宣言が想定されるシグネチャまたはアクセシビリティと一致しない場合は、エラーになります。

レコード構造体には、次のような合成メソッドが含まれています。

public override string ToString();

レコード構造体の PrintMembers メソッドが readonlyされている場合、合成された ToString() メソッドは readonly

このメソッドは、明示的に宣言できます。 明示的な宣言が想定されるシグネチャまたはアクセシビリティと一致しない場合はエラーです。

このメソッドは、次のタスクを実行します。

  1. StringBuilder インスタンスを作成します。
  2. レコード構造体名をビルダーに追加し、その後に "{" を追加します。
  3. レコード構造体の PrintMembers メソッドを呼び出してビルダーに渡し、true を返した場合は " " を呼び出します。
  4. "}" を追加します。
  5. builder.ToString()を使用してビルダーの内容を返します。

: 次のレコード構造体を考えてみましょう。

record struct R1(T1 P1, T2 P2);

このレコード構造体では、合成された印刷メンバーは次のようになります。

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }

    private bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString());
                                 // if P1 has a value type
        builder.Append(", ");

        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2.ToString());
                                 // if P2 has a value type

        return true;
    }

    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R1));
        builder.Append(" { ");

        if (PrintMembers(builder))
            builder.Append(" ");

        builder.Append("}");
        return builder.ToString();
    }
}

end の例

16.4.4 位置指定レコード構造体メンバー

16.4.4.1 全般

位置指定レコード構造体 (§16.2.1) は、前のサブクラスで説明したメンバーを提供するだけでなく、次のサブクラウスで説明するように、他のメンバーと同じ条件で追加のメンバーを合成します。

16.4.4.2 プライマリ コンストラクター

レコード構造体には、型宣言の値パラメーターに対応するシグネチャを持つパブリック コンストラクターがあります。 これは、型のプライマリ コンストラクターと呼ばれます。 プライマリ コンストラクターと同じシグネチャを持つコンストラクターが構造体に既に存在するのはエラーです。 型宣言に delimited_parameter_listが含まれていない場合、プライマリ コンストラクターは生成されません。

record struct R1
{
    public R1() { } // OK
}

record struct R2()
{
    public R2() { } // error: 'R2' already defines
                    // a constructor with the same parameter types
}

レコード構造体のインスタンス フィールド宣言には、変数初期化子を含めることができます。 プライマリ コンストラクターがない場合、インスタンス初期化子はパラメーターなしのコンストラクターの一部として実行されます。 それ以外の場合、実行時にプライマリ コンストラクターは、レコード構造体本体に出現するインスタンス初期化子を実行します。

レコード構造体にプライマリ コンストラクターがある場合、ユーザー定義コンストラクターには、プライマリ コンストラクターまたは明示的に宣言されたコンストラクターを呼び出す明示的な this コンストラクター初期化子が必要です。

プライマリ コンストラクターのパラメーターとレコード構造体のメンバーは、インスタンス フィールドまたはプロパティの初期化子内のスコープ内にあります。 インスタンス メンバーはこれらの場所でエラーになりますが、プライマリ コンストラクターのパラメーターはスコープ内にあり、使用でき、シャドウ メンバーになります。 静的メンバーも使用できます。

プライマリ コンストラクターのパラメーターが読み取られない場合は、警告が生成されます。

構造体インスタンス コンストラクターの明確な代入規則は、レコード構造体のプライマリ コンストラクターに適用されます。 たとえば、次のようなエラーが発生します。

record struct Pos(int X) // def assignment error in primary constructor
{
    private int x;
    public int X {
        get { return x; } set { x = value; } 
    } = X;
}

16.4.4.3 プロパティ

明示的に宣言されたインスタンス フィールドと同じ名前と型を持つ delimited_parameter_list の各パラメーターに対して、このサブドキュメントの残りの部分は適用されません。

delimited_parameter_listの各レコード構造体パラメーターには、値パラメーター宣言から名前と型を取得する、対応するパブリック プロパティ メンバーがあります。

レコード構造体の場合:

  • レコード構造体にreadonly修飾子、get、それ以外の場合はset場合は、パブリック getinit自動プロパティが作成されます。 セット アクセサー (setinit) の両方が "一致" と見なされます。 そのため、ユーザーは、合成された変更可能なプロパティの代わりに init のみのプロパティを宣言できます。

  • 型が一致する継承された abstract プロパティがオーバーライドされます。

  • レコード構造体に名前と型が必要なインスタンス フィールドがある場合、自動プロパティは作成されません。

  • 継承されたプロパティに publicget および set/init アクセサーがない場合はエラーです。

  • 継承されたプロパティまたはフィールドが非表示の場合はエラーです。

  • 自動プロパティは、対応するプライマリ コンストラクター パラメーターの値に初期化されます。

  • 属性は、対応するレコード構造体パラメーターに構文的に適用される属性の property: または field: ターゲットを使用して、合成された自動プロパティとそのバッキング フィールドに適用できます。

16.4.4.4 分解

少なくとも 1 つのパラメーターを持つ位置レコード構造体は、プライマリ コンストラクター宣言の各パラメーターに対する out パラメーター宣言を使用して、Deconstruct呼び出されたパブリック void返すインスタンス メソッドを合成します。 Deconstructの各パラメーターは、プライマリ コンストラクター宣言の対応するパラメーターと同じ型を持ちます。 メソッドの本体は、Deconstruct メソッドの各パラメーターを、インスタンス メンバーから同じ名前のメンバーへのアクセスの値に割り当てます。 本文でアクセスされるインスタンス メンバーに、readonlyget 以外のアクセサーを持つプロパティが含まれていない場合、合成された Deconstruct メソッドは readonly。 メソッドは明示的に宣言できます。 明示的な宣言が予期されるシグネチャまたはアクセシビリティと一致しない場合、または静的な場合はエラーです。

16.5 クラスと構造体の違い

16.5.1 全般

構造体は、いくつかの重要な方法でクラスと異なります。

  • 構造体は値型 (§16.5.2) です
  • すべての構造体型は、クラス System.ValueType (§16.5.3) から暗黙的に継承されます。
  • 構造体型の変数に代入すると、割り当てられている値の コピー が作成されます (§16.5.4)。
  • 構造体の既定値は、すべてのフィールドを既定値 (§16.5.5) に設定することによって生成される値です。
  • ボックス化操作とボックス化解除操作は、構造体型と特定の参照型 (§16.5.6) の間で変換するために使用されます。
  • thisの意味は、構造体メンバー内で異なります (§16.5.7)。
  • 構造体はファイナライザーを宣言できません。
  • イベント宣言、プロパティ宣言、プロパティ アクセサー、インデクサー宣言、およびメソッド宣言は修飾子を readonly することが許可されますが、通常、クラス内の同じメンバーの種類に対しては許可されません。

16.5.2 値セマンティクス

構造体は値型 (§8.3) であり、値セマンティクスがあると言います。 一方、クラスは参照型 (§8.2) であり、参照セマンティクスを持っていると言います。

構造体型の変数には構造体のデータが直接含まれますが、クラス型の変数にはデータを含むオブジェクトへの参照が含まれます。 構造体BA型のインスタンス フィールドが含まれており、Aが構造体型である場合、ABまたはBから構築された型に依存するのはコンパイル時エラーです。 構造体 X に直接依存します。 この定義を考えると、構造体が依存する構造体の完全なセットは、 間接的に依存 関係の推移的なクロージャです。

例:

struct Node
{
    int data;
    Node next; // error, Node directly depends on itself
}

は、 Node に独自の型のインスタンス フィールドが含まれているため、エラーです。 別の例

struct A { B b; }
struct B { C c; }
struct C { A a; }

は、各型 AB、および C が互いに依存するため、エラーです。

end の例

クラスを使用すると、2 つの変数が同じオブジェクトを参照できるため、1 つの変数に対する操作が他の変数によって参照されるオブジェクトに影響を与える可能性があります。 構造体では、変数にはそれぞれデータの独自のコピーがあります (参照パラメーターの場合を除く)。一方の操作が他方に影響を与えることはできません。 さらに、明示的に null 許容 (§8.3.12) を除き、構造体型の値を nullすることはできません。

: 構造体に参照型のフィールドが含まれている場合、参照されるオブジェクトの内容は他の操作によって変更できます。 ただし、フィールド自体の値 (つまり、参照するオブジェクト) は、異なる構造体値の変更によって変更することはできません。 注釈

: 次の場合

struct Point
{
    public int x, y;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

class A
{
    static void Main()
    {
        Point a = new Point(10, 10);
        Point b = a;
        a.x = 100;
        Console.WriteLine(b.x);
    }
}

出力が 10aへのbの割り当てによって値のコピーが作成されるため、ba.xへの割り当ての影響を受けません。 Pointクラスとして宣言されている場合、100aが同じオブジェクトを参照するため、出力はbされます。

end の例

16.5.3 継承

すべての構造体型は、クラス System.ValueTypeから暗黙的に継承され、クラス objectから継承されます。 構造体宣言では実装されているインターフェイスの一覧を指定できますが、構造体宣言で基底クラスを指定することはできません。

構造体型は決して抽象型でなく、常に暗黙的にシールされます。 したがって、 abstract および sealed 修飾子は構造体宣言では許可されません。

構造体の継承はサポートされていないため、構造体メンバーの宣言されたアクセシビリティを protectedprivate protected、または protected internalすることはできません。

構造体内の関数メンバーを抽象または仮想にすることはできません。また、 override 修飾子は、 System.ValueTypeから継承されたメソッドのオーバーライドのみを許可します。

16.5.4 割り当て

構造体型の変数に代入すると、割り当てられている値の コピー が作成されます。 これは、参照をコピーするが、参照によって識別されるオブジェクトをコピーするクラス型の変数への代入とは異なります。

代入と同様に、構造体が値パラメーターとして渡されるか、関数メンバーの結果として返されるときに、構造体のコピーが作成されます。 構造体は、参照渡しパラメーターを使用して、関数メンバーへの参照によって渡すことができます。

構造体のプロパティまたはインデクサーが代入のターゲットである場合、プロパティまたはインデクサー アクセスに関連付けられているインスタンス式は変数として分類されます。 インスタンス式が値として分類されると、コンパイル時エラーが発生します。 詳細については、 §12.24.2 を参照してください。

16.5.5 既定値

§9.3 で説明されているように、いくつかの種類の変数は、作成時に自動的に既定値に初期化されます。 クラス型とその他の参照型の変数の場合、この既定値は null。 ただし、構造体は nullできない値型であるため、構造体の既定値は、すべての値型フィールドを既定値に設定し、すべての参照型フィールドを nullすることによって生成される値です。

: 上記で宣言した Point 構造体を参照しています。

Point[] a = new Point[100];

は、配列内の各 Point を、 x フィールドと y フィールドを 0 に設定して生成された値に初期化します。

end の例

構造体の既定値は、構造体の既定のコンストラクターによって返される値に対応します (§8.3.3)。 構造体で明示的なパラメーターなしのインスタンス コンストラクターが宣言されていない場合、既定のコンストラクターが合成され、すべてのフィールドを既定値に設定した結果の値が常に返されます。 default式は、構造体が明示的なパラメーターなしのインスタンス コンストラクター (§16.4.9) を宣言している場合でも、常にゼロ初期化の既定値を生成します。

: 構造体は、既定の初期化状態を有効な状態と見なすために設計する必要があります。 この例では、

struct KeyValuePair
{
    string key;
    string value;

    public KeyValuePair(string key, string value)
    {
        if (key == null || value == null)
        {
            throw new ArgumentException();
        }

        this.key = key;
        this.value = value;
    }
}

ユーザー定義インスタンス コンストラクターは、明示的に呼び出された場合にのみ、 null 値から保護します。 KeyValuePair変数が既定値の初期化の対象となる場合、keyフィールドとvalue フィールドはnullされ、構造体はこの状態を処理するように準備する必要があります。

注釈

16.5.6 ボックス化とボックス化解除

クラス型の値は、コンパイル時に参照を別の型として扱うだけで、 object 型またはクラスによって実装されるインターフェイス型に変換できます。 同様に、 object 型の値またはインターフェイス型の値は、参照を変更せずにクラス型に変換できます (ただし、この場合は実行時の型チェックが必要です)。

構造体は参照型ではないため、これらの操作は構造体型に対して異なる方法で実装されます。 構造体型の値が ( §10.2.9 で定義されている) 特定の参照型に変換されるとボックス化操作が行われます。 同様に、( §10.3.7 で定義されている) 特定の参照型の値が構造体型に変換されると、ボックス化解除操作が実行されます。 クラス型に対する同じ操作との主な違いは、ボックス化とボックス化解除の スコープ ボックス化されたインスタンスとの間の構造体値です。

: したがって、ボックス化またはボックス化解除操作の後、ボックス化されていない struct に加えられた変更は、ボックス化された structには反映されません。 注釈

ボックス化とボックス化解除の詳細については、 §10.2.9 および §10.3.7 を参照してください。

16.5.7 この意味

構造体内のthisの意味は、§12.8.14 で説明されているように、クラス内のthisの意味とは異なります。 構造体型が System.ValueType から継承された仮想メソッド ( EqualsGetHashCodeToStringなど) をオーバーライドする場合、構造体型のインスタンスを介して仮想メソッドを呼び出しても、ボックス化は発生しません。 これは、構造体が型パラメーターとして使用され、型パラメーター型のインスタンスを介して呼び出しが発生した場合でも当てはまります。

例:

struct Counter
{
    int value;
    public override string ToString() 
    {
        value++;
        return value.ToString();
    }
}

class Program
{
    static void Test<T>() where T : new()
    {
        T x = new T();
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
    }

    static void Main() => Test<Counter>();
}

プログラムの出力は次のとおりです。

1
2
3

ToStringが副作用を与えるのは不適切なスタイルですが、この例では、x.ToString()の 3 回の呼び出しでボックス化が発生しなかったことを示しています。

end の例

同様に、メンバーが値型内に実装されている場合、制約付き型パラメーターのメンバーにアクセスするときにボックス化が暗黙的に行われることはありません。 たとえば、インターフェイス ICounter に、値の変更に使用できるメソッド Incrementが含まれているとします。 ICounterが制約として使用されている場合、Increment メソッドの実装は、ボックス化されたコピーIncrement呼び出された変数への参照を使用して呼び出されます。

例:

interface ICounter
{
    void Increment();
}

struct Counter : ICounter
{
    int value;

    public override string ToString() => value.ToString();

    void ICounter.Increment() => value++;
}

class Program
{
    static void Test<T>() where T : ICounter, new()
    {
        T x = new T();
        Console.WriteLine(x);
        x.Increment();              // Modify x
        Console.WriteLine(x);
        ((ICounter)x).Increment();  // Modify boxed copy of x
        Console.WriteLine(x);
    }

    static void Main() => Test<Counter>();
}

Incrementを最初に呼び出すと、変数xの値が変更されます。 これは、ボックス化されたIncrementのコピーの値を変更する、xの 2 回目の呼び出しと同じではありません。 したがって、プログラムの出力は次のようになります。

0
1
1

end の例

16.5.8 フィールド初期化子

§16.5.5 で説明されているように、構造体の既定値は、すべての値型フィールドを既定値に設定し、すべての参照型フィールドをnullに設定した結果の値で構成されます。 構造体の静的フィールドとインスタンス フィールドには、変数初期化子を含めることができます。ただし、インスタンス フィールド初期化子の場合は、少なくとも 1 つのインスタンス コンストラクターも宣言する必要があります。レコード構造体の場合は、 delimited_parameter_list が存在する必要があります。

例:

Console.WriteLine($"Point is {new Point()}");

struct Point
{
    public int x = 1;
    public int y = 1;

    public Point() { }

    public override string ToString()
    {
        return "(" + x + ", " + y + ")";
    }
}
Point is (1, 1)

end の例

構造体インスタンス コンストラクターにコンストラクター初期化子がない場合、そのコンストラクターは、その構造体で宣言されたインスタンス フィールドの variable_initializerで指定された初期化を暗黙的に実行します。 これは、コンストラクターへのエントリの直後に実行される割り当てのシーケンスに対応します。

構造体インスタンス コンストラクターに既定のパラメーターなしのコンストラクターを表す this() コンストラクター初期化子がある場合、宣言されたコンストラクターは暗黙的にすべてのインスタンス フィールドをクリアし、その構造体で宣言されたインスタンス フィールドの variable_initializerで指定された初期化を実行します。 コンストラクターに入るとすぐに、すべての値型フィールドが既定値に設定され、すべての参照型フィールドが nullに設定されます。 その直後に、 variable_initializerに対応する一連の割り当てが実行されます。

field_declarationstruct_declaration内で直接宣言され、struct_modifierreadonlyを持つ場合は、field_modifierreadonlyを持つ必要があります。

16.5.9 コンストラクター

構造体は、0 個以上のパラメーターを持つインスタンス コンストラクターを宣言できます。 構造体に明示的に宣言されたパラメーターなしのインスタンス コンストラクターがない場合は、パブリック アクセシビリティを使用して合成されます。パブリック アクセシビリティにより、常にすべての値型フィールドを既定値に設定し、すべての参照型フィールドを null (§8.3.3) に設定した結果の値を返します。 このような場合、インスタンス フィールド初期化子は、そのコンストラクターの実行時に無視されます。

明示的に宣言されたパラメーターなしのインスタンス コンストラクターには、パブリック アクセシビリティが必要です。

: 以下のコードでは

using System;
struct Point
{
    int x = -1, y = -2;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }

    public override string ToString()
    {
        return "(" + x + ", " + y + ")";
    }
}

class A
{
    static void Main()
    {
        Console.WriteLine($"Point is {new Point()}");
        Console.WriteLine($"Point is {new Point(0,0)}");
    }
}
Point is (0, 0)
Point is (0, 0)

ステートメントはどちらも、xを持つPointを作成し、y 0 に初期化します。パラメーターなしのインスタンス コンストラクターの呼び出しの場合は、両方のインスタンス フィールドに初期化子がありますが、実行されないため、驚くかもしれません。

end の例

構造体インスタンス コンストラクターは、フォーム base(argument_list)のコンストラクター初期化子を含められません。ここで、 argument_list は省略可能です。 インスタンス コンストラクターを実行しても、構造体の基本型 System.ValueTypeでコンストラクターが実行されるわけではありません。

構造体インスタンス コンストラクターの this パラメーターは、構造体型の出力パラメーターに対応します。 そのため、 this は、コンストラクターが返すすべての場所に確実に割り当てられます (§9.4)。 同様に、確実に割り当てられる前に、コンストラクター本体で読み取ることはできません (暗黙的であっても)。

構造体インスタンス コンストラクターでコンストラクター初期化子が指定されている場合、その初期化子は、コンストラクターの本体より前に発生する、これに対する明確な割り当てと見なされます。 したがって、本体自体には初期化要件はありません。

( fixed フィールド以外の) インスタンス フィールドは、 this() 初期化子を持たない構造体インスタンス コンストラクターで確実に割り当てられる必要があります。

: 以下のインスタンス コンストラクターの実装について考えてみましょう。

struct Point
{
    int x, y;

    public int X
    {
        set { x = value; }
    }

    public int Y 
    {
        set { y = value; }
    }

    public Point(int x, int y) 
    {
        X = x; // error, this is not yet definitely assigned
        Y = y; // error, this is not yet definitely assigned
    }
}

構築される構造体のすべてのフィールドが確実に割り当てられるまで、インスタンス関数メンバー (プロパティ X および Y の set アクセサーを含む) を呼び出すことができます。 ただし、 Point が構造体ではなくクラスである場合は、インスタンス コンストラクターの実装が許可されることに注意してください。 これには 1 つの例外があり、自動的に実装されるプロパティ (§15.7.4) が含まれます。 明確な代入規則 (§12.24.2) は、その構造体型のインスタンス コンストラクター内の構造体型の自動プロパティへの割り当てを特に除外します。このような割り当ては、自動プロパティの隠しバッキング フィールドの明確な割り当てと見なされます。 したがって、次の操作が許可されます。

struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x; // allowed, definitely assigns backing field
        Y = y; // allowed, definitely assigns backing field
   }
}

例終わり]

16.5.10 静的コンストラクター

構造体の静的コンストラクターは、クラスの場合とほとんど同じ規則に従います。 構造体型の静的コンストラクターの実行は、アプリケーション ドメイン内で発生する次のイベントの最初のイベントによってトリガーされます。

  • 構造体型の静的メンバーが参照されます。
  • 構造体型の明示的に宣言されたコンストラクターが呼び出されます。

: 構造体型の既定値 (§16.5.5) を作成しても、静的コンストラクターはトリガーされません。 (この例は、配列内の要素の初期値です)。 メモの送信

16.5.11 プロパティ

インスタンス プロパティのstruct_declaration 内のproperty_declaration (§15.7.1) には property_modifierreadonly が含まれている場合があります。 ただし、静的プロパティには、その修飾子は含まれません。

その構造体で宣言された読み取り専用プロパティを使用してインスタンス構造体変数の状態を変更しようとすると、コンパイル時エラーになります。

readonly修飾子を持つ自動実装プロパティがsetアクセサーも持つことは、コンパイル時エラーの原因になります。

readonly アクセサーを持つset構造体に自動的に実装されるプロパティのコンパイル時エラーです。

readonly アクセサーは暗黙的に読み取り専用と見なされるため、readonly構造体内で宣言された自動的に実装されるプロパティには、get修飾子は必要ありません。

プロパティ自体だけでなく、readonlyアクセサーとgetアクセサーに対してset修飾子を持つことは、コンパイル時エラーです。

プロパティがすべてのアクセサーに対して読み取り専用修飾子を持つことはコンパイル時エラーです。

: エラーを修正するには、アクセサーからプロパティ自体に修飾子を移動します。 注釈

プロパティ アクセサー式の場合は、次の s.P

  • s.P のプロセスがMの一時的なコピーを作成するときに、Tが型の set アクセサー sを呼び出すと、コンパイル時エラーになります。
  • s.P T型の get アクセサーを呼び出すと、必要に応じての一時的なコピーを作成するなど、sプロセスが実行されます。

自動的に実装されるプロパティ (§15.7.4) は、プロパティ アクセサーのみがアクセスできる非表示のバッキング フィールドを使用します。

: このアクセス制限は、自動的に実装されるプロパティを含む構造体のコンストラクターには、関数メンバーが呼び出されるか、コンストラクターが戻る前に確実に割り当てられるすべてのフィールドの要件を満たすために、明示的なコンストラクター初期化子が必要になることがよくあります。 注釈

16.5.12 メソッド

struct_declaration内のインスタンス メソッドのmethod_declaration (§15.6.1) にはmethod_modifierreadonlyを含めることができます。 ただし、静的メソッドには、その修飾子は含まれません。

その構造体で宣言された読み取り専用メソッドを使用してインスタンス構造体変数の状態を変更しようとすると、コンパイル時エラーになります。

読み取り専用メソッドは兄弟メソッド、非読み取り専用メソッド、またはプロパティまたはインデクサー get アクセサーを呼び出すことができますが、その結果、防御手段として this の暗黙的なコピーが作成されます。

読み取り専用メソッドは、読み取り専用の兄弟プロパティまたはインデクサー セット アクセサーを呼び出す場合があります。 兄弟メンバーのアクセサーが明示的または暗黙的に読み取り専用でない場合は、コンパイル エラーが発生します。

部分メソッドのすべての method_declarationは、 readonly 修飾子を持つ必要があります。または、それらのいずれも修飾子を持つ必要はありません。

16.5.13 インデクサー

struct_declaration内のインスタンス インデクサーのindexer_declaration (§15.9) には、indexer_modifierが含readonly

その構造体で宣言された読み取り専用インデクサーを使用してインスタンス構造体変数の状態を変更しようとすると、コンパイル時エラーになります。

インデクサー自体だけでなく、readonlyアクセサーまたは get アクセサーに対してset修飾子を持つことは、コンパイル時エラーです。

インデクサーがすべてのアクセサーに対して読み取り専用修飾子を持つことはコンパイル時エラーです。

: エラーを修正するには、アクセサーからインデクサー自体に修飾子を移動します。 注釈

16.5.14 イベント

インスタンス用のevent_declaration (§15.8.1) は、struct_declaration内の非フィールド状イベントにevent_modifierreadonlyを含む場合があります。 ただし、静的イベントには、その修飾子は含まれません。

16.5.15 安全なコンテキスト制約

16.5.15.1 全般

コンパイル時に、各式は、そのインスタンスとそのすべてのフィールドに安全にアクセスできるコンテキスト、 safe-contextに関連付けられます。 セーフ コンテキストは、式を囲むコンテキストであり、値をエスケープしても安全です。

コンパイル時の型が ref 構造体でない式には、呼び出し元コンテキストのセーフ コンテキストがあります。

default式には、任意の型に対して、呼び出し元コンテキストのセーフ コンテキストがあります。

コンパイル時の型が ref 構造体である既定以外の式の場合、次のセクションで定義されているセーフ コンテキストがあります。

セーフ コンテキストは、値がコピーされるコンテキストを記録します。 セーフ コンテキスト E1を持つ式S1から、セーフ コンテキスト E2を持つ式S2への代入を考えると、S2S1よりも広いコンテキストである場合はエラーになります。

参照変数 (§9.7.2)、declaration-blockfunction-member、および caller-context に定義されている ref-safe-context 値と同じ、3 つの異なるセーフ コンテキスト値があります。 式のセーフ コンテキストでは、次のように使用が制限されます。

  • return ステートメント return e1の場合、 e1 のセーフ コンテキストは呼び出し元コンテキストである必要があります。
  • 割り当てe1 = e2e2のセーフ コンテキストは、少なくともe1のセーフ コンテキストと同じ幅のコンテキストである必要があります。

メソッドの呼び出しで、ref型のoutまたはref struct引数 (型がreadonlyでない限り、レシーバーを含む) がセーフ コンテキストS1の場合、引数 (レシーバーを含む) はS1よりも狭いセーフ コンテキストを持つ可能性があります。

16.5.15.2 パラメーターセーフコンテキスト

ref 構造体型のパラメーター (インスタンス メソッドの this パラメーターを含む) には、呼び出し元コンテキストのセーフ コンテキストがあります。

16.5.15.3 ローカル変数のセーフ コンテキスト

ref 構造体型のローカル変数には、次のようなセーフ コンテキストがあります。

  • 変数が foreach ループの反復変数である場合、変数のセーフ コンテキストは、 foreach ループの式のセーフ コンテキストと同じです。
  • それ以外の場合、変数の宣言に初期化子がある場合、変数のセーフ コンテキストは、その初期化子のセーフ コンテキストと同じです。
  • それ以外の場合、変数は宣言の時点で初期化されておらず、呼び出し元コンテキストのセーフ コンテキストを持ちます。

16.5.15.4 フィールド セーフ コンテキスト

e.Fの型が ref 構造体型であるフィールド Fへの参照には、eのセーフ コンテキストと同じセーフ コンテキストがあります。

16.5.15.5 演算子

ユーザー定義演算子のアプリケーションは、メソッド呼び出し (§16.5.15.6) として扱われます。

e1 + e2c ? e1 : e2などの値を生成する演算子の場合、結果のセーフ コンテキストは、演算子のオペランドのセーフ コンテキストの中で最も狭いコンテキストです。 その結果、 +eなどの値を生成する単項演算子の場合、結果のセーフ コンテキストはオペランドのセーフ コンテキストになります。

: 条件演算子の最初のオペランドは boolであるため、そのセーフ コンテキストは呼び出し元コンテキストです。 その後、結果として得られるセーフ コンテキストは、2 番目と 3 番目のオペランドの最も狭いセーフ コンテキストになります。 注釈

16.5.15.6 メソッドとプロパティの呼び出し

メソッド呼び出し e1.M(e2, ...) またはプロパティ呼び出し e.P の結果の値には、次のコンテキストのうち最も小さいセーフ コンテキストがあります。

  • 呼び出し元コンテキスト。
  • すべての引数式 (受信側を含む) のセーフ コンテキスト。

プロパティの呼び出し ( get または set) は、上記の規則によって基になるメソッドのメソッド呼び出しとして扱われます。

16.5.15.7 stackalloc

stackalloc 式の結果には、関数メンバーのセーフ コンテキストがあります。

16.5.15.8 コンストラクターの呼び出し

コンストラクターを呼び出す new 式は、構築される型を返すと見なされるメソッド呼び出しと同じ規則に従います。

さらに、任意の初期化子が存在する場合、セーフ コンテキストは、すべてのオブジェクト初期化子式のすべての引数とオペランドのセーフ コンテキストの中で最も小さい値です。

: これらの規則は、次の形式のコンストラクターを持たない Span<T> に依存します。

public Span<T>(ref T p)

このようなコンストラクターにより、 Span<T> のインスタンスがフィールドとして使用され、 ref フィールドと区別できなくなります。 このドキュメントで説明する安全規則は、ref フィールドが C# または.NETで有効なコンストラクトではないことに依存します。 注釈