構造体型 (C# リファレンス)

"構造体型" (または "構造体型") とは、データおよび関連する機能をカプセル化できる値の型です。 構造体型を定義するには、struct キーワードを使用します。

public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public override string ToString() => $"({X}, {Y})";
}

ref struct 型と readonly ref struct 型については、ref struct 型に関する記事で説明します。

構造体型には、"値のセマンティクス" があります。 つまり、構造体型の変数には、型のインスタンスが含まれます。 既定では、変数値が代入時にコピーされ、引数がメソッドに渡され、メソッドの結果が返されます。 構造体型の変数の場合は、型のインスタンスがコピーされます。 詳細については、値の型に関するページを参照してください。

通常は、構造体型を使用して、ほとんどまたはまったく動作を提供しない小さなデータ中心型を設計します。 たとえば、.NET では、構造体型を使用して数値 (整数実数の両方)、ブール値Unicode 文字時刻インスタンスが表現されます。 型の動作に重点を置いている場合は、class を定義することを検討してください。 クラス型には "参照セマンティクス" があります。 つまり、クラス型の変数には、インスタンス自体ではなく、型のインスタンスへの参照が含まれています。

構造体型には値セマンティクスがあるため、"変更不可" の構造体型を定義することをお勧めします。

readonly 構造体

readonly 修飾子を使って、構造体型が変更不可であることを宣言します。 readonly 構造体のすべてのデータ メンバーを、次のように読み取り専用にする必要があります。

  • すべてのフィールド宣言には、readonly 修飾子が必要です
  • 自動的に実装されるものも含めて、すべてのプロパティは、読み取り専用である必要があります。 C# 9.0 以降では、プロパティに init アクセサーが含まれる場合があります。

それにより、readonly 構造体のどのメンバーも構造体の状態を変更しないことが保証されます。 つまり、コンストラクターを除く他のインスタンス メンバーは、暗黙的に readonly になります。

注意

readonly 構造体でも、変更可能な参照型のデータ メンバーは、それ自身の状態を変更できます。 たとえば、List<T> インスタンスを置き換えることはできませんが、新しい要素をそれに追加することはできます。

次のコードでは、C# 9.0 以降で使用できる、init 専用プロパティの setter を持つ readonly 構造体を定義します。

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

readonly インスタンス メンバー

readonly 修飾子を使って、インスタンス メンバーで構造体の状態を変更しないことを宣言することもできます。 構造体の型全体を readonly として宣言できない場合は、readonly 修飾子を使用して、構造体の状態を変更しないインスタンス メンバーをマークします。

readonly インスタンス メンバー内では、構造体のインスタンス フィールドに割り当てることはできません。 ただし、readonly メンバーから非 readonly メンバーを呼び出すことができます。 その場合、コンパイラを使用して構造体インスタンスのコピーを作成し、そのコピーで非 readonly メンバーを呼び出します。 その結果、元の構造インスタンスは変更されません。

通常、readonly 修飾子を次の種類のインスタンス メンバーに適用します。

  • メソッド:

    public readonly double Sum()
    {
        return X + Y;
    }
    

    System.Object で宣言されたメソッドをオーバーライドするメソッドに readonly 修飾子を適用することもできます。

    public readonly override string ToString() => $"({X}, {Y})";
    
  • プロパティとインデクサー:

    private int counter;
    public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }
    

    プロパティまたはインデクサーの両方のアクセサーに readonly 修飾子を適用する必要がある場合は、プロパティまたはインデクサーの宣言でそれを適用します。

    注意

    プロパティの宣言に readonly 修飾子が存在するかどうかに関係なく、コンパイラによって自動実装プロパティget アクセサーが readonly として宣言されます。

    C# 9.0 以降では、init アクセサーを持つプロパティまたはインデクサーに readonly 修飾子を適用することができます。

    public readonly double X { get; init; }
    

readonly 修飾子は、構造体型の静的フィールドに適用できますが、プロパティやメソッドなどの他の静的メンバーには適用できません。

パフォーマンスの最適化のためにコンパイラで readonly 修飾子を使用する場合があります。 詳細については、「安全で効率的な C# コードを記述する」をご覧ください。

非破壊な変化

C# 10 以降では、withを使用して、指定したプロパティとフィールドが変更された構造体型インスタンスのコピーを生成できます。 次の例に示すように、変更するメンバーとその新しい値は、オブジェクト初期化子構文を使用して指定します。

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

public static void Main()
{
    var p1 = new Coords(0, 0);
    Console.WriteLine(p1);  // output: (0, 0)

    var p2 = p1 with { X = 3 };
    Console.WriteLine(p2);  // output: (3, 0)

    var p3 = p1 with { X = 1, Y = 4 };
    Console.WriteLine(p3);  // output: (1, 4)
}

record 構造体

C# 10 以降では、レコード構造の種類を定義できます。 レコードの種類は、データをカプセル化するための組み込みの機能を提供します。 record structreadonly record struct タイプの両方を定義できます。 レコード構造体を ref struct にすることはできません。 使用例を含む詳細については、「レコード」を参照してください。

構造体の初期化と既定値

struct 型の変数には、その struct のデータが直接含まれています。 それにより、既定値が設定されている初期化されていない struct と、それを構築することによって設定された値が格納されている初期化された struct の間には区別があります。 たとえば、次のようなコードについて考えます。

public readonly struct Measurement
{
    public Measurement()
    {
        Value = double.NaN;
        Description = "Undefined";
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; }

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement();
    Console.WriteLine(m1);  // output: NaN (Undefined)

    var m2 = default(Measurement);
    Console.WriteLine(m2);  // output: 0 ()

    var ms = new Measurement[2];
    Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
}

前の例で示したように、既定値の式では、パラメーターなしのコンストラクターは無視され、構造体型の既定値が生成されます。 構造体型の配列のインスタンス化では、パラメーターなしのコンストラクターも無視され、構造体型の既定値が設定された配列が生成されます。

既定値を目にする最も一般的な状況は、配列内または内部ストレージに変数のブロックが含まれる他のコレクション内です。 次の例では、30 個の TemperatureRange 構造体の配列が作成され、それぞれに既定値が設定されます。

// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];

struct 型はデータを直接格納するため、構造体のすべてメンバー フィールドは、作成時に "確実に割り当てられる" 必要があります。 構造体の default 値では、すべてのフィールドに 0 が "確実に割り当てられます"。 コンストラクターが呼び出されたら、すべてのフィールドを確実に割り当てる必要があります。 フィールドの初期化には、次のメカニズムを使用します。

  • 任意のフィールドまたは自動的に実装されるプロパティに、"フィールド初期化子" を追加できます。
  • コンストラクターの本体で、任意のフィールドまたは自動プロパティを初期化できます。

C# 11 以降では、構造体に初期化されていないフィールドがある場合、コンパイラによってそれらのフィールドを既定値に初期化するコードがコンストラクターに追加されます。 コンパイラでは、通常の確実な割り当ての分析が実行されます。 割り当てられる前にアクセスされるフィールド、またはコンストラクターの実行完了時に確実に割り当てられていないフィールドには、コンストラクターの本体が実行される前に既定値が割り当てられます。 すべてのフィールドが割り当てられる前に this がアクセスされる場合は、コンストラクターの本体が実行される前に構造体が既定値に初期化されます。

public readonly struct Measurement
{
    public Measurement(double value)
    {
        Value = value;
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Measurement(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary measurement";

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement(5);
    Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

    var m2 = new Measurement();
    Console.WriteLine(m2);  // output: 0 ()

    var m3 = default(Measurement);
    Console.WriteLine(m3);  // output: 0 ()
}

すべての struct には、パラメーターなしの public コンストラクターがあります。 パラメーターなしのコンストラクターを作成する場合は、public にする必要があります。 構造体でフィールド初期化子を宣言する場合は、コンストラクターを明示的に宣言する必要があります。 そのコンストラクターは、パラメーターなしである必要はありません。 構造体でフィールド初期化子を宣言してもコンストラクターを宣言しない場合は、コンパイラによってエラーが報告されます。 明示的に宣言されたコンストラクター (パラメーターありまたはパラメーターなし) では、その構造体のすべてのフィールド初期化子が実行されます。 コンストラクター内でフィールド初期化子または代入がないすべてのフィールドは、既定値に設定されます。 詳細については、パラメーターなしの構造体コンストラクター機能の提案に関するメモを参照してください。

構造体型のすべてのインスタンス フィールドにアクセスできる場合は、それを new 演算子なしでインスタンス化することもできます。 その場合は、インスタンスを初めて使用する前に、すべてのインスタンス フィールドを初期化する必要があります。 その方法を次の例に示します。

public static class StructWithoutNew
{
    public struct Coords
    {
        public double x;
        public double y;
    }

    public static void Main()
    {
        Coords p;
        p.x = 3;
        p.y = 4;
        Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
    }
}

組み込みの値型の場合は、対応するリテラルを使用して型の値を指定します。

構造体型の設計に関する制限事項

構造体には、クラス型のほとんどの機能があります。 いくつかの例外があり、一部の例外はより新しいバージョンで削除されています。

  • 構造体型は、他のクラスまたは構造体型から継承することができないほか、クラスのベースとすることもできません。 ただし、構造体型では interfaces を実装することができます。
  • 構造体型内でファイナライザーを宣言することはできません。
  • C# 11 より前の構造体型のコンストラクターでは、型のすべてのインスタンス フィールドを初期化する必要があります。
  • C# 10 より前では、パラメーターなしのコンストラクターを宣言することはできません。
  • C# 10 より前では、インスタンス フィールドまたはプロパティを、それらの宣言で初期化することはできません。

構造体型の変数を参照渡しする

構造体型の変数を引数としてメソッドに渡す場合、またはメソッドから構造体型の値を返す場合は、構造体型のインスタンス全体がコピーされます。 値渡しは、大規模な構造体型を必要とするハイパフォーマンスのシナリオの場合、コードのパフォーマンスに影響を与える可能性があります。 値のコピーを回避するには、構造体型の変数を参照渡しします。 引数を参照渡しする必要があることを示すには、refout、または in のメソッド パラメーター修飾子を使用します。 メソッドの結果を参照渡しによって返すには、ref 戻り値を使用します。 詳細については、「安全で効率的な C# コードを記述する」をご覧ください。

struct 制約

また、struct 制約struct キーワードを使用して、型パラメーターが null 非許容値型であることを指定します。 構造体と列挙型の型は、どちらも struct 制約を満たしています。

変換

どの構造体型にも (ref struct 型を除く)、System.ValueType 型と System.Object 型の間にボックス化およびボックス化解除の変換が存在します。 また、構造体型と、これによって実装されるインターフェイスとの間にも、ボックス化とボックス化解除の変換が存在します。

C# 言語仕様

詳細については、C# 言語仕様の「構造体」セクションを参照してください。

struct 機能の詳細については、機能の提案に関する次の記述を参照してください。

関連項目