C#の型(組込み型、クラス、構造体、列挙型)には大きく分けて2つのタイプがあります。 1つは値型と呼ばれるもので、もう1つは参照型と呼ばれるものです。 ここでは、その値型と参照型の違いについて説明していきます。
値型(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 + ", " + this.y + ")"; } } class ValueTypeSample { public 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 + ", " + this.y + ")"; } } class ReferenceTypeSample { public 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の値と一緒にaとcの値も変更されています。
この違いは、値型は代入時に値のコピーを受け取るのに対し、 参照型は値の実体への参照のみを受け取るために生じるものです。 この違いを図で説明すると以下のようになります。
| 値型 | 参照型 | |
|---|---|---|
| 代入時 |
それぞれの変数は値のコピーを保持。 |
値の実体は別のところにあり、 それぞれの変数は実体への参照のみを持つ。 |
bの値変更時
|
|
|
値型と参照型にはそれぞれ利点・欠点があります。
値型は変数ごとに別個の値を保持するため、 代入時(関数に引数として渡す場合も含む)に値の複製を行う必要があります。 サイズが大きい(メンバ変数が多い)場合、複製に大きな手間がかかり非効率的です。 しかし、値を直接操作できるため、値の読み書きは高速になります。
一方、参照型は代入時には参照情報のみを渡すので、 どんなにサイズが大きくても大きな手間はかかりません。 しかし、値を操作する場合、参照情報を用いて実体のある場所を探してから値の操作を行う必要があるので、 値の読み書きは値型にくらべ低速になります。
また、 「クラスの継承」 や 「多態性とは」 で説明するような、継承や仮想メソッドなどの多態的な振る舞いは参照型でしかできません。
表1: 値型・参照型の特徴
| 値型 | 参照型 | |
|---|---|---|
| 代入時 | 値の複製が生じる | 値は複製しない |
| 利点 | 間接参照が生じないので、メンバアクセスが高速 | 複製が生じないので、変数への代入・引数渡しが高速 |
| 欠点 | 型のサイズが大きいとき、複製のコストが大きい | 間接参照が生じて、メンバアクセス時に少しコストがかかる |
| 継承・多態的ふるまいができない |
このような特徴があるため、通常は データのサイズが小さく、継承の必要のないものは構造体として定義し、 それ以外のものはクラスとして定義します。
C#には組込み型、クラス、構造体など、さまざまな型がありますが、 これらは以下のように分類されます。
| 値型 | 構造体型 |
ユーザー定義構造体(struct)
| ||
| 数値型 | 整数型 |
| ||
| 不動小数点型 |
| |||
| ||||
| ||||
列挙型(enum)
| ||||
| 参照型 |
クラス(class)
| |||
インターフェース(interface)
| ||||
デリゲート(delegate)
| ||||
| ||||
| ||||
| 配列 | ||||
注: