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

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

構造体型を設計する場合は、class 型と同じ機能を使用できますが、次の例外があります。

  • パラメーターなしのコンストラクターを宣言することはできません。 すべての構造体型には、型の既定値を生成する暗黙的なパラメーターなしのコンストラクターが既に備わっています。

    注意

    C# 10 以降では、構造体型でパラメーターなしのコンストラクターを宣言できます。 詳細については、「パラメーターなしのコンストラクターとフィールド初期化子」セクションを参照してください。

  • インスタンス フィールドまたはプロパティを、それらの宣言で初期化することはできません。 ただし、static または const フィールド、あるいは静的プロパティについては、それらの宣言で初期化することができます。

    注意

    C# 10 以降では、インスタンス フィールドまたはプロパティを、それらの宣言で初期化することはできません。 詳細については、「パラメーターなしのコンストラクターとフィールド初期化子」セクションを参照してください。

  • 構造体型のコンストラクターでは、型のすべてのインスタンス フィールドを初期化する必要があります。

  • 構造体型は、他のクラスまたは構造体型から継承することができないほか、クラスのベースとすることもできません。 ただし、構造体型では interfaces を実装することができます。

  • 構造体型内でファイナライザーを宣言することはできません。

パラメーターなしのコンストラクターとフィールド初期化子

C# 10 以降では、次の例に示すように、構造体型でパラメーターなしのインスタンス コンストラクターを宣言できます。

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 ()
}

前の例で示したように、既定値の式では、パラメーターなしのコンストラクターは無視され、構造体型の既定値が生成されます。これは、すべての値型フィールドを既定値 (0 ビット パターン) に設定し、すべての参照型フィールドを null に設定することによって生成される値です。 構造体型の配列のインスタンス化では、パラメーターなしのコンストラクターも無視され、構造体型の既定値が設定された配列が生成されます。

C# 10 以降では、次の例に示すように、インスタンス フィールドまたはプロパティを宣言で初期化することもできます。

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

    public Measurement(double value, string description)
    {
        Value = value;
        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 ()
}

パラメーターなしのコンストラクターを明示的に宣言しない場合、構造体型にはパラメーターなしのコンストラクターが用意され、その動作は次のようになります。

  • 構造体型に明示的なインスタンス コンストラクターがある場合、またはフィールド初期化子がない場合、前の例のように、暗黙的なパラメーターなしのコンストラクターによって、フィールド初期化子に関係なく、構造体型の既定値が生成されます。

  • 構造体型に明示的なインスタンス コンストラクターがなく、フィールド初期化子がある場合、次の例に示すように、指定されたフィールドの初期化を実行するパラメーターなしのパブリック コンストラクターが、コンパイラによって合成されます。

    public struct Coords
    {
        public double X = double.NaN;
        public double Y = double.NaN;
    
        public override string ToString() => $"({X}, {Y})";
    }
    
    public static void Main()
    {
        var p1 = new Coords();
        Console.WriteLine(p1);  // output: (NaN, NaN)
    
        var p2 = default(Coords);
        Console.WriteLine(p2);  // output: (0, 0)
    
        var ps = new Coords[3];
        Console.WriteLine(string.Join(", ", ps));  // output: (0, 0), (0, 0), (0, 0)
    }
    

前の例で示したように、既定値の式と配列のインスタンス化では、フィールド初期化子は無視されます。

詳細については、パラメーターなしの構造体コンストラクター機能の提案に関するメモを参照してください。

構造体型のインスタンス化

C# では、宣言された変数を使用するには、事前にこれを初期化する必要があります。 構造体型の変数は (null 許容値型の変数でない限り) null とすることができないため、対応する型のインスタンスをインスタンス化する必要があります。 それにはいくつかの方法があります。

通常は、new 演算子を使用して適切なコンストラクターを呼び出すことによって、構造体型をインスタンス化します。 すべての構造体型に、少なくとも 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)
    }
}

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

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

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

ref 構造体

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

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

通常、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 以降で導入された機能の詳細については、次の機能の提案に関するメモを参照してください。

関連項目