2-3 値型と参照型

2-3-1   2 種類のデータ型

  C# には、**2-2-1 「C# で予約されたデータ型」**で触れたように、予約されたデータ型がいくつかあります。このデータ型は、大きく 2 種類に分類することができます。それが「値型」と「参照型」です。この 2 種類はデータのメモリ確保の方法が異なります。

 変数などでデータを扱う際には、そのデータ型が「値型」であるか、「参照型」であるか意識する必要があります。この分類は各データ型によって固定です。例えば「int 型」の変数は常に値型です。また「string 型」の変数では、その変数は常に「参照型」です(表 2-4 )。

[例]値型と参照型

  
int x;    // 値型の変数
string str; // 参照型の変数

●表 2-4 ● 値型と参照型

型(タイプ) 型の分類
sbyte 値 型
short
int
long
byte
ushort
uint
ulong
float
double
bool
char
decimal
object 参照型
string

 このあとは、「値型」と「参照型」がそれぞれどのようなものかを説明します。

2-3-2  値型とは?

 値型データの変数を宣言した場合、その型のデータ領域自体が確保されることを意味します。例えば、int 型の変数 x をローカル変数として宣言すれば、int 型は値型で 4 バイト長であるので、この変数 x のために 4 バイトの領域が確保されます。また、long 型の変数 y を宣言すれば、long 型は 8 バイトであることから、この変数 y のために 8 バイトの領域が確保されます。

 以下の例は、この変数宣言をおこなっている例です( Main メソッドのみ抜粋)。

[例]値型である int 型、long 型の変数領域を確保

  
1: public static void Main(string [] args)
2: {
3:  int x; //4 バイトの領域確保
4:  long y; //8 バイトの領域確保
5: }

 なお、この例では、変数 x と変数 y は Main メソッドのローカル変数であるので、Main メソッドの開始とともに変数 x と変数 y の領域が確保され、Main メソッドの終了とともに変数は解放されます。

 この値型のデータの扱いは、特に難しくはないと思います。少々わかりづらいのが、次項で説明する参照型です。

2-3-3  参照型とは?

 参照型は、値型とは異なり、その変数を宣言したからといって、そのデータ型にみあう領域が確保されるわけではありません。

  C# で予約されたデータ型のうち、参照型は「object 型」と「string 型」です。この 2 つの型については、以下のように変数を宣言しても、その型にみあう大きさのデータ領域がすぐに確保されるわけではありません( object 型を理解するには、**第 4 章「クラスの定義と実装」**でのクラスとクラス派生の概念が必要なので、ここでは省略します)。

[例]参照型である object 型、string 型の変数宣言

  
1: public static void Main(string [] args)
2: {
3:  object x; // データ領域はまだ確保されない
4:  string y; // データ領域はまだ確保されない
5: }

 このプログラムにあるように、変数 x と変数 y をローカル変数として宣言した場合、メモリ上に確保されるのはその型のデータ実体ではなく、データ実体の「メモリ上の位置情報」を格納する領域です。メモリ上の位置情報とは、データ実体がメモリ上のどこに存在するか特定するための情報です。この位置情報のことを「参照情報」ともいいます。この参照情報の正確なフォーマットは意識する必要はありません(参照情報のフォーマットは本書では言及しません)。しかし、メモリ上の位置を特定するためには参照情報として、メモリ番地(メモリアドレス)などを利用していると考えられます。

 以下のように、string 型変数 s に文字列定数を代入する場合、感覚的には変数 s に文字列データが代入されるようにみえます。しかし厳密には、ダブルコーテーションで囲まれたデータが、CLR ( Common Language Runtime )によってメモリ領域に確保されており、その文字列データが存在する位置の情報が、変数 s に代入されています。このような状態を「変数 s は、文字列"hello, world!"を参照している」と表現します。変数 s に代入されるのは、文字列データの実体ではなく、文字列データを参照するための位置情報です。

[例]参照情報を変数 s に設定する

  
    string s;
s = "hello, world!";
  

 この例をみる限り、値型と参照型のデータの取り扱い方に違いはあまりみえないかもしれません。しかし、2-8 「基本的なメソッド定義と呼び出し」で詳しく触れますが、メソッド呼び出しでのデータの受け渡しなどでは、データの扱い方に大きく影響してきます。ここでは、値型と参照型の基本的な性質の違いを理解するまでにとどめておきます。値型と参照型での「=」演算子を使った代入式を比較してみると、以下のようになります(図 2-1 )。

図 1

●図 2-1 ● 値型変数と参照型変数の値の代入

 この例でもわかるように、参照型の変数 s 自体は、string 型のデータ実体ではないことがわかります。それでは、参照型データの実体はどのように確保されるのでしょうか?

 文字列定数の場合は、上記の例のようにダブルコーテーションを使った文字列データを変数に代入するだけで string 型データが確保されます。文字列に関しては、書き方はそれほど難しくありません。それ以外の参照型データでは、new キーワードを使って明示的にデータ領域を確保します。new キーワードについては、この後の**2-4 「配列」と第 4 章「クラスの定義と実装」**で登場します。

<ワンポイント>

● for VB6

  VB でいえば、参照型のデータは Object 型や任意の COM オブジェクトのデータ型です。以下のように変数が宣言されている場合、1 行目の変数 x が値型なので、Integer 型データが格納される領域を確保しますが、2 行目や 3 行目の変数の宣言だけでは、「Object」型や「Excel.Application」型のデータ実体は存在しません。実体を生成するためには、4 行目にあるように new キーワードなどで明示的につくる必要があります。VB の Set ステートメントは、参照情報を変数に設定する機能があります。

[例] Visual Basic の変数宣言

  1: public static void Main(string [] args)
2: {
3:  object x; // データ領域はまだ確保されない
4:  string y; // データ領域はまだ確保されない
5: }

 なお、Visual Basic 6.0 では参照情報の代入には Set を使い、通常の値の代入には単に「=」演算子だけ(または Let )を使うというように、使い分けがありましたが、C# では値型の代入も参照型の代入も「=」演算子を使います。

● for C++

 以下の例は、C++ の変数宣言です。C++ のデータは、「値型」、「ポインタ型」、「参照型」の 3 つに分類できますが、その役割の観点からすると、C# の参照型は C++ のポインタ型といえます。

  C++ の参照型は、ある特定の変数の別名(エイリアス)です。C++ のポインタ型と C# の参照型は、後から参照情報(アドレス)を入れ替えることができますが、C++ の参照型は、後から参照情報を入れ替えることはできません。

[例] C++ の変数宣言

  
1: int x;           //C++ の値型
2: char* s = "Hello, world!"; //C++ のポインタ型
3: int &y; = x; //C++ 参照型 (x と y は一身同体)、C# は該当なし

 もちろん、C# の参照型が C++ のポインタ型に相当するといっても、C# は CLR 環境下で実行されるので、C# の参照型は任意のアドレスに自由にはアクセスできない、タイプセーフなデータ型です。