参照型と値型
オブジェクトの代入
.NET Framework の基本型である int 型や double 型などの数値、及び char 型や bool 型など、単一の値を表すデータを値型(value type)と呼びます。これに対し、クラス型のオブジェクトのような複合的な情報を持つオブジェクトは参照型(reference type)と呼ばれ、値型と区別されます。
値型の変数は、他の変数やメソッドのパラメータにデータを渡すとき、その内容を複製します。値を別の変数に代入すると、値の複製が変数に保存されます。
int x = 10; int y = x; y = 100; System.Console.WriteLine("x=" + x + ", y=" + y);
上のコードでは int 型の x 変数を、同じ int 型の y 変数に代入しています。このとき x 変数に保存されている値 10 が y 変数に複製されます。その後 y 変数に 100 を代入しているため y 変数の値は 100 に書き換えられます。その結果、出力は x 変数が 10、y 変数が 100 となります。何も特別なことはありません。
ところが、参照型は非常に興味深い動作をします。参照型の変数はインスタンスが配置されているメモリ上の有効なアドレスを表す数値を保存しています。プログラムは参照からインスタンスにアクセスしてオブジェクトを操作します。クラス型の変数は全て参照型であり、その値はインスタンスが保存されているメモリアドレスです。C言語を理解している人にはポインタに相当するものだと説明したほうが早いでしょう。
上のコードのように、クラス型の変数を別の変数に代入した場合、インスタンスは複製されません。単に、変数が持つインスタンスへの参照が複製されます。複数の変数が同じインスタンスを参照している場合、ある変数の操作が、別の変数に影響することがあるため注意しなければなりません。
class MagicalGirl { public string Name { get; set; } } class Test { public static void Main(string[] args) { MagicalGirl nanoha = new MagicalGirl(); nanoha.Name = "高町なのは"; MagicalGirl stern = nanoha; stern.Name = "星光の殲滅者"; System.Console.WriteLine("nanoha.Name=" + nanoha.Name); System.Console.WriteLine("stern.Name=" + stern.Name); } }
コード1は参照型の代入が単純な参照の複製であることを証明します。最初に MagicalGirl クラスのインスタンスを生成し、nanoha 変数に代入しています。続いて nanoha 変数を stern 変数に代入しています。ここが重要です。
このプログラムでは 1 度しか new 演算子によるオブジェクト生成式を実行していないため、メモリ上のインスタンスは 1 つしかありません。nanoha 変数を stern 変数に代入するということは、nanoha 変数が保持しているインスタンスの参照を、stern 変数に複製するということであり、インスタンスそのものを複製するということではありません。よって、nanoha 変数と stern 変数は、全く同じオブジェクトです。
stern 変数から Name プロパティに新しい文字列を設定していますが、実行結果から確認できるように、stern 変数の変更が nanoha 変数にも反映されています。2 つの変数が同じインスタンスを共有していることが理解できるでしょう。代入だけではなく、メソッドパラメータに渡した場合も同様です。
null 参照
参照型の変数が正しい参照先を持っていない状態は定数 null によって表されます。null キーワードは、あらゆる参照型と互換性のある特別な値で、インスタンスを参照していない(有効なインスタンスが存在しない)ことを表します。
有効なインスタンスへの参照を持たない null の変数からオブジェクトにアクセスしようとした場合 NullReferenceException と呼ばれる例外が発生します。例外について詳細は後述しますが、プログラムに矛盾が発生し、何らかの解決処理を施さない限り復帰できない状態に陥ったことを表します。
class MagicalGirl { public string Name { get; set; } } class Test { public static void Main(string[] args) { MagicalGirl unknown = null; unknown.Name = "白い魔法少女"; } }
コード2は意図的にクラス型の変数に null を代入し、null を指す変数からメンバアクセスを試みています。Visual Studio のデバッグモードで実行すると、実行結果のように例外が発生し、実行が停止されます。
変数が有効なインスタンスを参照しているかどうかは null と比較することで調べられます。変数がオブジェクト生成式によって作られた有効なインスタンスを指している場合は null と比較して一致しないことが保証されます。
if (obj == null) System.Console.WriteLine("無効なオブジェクトです");
オブジェクトが null の状態を取る可能性がある場合、事前に上記のような if 文で変数が null かどうかを調べる必要があります。