C# 値型と参照型の違い Box化とは
概要
今日は値型と参照型、値のBox化について解説します。
まず、値型と参照型の違いについて解説し、そのあとにBox化についてを解説します。
1.値型と参照型の違い
C# には値型と参照型があり値型は int , bool , float 等の組み込み型の他に struct がある。 値型の特徴として null が許容されず、nullを許容する( = 参照型として扱う )には ?演算子を利用します。
int a = null; // NG int? b = null; // OK
また、値型は参照型では無いので代入演算子を返した段階で変数のインスタンスが変わるという特徴があります。 C# で開発をしている参照型と勘違いするケースもあるので注意してください。
値型の挙動について例を示します。
// !値型の Point構造体 struct Point { double X ,Y ; public RefPoint(double x, double y) { X = x; Y = y; } }
Point a = new Point( 1, 2 ); Point b = a ; b.X = 3; Console.WriteLine( a.X ); // 1 Console.WriteLine( b.X ); // 3
実際に値を代入して結果をみてみると代入された時点で値がインスタンスが変わっているので bの値を変更してもaには影響しないことがわかります。
一方参照型は その名の通り参照としてデータを扱います。 組み込み型のstring や class が該当します。 例えば System.Windows.Point型を参照型で定義しなおします。
// !値型の Pointクラス class RefPoint { double X ,Y ; public RefPoint(double x, double y) { X = x; Y = y; } }
ご覧の通り実は変わるのは struct か、 class かだけです。
RefPoint a = new Point( 1, 2 ); RefPoint b = a ; b.X = 3; Console.WriteLine( a.X ); // 3 Console.WriteLine( b.X ); // 3
実際に値を代入して結果をみてみると代入されても参照型はインスタンスが変わらないので値 b の値を変更した段階で a の値が変わってしまっています。
これが参照型と値型の違いです。
2 .object型
少し話は変わるのですが C#には object型というものがあります。 object型は全ての型の既定クラスなのでどんな型でも暗黙的にキャストできます。
object a = (int)0; object b = "hello"; object c = new Point( 0 , 0 ); object d = new RefPoint( 0 , 0 ); a = null; // a に nullを代入
あれ...?
値型であるintも参照型であるstringも代入できています。これはいったいどういうことなのでしょうか?
更には値型を代入した ' object a ' に null
が代入されています。[1]で解説した値型にはnullが代入できないといったのに矛盾してしまっています。
3. Box化
この問題を解決するのが Box化(Boxing)とBox化の解除(UnBoxing)です。
結論から言うとBox化とは値型を参照型に変換する機構です。
つまり object型は参照型で、object a = (int)0;
のタイミングで参照型に変換されていたのです。
なので nullが代入できたという訳です。
object a = 0.0d; // 暗黙的なキャストでボックス化 double b = (double)a; // 明示的なキャストでボックス化を解除
このBox化便利なのですが実は、新しいメモリを確保してしまうので一見単なるキャストに見えるのにメモリ確保を伴うコストの大きい処理です。
Box化を考慮した高速化法
固定値であればstatic領域にBox化済みオブジェクトを定義して置くことで無駄なアロケーション(メモリ確保)が無くなりパフォーマンスが有利になります。 (実はこれWPFの内部などでも利用されている手法です。)
public static class Boxes { public static object TrueBox = (object)true; public static object FalseBox = (object)true; }