概要
C#の型(組込み型、クラス、構造体、列挙型)には大きく分けて2つのタイプがあります。 1つは値型と呼ばれるもので、もう1つは参照型と呼ばれるものです。 ここでは、その値型と参照型の違いについて説明していきます。
ポイント
-
C# には値型と参照型がある。
-
値型: 変数に直接値が格納される。
-
参照型: 変数が持っているのは参照情報(実体がどこにあるのかという情報)だけ。実体は別の場所に確保される。
-
構造体は値型で、クラスは参照型になる。
おさらい: C# の型の分類
C#の型は以下のように分類されます。
本項では、この青い四角で囲った分類、値型と参照型の違いについて説明していきます。
値型と参照型の違い
「C# の型」で概要だけ紹介していますが、 C# の型には値型と参照型という区別があります。 C# の型の中で、構造体とクラスは非常に似通った機能ですが、この2者を区別する一番大きなポイントが、値型か参照型かです。 構造体(struct キーワードを使って定義した型)は値型に、 クラス(class キーワードを使って定義した型)は参照型になります。
値型(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
の値と一緒にa
とc
の値も変更されています。
この違いは、値型は代入時に値のコピーを受け取るのに対し、 参照型は値の実体への参照のみを受け取るために生じるものです。 この違いを図で説明すると以下のようになります。
値型 | 参照型 | |
---|---|---|
代入時 | それぞれの変数は値のコピーを保持。 | 値の実体は別のところ※にあり、 それぞれの変数は実体への参照のみを持つ。 |
b の値変更時 |
b の値のみ変更される。
|
b が参照している実体の値が変更される。
同じ実体を参照しているa とc も変更されたかのように見える。
|
※ 「ヒープ」と呼ばれる領域に実体を確保します。
null
「クラス」のページの「null」などで「有効なインスタンスを持っていない」という状態を null という」と言っていますが、この点について、「参照」という観点から改めて説明します。
参照型の場合、「実体はどこか別の場所にあって、変数はそれを参照しているだけ」という状態になっています。 そして、元々の意味での null は「どこも参照していない」ということを表しています。
内部挙動的には、C# の参照型変数はポインターと大差がなくて(ガベージ コレクションに管理されているかどうかだけの差)、null も内部的には単なる「0 で表される番地」になっています。 これは、「不定なよくわからない値よりは、わかりやすく無効な参照であることがわかる値がある方がマシ」ということで、変数を0で埋めています。
ただ、現在では、null許容値型などの機能もあり、 参照かどうかとかは関係なく、単に「無効な値」を表すために null が使われます。
値型と参照型の利点
値型と参照型にはそれぞれ利点・欠点があります。
値型は変数ごとに別個の値を保持するため、 代入時(関数に引数として渡す場合も含む)に値の複製を行う必要があります。 サイズが大きい(メンバー変数が多い)場合、複製に大きな手間がかかり非効率的です。 しかし、値を直接操作できるため、値の読み書きは高速になります。
一方、参照型は代入時には参照情報のみを渡すので、 どんなにサイズが大きくても大きな手間はかかりません。 しかし、値を操作する場合、参照情報を用いて実体のある場所を探してから値の操作を行う必要があるので、 値の読み書きは値型にくらべ低速になります。
また、「クラスの継承」や「多態性とは」で説明するような、継承や仮想メソッドなどの多態的な振る舞いは参照型でしかできません。
値型 | 参照型 | |
---|---|---|
代入時 | 値の複製が生じる | 値は複製しない |
利点 | 間接参照が生じないので、メンバーアクセスが高速 | 複製が生じないので、変数への代入・引数渡しが高速 |
スタックを使うのでメモリ確保が早い | ||
欠点 | 型のサイズが大きいとき、複製のコストが大きい | 間接参照が生じて、メンバーアクセス時に少しコストがかかる |
継承・多態的ふるまいができない | ヒープを使うのでメモリ確保が少し遅い |
このような特徴があるため、通常は データのサイズが小さく、継承の必要のないものは構造体として定義し、 それ以外のものはクラスとして定義します。
C#の型の分類
C#には組込み型、クラス、構造体など、さまざまな型がありますが、 これらは以下のように分類されます。
値型 | 構造体型 |
ユーザー定義構造体(struct )
|
||
数値型 | 整数型 |
byte, sbyte, char, short, ushort, int, uint, long, ulong
|
||
浮動小数点型 |
float, double
|
|||
decimal
|
||||
bool
|
||||
列挙型(enum )
|
||||
参照型 |
クラス(class )
|
|||
インターフェース(interface )
|
||||
デリゲート(delegate )
|
||||
object
|
||||
string
|
||||
配列 |
注: 色付き文字で書かれているものは組込み型