++C++; // 未確認飛行 C 避けて通れない「非同期処理」を克服しよう

Top総合 目次C# によるプログラミング入門

値型と参照型

このエントリーをはてなブックマークに追加

目次

キーワード

概要

C#の型(組込み型、クラス、構造体、列挙型)には大きく分けて2つのタイプがあります。 1つは値型と呼ばれるもので、もう1つは参照型と呼ばれるものです。 ここでは、その値型と参照型の違いについて説明していきます。

ポイント
  • C# には値型と参照型がある。
  • 値型: 変数に直接値が格納される。
  • 参照型: 変数が持っているのは参照情報(実体がどこにあるのかという情報)だけ。実体は別の場所に確保される。
  • 構造体は値型で、クラスは参照型になる。

値型と参照型の違い

値型(value type)と参照型(reference type)の違いは、その名の通り、その型の値を直に保持するか、値の参照を保持するかです。 この参照を持つというのがどういうことなのか説明するために、 以下のような2つのコードについて考えてみましょう。

// 値型(構造体は値型になる)
struct Point
{
  public int x, y;

  public Point(int x, int y){this.x = x; this.y = y;}
  public override string ToString()
  {
    return "(" + this.x.ToString() + ", " + this.y.ToString() + ")";
  }
}

class ValueTypeSample
{
  static void Main()
  {
    Console.Write("値型の場合");
    Point a = new Point(12, 5);
    Point b = a;
    Point c = a;
    Console.Write("a: {0}\nb: {1}\nc: {2}\n", a, b, c);
    b.x = 0;
    Console.Write("a: {0}\nb: {1}\nc: {2}\n", a, b, c);
  }
}
// 参照型(クラスは参照型になる)
class Point
{
  public int x, y;

  public Point(int x, int y){this.x = x; this.y = y;}
  public override string ToString()
  {
    return "(" + this.x.ToString() + ", " + this.y.ToString() + ")";
  }
}

class ReferenceTypeSample
{
  static void Main()
  {
    Console.Write("参照型の場合");
    Point a = new Point(12, 5);
    Point b = a;
    Point c = a;
    Console.Write("a: {0}\nb: {1}\nc: {2}\n", a, b, c);
    b.x = 0;
    Console.Write("a: {0}\nb: {1}\nc: {2}\n", a, b, c);
  }
}

この2つのコードは、その大部分はまったく一緒で、 Point型が構造体になっているか、 クラスになっているかという部分だけが異なります。

これまで、クラスについて、メソッドやコンストラクタ、プロパティなどの説明を行ってきましたが、 実はこれらはすべて構造体でも定義することができます。 こうしてみると、構造体とクラスはほとんど同じもののように見えるかもしれません。 (実際、構造体とクラスはかなり多くの共通点を持っています。) この2つのもっとも大きな違いは、 構造体は値型で、クラスは参照型であるということです。

コード中では、 まずa, b, cという3つの変数に同じ値を代入し、一度画面に値を出力します。 その後、bの値だけ変更し、再び画面に値を出力します。 出力結果は以下のようになります。

値型の場合
a: (12, 5)
b: (12, 5)
c: (12, 5)
a: (12, 5)
b: (0, 5)
c: (12, 5)
参照型の場合
a: (12, 5)
b: (12, 5)
c: (12, 5)
a: (0, 5)
b: (0, 5)
c: (0, 5)

値型(構造体)を用いたほうはbの値だけが変更され、 参照型(クラス)を用いたほうはbの値と一緒にacの値も変更されています。

この違いは、値型は代入時に値のコピーを受け取るのに対し、 参照型は値の実体への参照のみを受け取るために生じるものです。 この違いを図で説明すると以下のようになります。

値型参照型
代入時

図1:

それぞれの変数は値のコピーを保持。

図2:

値の実体は別のところにあり、 それぞれの変数は実体への参照のみを持つ。

bの値変更時

図3:

bの値のみ変更される。

図4:

bが参照している実体の値が変更される。 同じ実体を参照しているacも変更されたかのように見える。

値型と参照型の利点

値型と参照型にはそれぞれ利点・欠点があります。

値型は変数ごとに別個の値を保持するため、 代入時(関数に引数として渡す場合も含む)に値の複製を行う必要があります。 サイズが大きい(メンバー変数が多い)場合、複製に大きな手間がかかり非効率的です。 しかし、値を直接操作できるため、値の読み書きは高速になります。

一方、参照型は代入時には参照情報のみを渡すので、 どんなにサイズが大きくても大きな手間はかかりません。 しかし、値を操作する場合、参照情報を用いて実体のある場所を探してから値の操作を行う必要があるので、 値の読み書きは値型にくらべ低速になります。

また、 「クラスの継承」 や 「多態性とは」 で説明するような、継承や仮想メソッドなどの多態的な振る舞いは参照型でしかできません。

表1: 値型・参照型の特徴

値型参照型
代入時値の複製が生じる値は複製しない
利点間接参照が生じないので、メンバーアクセスが高速複製が生じないので、変数への代入・引数渡しが高速
欠点型のサイズが大きいとき、複製のコストが大きい間接参照が生じて、メンバーアクセス時に少しコストがかかる
継承・多態的ふるまいができない

このような特徴があるため、通常は データのサイズが小さく、継承の必要のないものは構造体として定義し、 それ以外のものはクラスとして定義します。

C#の型の分類

C#には組込み型、クラス、構造体など、さまざまな型がありますが、 これらは以下のように分類されます。

値型構造体型 ユーザー定義構造体(struct)
数値型整数型 byte, sbyte, char, short, ushort, int, uint, long, ulong
浮動小数点型 float, double
decimal
bool
列挙型(enum)
参照型 クラス(class)
インターフェース(interface)
デリゲート(delegate)
object
string
配列

注: 色付き文字で書かれているものは組込み型

[お問い合わせ](q)