構造体 クラスとの違い

基本文法
C#言語の構造体は以下のような特徴があります。
  • 参照型ではなくて値型
  • クラスと同様に状態(フィールド)と振る舞い(メソッド)とを定義できる
  • ヒープではなくてスタックにアロケートされる
  • 自身はSystem.ValueType型を(暗黙的に)直接継承する
  • 継承できない(明示的に親を指定できない・自身も親になれない)
  • interfaceを実現(継承)することはできる
  • 引数無しのコンストラクタを定義できない
  • ファイナライザを定義できない

他の言語で使われる構造体とは用語が同じでも中身は違っている可能性が高いです。
クラスの下位互換のような機能の割に実装や使い方が難しい。
使いどころはあまりないかも(ネイティブをP/Invokeする時ぐらい?)。

値型はメソッドの引数で値渡

メソッドの引数に構造体を指定した場合は値渡し(つまりインスタンスのコピー)となります。
メソッド内で値を書き換えても呼び出し元の値は変わりません。
クラスと同様に参照渡しにするにはrefを指定する必要があります。


    // 構造体
    struct StructSample
    {
        public int Value { get; set; }
    }
    // クラス
    class ClassSample
    {
        public int Value { get; set; }
    }

    class Sample
    {
        static void Main(string[] args)
        {
            StructSample ss = new StructSample{ Value = 3 };
            ClassSample cs = new ClassSample  { Value = 3 };

            SetValue(ss); // 構造体の値渡し
            SetValue(cs); // クラスの参照し

            int value1 = ss.Value; //   3 
            int value2 = cs.Value; // 100

            SetValueRef(ref ss); // 構造体の参照し

            int value3 = ss.Value; // 200
        }

        static void SetValue(StructSample structSample)
        {
            structSample.Value = 100;
        }

        static void SetValue(ClassSample classSample)
        {
            classSample.Value = 100;
        }

        static void SetValueRef(ref StructSample  structSample)
        {
            structSample.Value = 200;
        }
    }

インスタンス化

構造体の全フィールドにアクセスできる場合は、new演算子なしで使うことができる。

    class Program
    {
        static void Main(string[] args)
        {

            SampleStruct sampleStruct;

            sampleStruct.X = 10;

            // Yの値が初期化されてないのでエラー
            //int sum = sampleStruct.GetSum();

            sampleStruct.Y = 20;

            // 全フィールドに初期値が設定されたので使える
            int sum = sampleStruct.GetSum();
        }
    }

    struct SampleStruct
    {
        public int X;
        public int Y;

        public int GetSum() => X + Y;
    }

構造体の初期化

・インスタンスフィールドやプロパティを宣言で初期化できない
・パラメタなしのコンストラクタを宣言できない
・コンストラクタで初期化する場合は全フィールドを初期化する必要がある

※C#の版により構造体の初期化に関する仕様がだいぶ違うので要注意
    public struct Sample
    {
        public int ID { get; set; } 
        public string Name { get; set; }

        // フィールドで初期化できない
        // public int ID { get; set; } = 10;
 
        // 引数なしのコンストラクタは定義できない
        //public Shop()
        //{ }
 
        // これが可能
        public Shop(int id, string name)
        {
            ID = id;
            Name = name;
        }

        // 全フィールドを初期化する必要がある
        //public Shop(int id)
        //{
        //    ID = id;
        //}
 
    }

readonly 構造体

・C#7.2で採用された新しい文法
・すべてのフィールドにもreadonly修飾子が必要
・すべてのプロパティは読取専用である必要がある

    readonly struct SampleStruct
    {
        // readony無しのフィールドは定義できない
        //public int X;

        public readonly int X;

        // 読取専用のみ定義可能
        //public int Y { get; set; }

        public int Y { get; }

        public SampleStruct(int x , int y)
        {
            X = x;
            Y = y;
        }

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

    }

ref構造体

C#7.2で追加された新しい文法
別途記事にする予定です。