次の方法で共有


C# 構造体

ヒント

ソフトウェアの開発は初めてですか? 最初に、 作業の開始 に関するチュートリアルから始めます。 コードに軽量の値型が必要になると、構造体が発生します。

別の言語で経験がありますか? C# 構造体は、C++ または Swift の構造体に似た値型ですが、ボックス化されるとマネージド ヒープ上に存在し、インターフェイス、コンストラクター、メソッドをサポートします。 C# の 読み取り専用構造体 セクションを、特定のパターンについてスキミングします。 レコード構造体については、「 レコード」を参照してください。

構造体は、ヒープ上のオブジェクトへの参照ではなく、そのデータをインスタンスに直接保持する値型です。 新しい変数に構造体を割り当てると、ランタイムはインスタンス全体をコピーします。 1 つの変数に対する変更は、各変数が異なるインスタンスを表しているため、もう一方の変数には影響しません。 モデリング動作ではなくデータを格納する主な役割を持つ小規模で軽量な型には構造体を使用します。 たとえば、座標、色、測定、構成設定などがあります。

構造体を使用する場合

型を定義する場合、次の条件で構造体を使用します:

  • 1 つの値または関連する値の小さなグループ (約 16 バイト以下) を表します。
  • 値セマンティクスがあります。同じデータを持つ 2 つのインスタンスが等しい必要があります。
  • 主に、動作のモデルではなく、データ コンテナーです。
  • 基本型からの継承は必要ありません (構造体は他の構造体やクラスから継承できませんが、インターフェイスを実装できます)。

クラス、レコード、タプル、インターフェイスを含むより広範な比較については、「 型の種類を選択する」を参照してください。

構造体を宣言する

struct キーワードを使用して構造体を定義します。 構造体には、クラスと同様に、フィールド、プロパティ、メソッド、コンストラクターを含めることができます。

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

    public readonly double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }

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

Point構造体は、2 つのdouble値を格納し、2 つのポイント間の距離を計算するメソッドを提供します。 DistanceTo メソッドは構造体の状態を変更しないため、readonlyマークされます。 このパターンは、読み取り専用メンバーで説明されています。

値意味論

構造体は 値型です。 代入はデータをコピーするので、各変数は独自の独立したコピーを保持します。

var p1 = new Point { X = 3, Y = 4 };
var p2 = p1; // copies the data
p2.X = 10;

Console.WriteLine(p1); // (3, 4)  — p1 is unchanged
Console.WriteLine(p2); // (10, 4) — only p2 was modified

構造体はデータ コンテナーであるため、割り当てではすべてのデータ メンバーが新しい独立したインスタンスにコピーされます。 各コピーは異なります。 1 つを変更しても、もう一方には影響しません。 この動作は、代入が参照のみをコピーし、両方の変数が同じオブジェクトを共有する クラスとは異なります。 区別の詳細については、「値の 型と参照型」を参照してください

構造体コンストラクター

構造体のコンストラクターは、クラスで行うのと同じ方法で定義できます。 構造体には、カスタムの既定値 を設定するパラメーターなしのコンストラクター を含めることができます。 "パラメーターなしのコンストラクター" という用語は、new (コンストラクター ロジックを実行する) で作成されたインスタンスを、式で作成されたdefaultのインスタンス (すべてのフィールドをゼロ初期化) と区別します。

struct ConnectionSettings
{
    public string Host { get; set; }
    public int Port { get; set; }
    public int MaxRetries { get; set; }

    public ConnectionSettings()
    {
        Host = "localhost";
        Port = 8080;
        MaxRetries = 3;
    }
}

パラメーターなしのコンストラクターは、引数なしで new を使用すると実行されます。 default式はコンストラクターをバイパスし、すべてのフィールドを既定値 (0nullfalse) に設定します。 違いに注意してください。

var custom = new ConnectionSettings();
Console.WriteLine($"{custom.Host}:{custom.Port} (retries: {custom.MaxRetries})");
// localhost:8080 (retries: 3)

var defaults = default(ConnectionSettings);
Console.WriteLine($"{defaults.Host ?? "(null)"}:{defaults.Port} (retries: {defaults.MaxRetries})");
// (null):0 (retries: 0)

コンパイラは、コンストラクターで明示的に設定しないフィールドを自動的に初期化します。 既定値以外が必要なフィールドのみを初期化できます。

struct GameTile
{
    public int Row { get; set; }
    public int Column { get; set; }
    public bool IsBlocked { get; set; }

    public GameTile(int row, int column)
    {
        Row = row;
        Column = column;
        // IsBlocked is automatically initialized to false
    }
}

次の例では、 IsBlockedの既定値を表示します。

var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False

IsBlocked プロパティはコンストラクターに割り当てられないため、コンパイラによってfalseに設定されます (bool の既定値)。 この機能により、いくつかのフィールドのみを設定する必要があるコンストラクターの定型句が減ります。

読み取り専用の構造体と読み取り専用のメンバー

readonly structでは、インスタンス メンバーが構造体の状態を変更していないことを保証します。 コンパイラは、すべてのフィールドと自動実装プロパティを読み取り専用にすることで、この保証を適用します。

readonly struct Temperature
{
    public double Celsius { get; }

    public Temperature(double celsius) => Celsius = celsius;

    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;

    public override string ToString() => $"{Celsius:F1}°C ({Fahrenheit:F1}°F)";
}

次の例では、 Temperature インスタンスを作成し、そのプロパティを読み取ります。

var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only

構造体全体を不変にする必要がない場合は、代わりに個々のメンバーを readonly としてマークします。 readonly メンバーは構造体の状態を変更できません。コンパイラはその保証を検証します。

struct Velocity
{
    public double X
    {
        readonly get;
        set;
    }

    public double Y
    {
        readonly get;
        set;
    }

    public readonly double Speed => Math.Sqrt(X * X + Y * Y);

    public readonly override string ToString() => $"({X}, {Y}) speed={Speed:F2}";
}

次の例は、変更可能なプロパティが変更されたときに readonly メンバーが更新された値を返す場合を示しています。

var v = new Velocity { X = 3, Y = 4 };
Console.WriteLine(v.Speed); // 5
Console.WriteLine(v);       // (3, 4) speed=5.00
v.X = 6;
Console.WriteLine(v.Speed); // 7.211...

メンバーを readonly マークすると、コンパイラが防御コピーを最適化するのに役立ちます。 readonly パラメーターを受け取るメソッドにin構造体を渡すと、コンパイラはコピーが不要であることを認識します。

こちらも参照ください