値型と参照型
プログラミングを学習すると必ずやらなければならない、そしてよく分からない?値型と参照型の違いについて。
このブログは初学者向けの教本では無く、初学者に説明した中でよく分かってもらえない・理解してもらえない分野のみを抜粋して記録するためのもので、このような基本的な話をするつもりはありませんでしたが、意外と分かっていない(分かった風になっている)のと、string型の話でどうしても連続性を持たせたかったので、簡単に説明してみようと思います。
C#(VB.NET)の型は「値型」と「参照型」の二つに分類されます。おなじみのint型やstring型、クラス、構造体、デリゲートなどもこのどちらかの型に分類されます。
値型 | string型以外のプリミティブ型 (int、double、decimal、date、boolなど) |
スタック領域を使用するので動作は軽快。また間接的な参照が発生しないので、アクセスも高速に行われる。 その反面、サイズが大きいとコストがかかる。 |
構造体 | ||
列挙型 | ||
Null許容型 | ||
参照型 | オブジェクト型 | ヒープ領域を使用するので値型よりは動作が遅い。また間接的な参照が発生するので、アクセスも値型に比べるとコストがかかる。 複製は行われない(参照先の複製のみ)ので、サイズの大小に関わらず代入は高速に行われる。 |
配列 | ||
string型 | ||
クラス | ||
デリゲート |
値型
値型は変数に値を直接保存します。必ず変数と値が1対1の関係になるのが特徴です。
int int1 = 100; int int2 = 200;
Dim int1 As Integer = 100 Dim int2 As Integer = 200
以下のようなイメージです。それぞれの変数ボックスに値が入っています。
当然、変数に値を代入すれば直接値が変更されますので、以下のようにしても結果は直感的に分かると思います。算数と一緒ですね。
int int1 = 100; int int2 = int1; Console.WriteLine(int2); int2 = 200; Console.WriteLine(int1); Console.WriteLine(int2);
Dim int1 As Integer = 100 Dim int2 As Integer = int1 Console.WriteLine(int2) int2 = 200 Console.WriteLine(int1) Console.WriteLine(int2)
100 100 200
値型は特に難しいことはないと思います。
参照型
問題は参照型です。これは変数と値が1対1の関係にはなりません。というのも、変数には直接値が保存されるのではなく、別の場所に値が保存され、その宛先(参照先)が変数に保存されます。ここで大事なことは、値型とは違い、「new」をすることです。これによって新しい保存先が作成されます。よって、newしないで以下のようなコードを書くと、変数と値の参照がn対1の関係になります。
List<int> lst1 = new List<int>() { 100, 200 }; List<int> lst2 = lst1;
Dim lst1 As List(Of Integer) = New List(Of Integer) From {100, 200} Dim lst2 As List(Of Integer) = lst1
つまり、lst2はnewせずlst1を代入していますので、lst1に保存されている宛先(ここではxxx)がlst2に代入される形です。
ここで変数lst2対して変更を加えてみましょう。
List<int> lst1 = new List<int>() { 100, 200 }; List<int> lst2 = lst1; lst2.Add(300); Console.WriteLine(string.Join(",", lst1)); Console.WriteLine(string.Join(",", lst2));
Dim lst1 As List(Of Integer) = New List(Of Integer) From {100, 200} Dim lst2 As List(Of Integer) = lst1 lst2.Add(300) Console.WriteLine(String.Join(",", lst1)) Console.WriteLine(String.Join(",", lst2))
lst2に対して300を追加しました。lst2は100、200、300の3つ値を保持しているのは簡単に分かると思います。では、lst1はどうなるでしょうか。lst2と同じ結果になると思った方はこの先読む必要はありません。
100,200,300 100,200,300
図で表すと以下の通りです。
lst1もlst2も参照型なので値は保持していません。あくまで宛先(ここではxxx)を保持しているにすぎないのです。よって、lst2に対して変化を加えると、同じ宛先を保持しているlst1も影響を受けるということになります。
では、以下の場合はどうでしょうか。
List<int> lst1 = new List<int>() { 100, 200 }; List<int> lst2 = lst1; lst2 = new List<int>() { 100, 200, 300 }; Console.WriteLine(string.Join(",", lst1)); Console.WriteLine(string.Join(",", lst2));
Dim lst1 As List(Of Integer) = New List(Of Integer) From {100, 200} Dim lst2 As List(Of Integer) = lst1 lst2 = New List(Of Integer) From {100, 200, 300} Console.WriteLine(String.Join(",", lst1)) Console.WriteLine(String.Join(",", lst2))
3行目でnewしていますので、新しい宛先(ここではyyy)が作成されていますので、lst2に変化が加わってもlst1は影響を受けません。
string型も参照型ですが、少し特殊なので別の機会にまとめます。