構造体(struct)を理解しよう!

[C#] 構造体(struct)を理解しよう!

※ 当サイトは広告を含みます。

ここではC#の構造体について学びます。
構造体を凄く簡単に説明すると、劣化版クラスです。

りさ
りさ

あまりに酷い説明。

管理人
管理人

だって、現実的にそうなんだもん。

構造体(struct)

最初に伝えたとおり、構造体はクラスの劣化版です。構造体で可能なことはクラスで全て実現できます。
例えばフィールドとかメソッド、プロパティみたいな基本機能は構造体にも作成できます。
また、使用方法もクラスと同じです。と言うか、ぱっとみの見た目もクラスと同じような形してます。

逆に構造体はクラスの劣化版なので、いくつか機能に制限があります。
例を上げるなら構造体は継承不可能です。つまり、クラスのような継承関係は作れません。

まぁ、他にも色々あるのですが、実はC#の言語バージョンが上がるにつれて制限が減ってます。
ちなみに何らかの制限に違反すればビルドエラーになります。なので、試しに記述して駄目なら諦めてください。

構造体は値型

構造体の重要なポイントは1つ。構造体は値型です。
これだけ覚えましょう。他は劣化版クラスと思えば問題ないです。

定義方法

classの代わりにstructと記述します。
フィールドやプロパティの定義方法は変わりません。

構造体

以下がサンプルコードです。


namespace Sample
{
  // structを利用して定義する
  public struct Position
  {
    // プロパティ
    public int X { get; set; }
    public int Y { get; set; }

    // コンストラクタ
    public Position(int x, int y)
    {
      this.X = x;
      this.Y = y;
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      // インスタンスの生成はクラスと同じ
      Position pos = new Position(512, 256);

      Console.WriteLine($"X={pos.X}, Y={pos.Y}");
    }
  }
}

メリット

構造体のメリットは値型な部分です。と言うか、これ以外にメリットがありません。

値型は参照型と異なり、引数の値渡しでコピーを必要とします。その代わりスタックに格納されるので早いです。
また、値型は非nullとなるため、構造体はクラスと違って必ず初期化(Default値が利用)されてます。

つまり、以下みたいなコードでも例外は発生しません。
仮にクラスで同じことをするとNullReferenceExceptionが発生します。


namespace Sample
{
  // structを利用して定義する
  public struct Position
  {
    // プロパティ
    public int X { get; set; }
    public int Y { get; set; }

    // コンストラクタ
    public Position(int x, int y)
    {
      this.X = x;
      this.Y = y;
    }
  }

  internal class Program
  {
    // ローカル変数だと初期化されないので、ここに記述する
    private static Position pos;

    static void Main(string[] args)
    {
      // クラスと違ってnewしなくてもアクセス可能
      pos.X = 128;
      pos.Y = 128;

      Console.WriteLine($"X={pos.X}, Y={pos.Y}");
    }
  }
}

POINT構造体を初期化しない場合は各フィールド型のDefault値が利用される。

値型の特性上、大量のフィールドを所有する構造体を値渡しするとパフォーマンスが落ちます。
故に構造体はデータサイズの小さい集合をまとめるために使うのがベストです。
例として、先ほど利用した位置座標(X,Y)を所有する構造体は合計8byteになります。

メモリサイズ

C#を問わず、大抵のプログラミング言語はメモリを効率的に利用(速度も含めて)するように設計されてます。
実はメモリへのアクセスは4byte8byteのほうが効率が良く、3の倍数とか意味不明な倍数だと効率が下がります。

でっ、C#ではそれらを踏まえてオブジェクトのサイズを良い感じに整えてくれる機能があり、構造体も影響を受けます。
具体的には端数になったら空き領域を作って無理やり4か8の倍数になります。倍数はプラットフォームで異なるかも。
正直、倍数の数値はさほど重要ではないので深くは追求しません。気になる方は自分で調べてください。

実際に以下のコードを実行すると各サイズが表示されますが、構造体の合計値とは異なるはずです。


namespace Sample
{
  public struct Set
  {
    public int X;
    public int Y;

    public byte B;
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine($"sizeof(int)={sizeof(int)}");
      Console.WriteLine($"sizeof(byte)={sizeof(byte)}");
      Console.WriteLine($"sizeof(Set)={System.Runtime.InteropServices.Marshal.SizeOf(typeof(Set))}");
    }
  }
}

POINT構造体のメモリサイズは、必ずしもフィールドの合計値ではない。

構造体 or クラスの選択

さて、誰しもが気になると思いますが、クラスではなく構造体を利用すべきポイントは何でしょうか?
分かりやすいのは、先程のような数個の値型の集合です。小さいデータかつ複数で1つを成すようなデータは構造体が向いてます。

ここで朗報です。実はC#の公式ドキュメントに構造体を選ぶべきポイントが書いてあります。
意味が分からない部分もあると思いますが、気にせず読んでみましょう。

検討すべき条件型のインスタンスが小さく、有効期間が短いことが多い場合、または他のオブジェクトに埋め込まれることが多い場合は、クラスではなく構造体を定義することを検討してください。

満たすべき条件(全て満たさないと駄目)・プリミティブ型 (int, doubleなど) と同様に、論理的に単一の値を表す。
・インスタンスのサイズが16byte未満である。
・不変である。
・頻繁にボックス化する必要がない。

なんですが、これは理想です。完全にこれを満たすことができるパターンは現実世界には殆ど無いです。

管理人
管理人

と言うことで、ありがたい忠告は全部無視します。

りさ
りさ

おい。

管理人
管理人

仕方ないじゃん。だって満たせないんだもん。

個人的な答えですが、選択に迷うくらいならクラスが正解です。
構造体は継承できない等の制約があるため、設計の見直しが発生するとかなり辛いです。
特に「やっぱりクラスに変えよ」とか思っても、すでに手遅れな場合があります。

オススメするのは例で記述したような、複数個の値型で1つを表現する構造です。
利用する側からも関連する値が集合するため、非常に分かりやすい設計になります。

管理人
管理人

迷ったらクラス。もはや、この感覚でいい。

あとがき

TimeSpan型とか、.NETに標準搭載されてる構造体もあります。
自前で構造体を作る場合は、これらの使い方を参考にするのがオススメですよ。

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。