構造体型 (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})";
}

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

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

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

readonly 構造体

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

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

それにより、readonly 構造体のどのメンバーも構造体の状態を変更しないことが保証されます。 C# 8.0 以降では、コンストラクターを除く他のインスタンス メンバーは、暗黙的に 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 インスタンス メンバー

C# 8.0 以降では、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 の間には区別があります。 たとえば、次のようなコードについて考えます。

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 にする必要があります。 パラメーターなしの public コンストラクターを作成しないと、コンパイラによって生成されます。 コンパイラによって生成されたパラメーターなしのコンストラクターでは、任意のフィールド初期子が実行されて、他のすべてのフィールドには既定値が生成されます。 フィールド初期化子を宣言する場合は、明示的なコンストラクターを 1 つ宣言する必要があります。 1 つの明示的なコンストラクターは、パラメーターなしのコンストラクターにすることができます。 本体は空でもかまいません。 詳細については、パラメーターなしの構造体コンストラクター機能の提案に関するメモを参照してください。

構造体型のすべてのインスタンス フィールドにアクセスできる場合は、それを 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# コードを記述する」をご覧ください。

ref 構造体

C# 7.2 以降、ref 修飾子は、構造体型の宣言内で使用できます。 ref 構造体型のインスタンスはスタック上に割り当てられます。マネージド ヒープにエスケープすることはできません。 これを確実にするために、コンパイラでは次のように ref 構造体型の使用が制限されます。

  • ref 構造体を配列の要素型にすることはできません。
  • ref 構造体をクラスまたは非 ref 構造体のフィールドの宣言型にすることはできません。
  • ref 構造体ではインターフェイスを実装できません。
  • ref 構造体を System.ValueType または System.Object にボックス化することはできません。
  • ref 構造体を型引数にすることはできません。
  • ref 構造体変数をラムダ式またはローカル関数でキャプチャすることはできません。
  • ref 構造体変数を async メソッド内で使用することはできません。 ただし、同期メソッドでは ref 構造体変数を使用できます (Task または Task<TResult> を返すメソッドなど)。
  • ref 構造体変数を反復子内で使用することはできません。

C# 8.0 以降、破棄可能な ref 構造体を定義することができます。 これを行うには、ref 構造体が、破棄可能なパターンに適合していることを確認してください。 つまり、ここには、アクセス可能かつパラメーターなしで、void 戻り値の型が含まれる、インスタンスまたは拡張 Dispose メソッドが含まれています。

通常、ref 構造体型のデータ メンバーも含む型が必要な場合は、ref 構造体型を定義します。

public ref struct CustomRef
{
    public bool IsValid;
    public Span<int> Inputs;
    public Span<int> Outputs;
}

ref 構造体を readonly として宣言するには、型宣言内で readonly 修飾子と ref 修飾子を組み合わせます (readonly 修飾子は ref 修飾子よりも前にある必要があります)。

public readonly ref struct ConversionRequest
{
    public ConversionRequest(double rate, ReadOnlySpan<double> values)
    {
        Rate = rate;
        Values = values;
    }

    public double Rate { get; }
    public ReadOnlySpan<double> Values { get; }
}

.NET では、ref 構造体の例として System.Span<T>System.ReadOnlySpan<T> があります。

struct 制約

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

変換

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

C# 言語仕様

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

C# 7.2 以降で導入された機能の詳細については、次の機能の提案に関するメモを参照してください。

関連項目