C# における構造体の基礎

1. 構造体とは

構造体は開発者が定義するデータ型であり、クラスと非常によく似ており、データメンバと関数メンバを持ちます。クラスと似ていますが、以下の違いがあります。

  • クラスは参照型ですが、構造体は値型です

  • 構造体は暗黙的にシール(sealed)されており、他のクラスを派生できません

struct StructName
{
    // メンバ
}
struct PointData
{
    public int X;
    public int Y;
}
static void Main(string[] args)
{
    // 構造体の使用例
    {
        PointData first;
        first.X = 10; first.Y = 10;
        PointData second;
        second.X = 30; second.Y = 20;
        PointData third;
        third.X = 30; third.Y = 30;

        Console.WriteLine($"点1: {first.X}, {first.Y}");
        Console.WriteLine($"点2: {second.X}, {second.Y}");
        Console.WriteLine($"点3: {third.X}, {third.Y}"); 
    }
}

2. 構造体は値型

すべての値型と同様に、構造体型の変数は独自のデータを持ちます。そのため:

  • 構造体型の変数は null にはなりません。

  • 2つの構造体変数が同じオブジェクトを参照することはありません。

  • 構造体のフィールド値はスタックにのみ存在します。

class ClassSample
{
    public int X;
    public int Y;
}
struct StructSample
{
    public int X;
    public int Y;
}
class Program
{
    static void Main(string[] args)
    {
        ClassSample cs = new ClassSample();
        StructSample ss = new StructSample();
    }
}

3. 構造体の代入

構造体を別の構造体に代入することは、ある構造体の値を別の構造体にコピーすることです。

  • クラスの代入後、cs2cs1 はヒープ上の同じオブジェクトを指します。

  • しかし構造体の代入後、ss2 のメンバ値は ss1 と同じになります(独立したコピー)。

class ClassSample
{
    public int X;
    public int Y;
}
struct StructSample
{
    public int X;
    public int Y;
}
static void Main(string[] args)
{
    ClassSample cs1 = new ClassSample();
    StructSample ss1 = new StructSample();
    ClassSample cs2 = null;
    StructSample ss2 = new StructSample();

    cs1.X = ss1.X = 5;
    cs1.Y = ss1.Y = 10;

    cs2 = cs1;  // 参照のコピー
    ss2 = ss1;  // 値のコピー
}

4. コンストラクタとデストラクタ

構造体にはインスタンスコンストラクタと静的コンストラクタを持てますが、デストラクタは持てません。

(1)インスタンスコンストラクタ

  • 言語は各構造体に対して暗黙的にパラメータなしのコンストラクタを提供します。

  • このコンストラクタは構造体の各メンバをその型のデフォルト値に設定します

    • 値メンバはそれぞれのデフォルト値
    • 参照メンバは null
  • 各構造体には事前定義されたパラメータなしのコンストラクタが存在し、削除や再定義はできません

  • しかし、パラメータを持つ別のコンストラクタを作成することは可能です。これはクラスとは異なり、クラスでは他のコンストラクタが宣言されていない場合のみ、コンパイラが暗黙的なパラメータなしコンストラクタを提供します

  • コンストラクタを呼び出すには、構造体でも new 演算子が必要ですが、ヒープからメモリを割り当てるわけではありません

    struct StructSample
    {
        public int X;
        public int Y;
    
        public StructSample(int x, int y)
        {
            X = x;
            Y = y;
        }
    }
    StructSample sample = new StructSample(5, 10);
  • new を使わない場合、以下の制限があります。

    • データメンバを明示的に設定した後でなければ、その値を使用できません。
    • すべてのデータメンバに代入した後でなければ、構造体の関数メンバを呼び出せません
struct StructSample
{
    public int X;
    public int Y;
}
static void Main(string[] args)
{
    StructSample s1;
    StructSample s2;
    // Console.WriteLine($"{s1.X}, {s1.Y}"); // コンパイルエラー
    s2.X = 5;
    s2.Y = 10;
    Console.WriteLine($"{s2.X}, {s2.Y}"); // 正常
    // s1 は初期化されていないため、メンバアクセスは不可
}

(2)静的コンストラクタ

クラスと同様に、構造体の静的コンストラクタは静的データメンバを作成し初期化します。インスタンスメンバは参照できません。構造体の静的コンストラクタはクラスの静的コンストラクタと同じ規則に従いますが、パラメータなしの静的コンストラクタを許可します。

以下のいずれかが発生する前に、静的コンストラクタが呼び出されます:

  • 明示的に宣言されたコンストラクタの呼び出し
  • 構造体の静的メンバへの参照

(3)まとめ

コンストラクタの種類 説明
パラメータなしインスタンスコンストラクタ プログラム内で宣言できません。 システムがすべての構造体に対して暗黙的なコンストラクタを提供し、削除や再定義はできません。
パラメータ付きインスタンスコンストラクタ プログラム内で宣言できます。
静的コンストラクタ プログラム内で宣言できます。
デストラクタ 宣言は許可されていません。

5. プロパティとフィールドの初期化子

  • 構造体の宣言内では、インスタンスプロパティやインスタンスフィールドの初期化子は使用できません。

  • しかし構造体が静的でなくても、静的プロパティや静的フィールドは宣言時に初期化できます。

struct StructSample
{
    public int X = 0;   // コンパイルエラー
    public int Y = 19;  // コンパイルエラー

    public int MyProp { get; set; } = 5; // コンパイルエラー
}

6. 構造体はシール(sealed)

  • 構造体は常に暗黙的にシールされており、他の構造体を派生できません

  • 継承をサポートしないため、以下のクラス修飾子は構造体メンバでは意味がなく、使用できません。

    • protected
    • protected internal
    • abstract
    • sealed
    • virtual
  • 構造体自体は System.ValueType から派生し、System.ValueTypeobject から派生します。

  • 構造体メンバに使用できる継承関連のキーワードは newoverride の2つだけです。これらは System.ValueType の同名メンバを隠蔽またはオーバーライドするために使用します。

7. ボックス化とボックス化解除

構造体のインスタンスを参照型オブジェクトとして扱うにはボックス化が必要で、その逆はボックス化解除です。

8. 構造体の戻り値とパラメータ

  • 戻り値

    • 構造体を戻り値として使用する場合、そのコピーが作成され、関数メンバから返されます
  • 値パラメータ

    • 構造体を値パラメータとして使用する場合、実引数構造体のコピーが作成されます。そのコピーがメソッドの実行に使用されます
  • ref および out パラメータ

    • 構造体を ref または out パラメータとして使用する場合、メソッドには構造体への参照が渡されるため、データメンバを変更できます

9. その他

  • 構造体の割り当てにかかるオーバーヘッドはクラスインスタンスの作成よりも小さいため構造体をクラスの代わりに使用するとパフォーマンスが向上する場合があります。ただし、ボックス化・ボックス化解除のコストに注意してください。

  • 組み込みの単純型(intdouble など)は、.NET や C# ではプリミティブ型として扱われますが、実際には .NET ではすべて構造体として実装されています。

  • 部分クラスと同様の方法で部分構造体(partial struct)を宣言できます。

  • 構造体はクラスと同様にインターフェースを実装できます。

タグ: C# 構造体 値型 ボックス化 静的コンストラクタ

5月10日 21:53 投稿