構造体・クラス、ボックス化

@ITのC#連載記事を元に勉強を進める企画。
XNA の勉強と並行している時点で矛盾がある気もするが、そこは気にしない。
やりたいようにやるのが自分にとって一番やりやすい、つまりはかどるからである。
 
さて、少しC#そのものの勉強記事から遠ざかっていたので、まずは参照している@ITの連載を紹介しておく。
忙しさを理由にしたり、XNAをいじったりしている間に、C#3.0の連載も始まっていることだし。
 

@IT - 連載 改訂版 C#入門:
 http://www.atmarkit.co.jp/fdotnet/csharp_abc2/index/index.html
@IT - 連載 C# 2.0 入門:
 http://www.atmarkit.co.jp/fdotnet/csharp20/index/index.html
@IT - 連載 C# 3.0 入門:
 http://www.atmarkit.co.jp/fdotnet/csharp30/index/index.html

 
ちょっと過去に遡って、私がC#の勉強をしたつもりの記事も挙げておく。

2007/10/19 interface:
 http://d.hatena.ne.jp/levin_gsp/20071019/1192799664
2007/10/24 interface 2:
 http://d.hatena.ne.jp/levin_gsp/20071024/1193215910
2007/10/29 ジャグ配列:
 http://d.hatena.ne.jp/levin_gsp/20071029/1193654852
2007/10/31 不慣れでもMSDN検索:
 http://d.hatena.ne.jp/levin_gsp/20071031/1193825179
2007/11/06 匿名メソッド:
 http://d.hatena.ne.jp/levin_gsp/20071106/1194354133
2007/11/09 FizzBuzz問題・解答編:
 http://d.hatena.ne.jp/levin_gsp/20071109/1194579778
2007/11/19 ジェネリック
 http://d.hatena.ne.jp/levin_gsp/20071119/1195467451
2007/11/27 ラムダ:
 http://d.hatena.ne.jp/levin_gsp/20071127/1196143533
2007/11/30 サンプル時計:
 http://d.hatena.ne.jp/levin_gsp/20071130/1196407261
2008/04/15 ラムダその2:
 http://d.hatena.ne.jp/levin_gsp/20080415/1208260855

 
ん。
結構サボった。…どんまい。
 
気を取り直して。
実は@IT記事に沿って進めることすら出来ていない。
読んではいるけど、一つ一つサンプルを自力で捻出していない、という意味で。
 
そういう決めごとを設けるのも一つだが、この辺りも冒頭に書いたとおり、やりやすいようにやるのが性分のようで。
今回は、間も空いてしまったので、基礎に立ち返る。
C#2.0/3.0 とかのたまう前に私はまだ C# そのものの基本がわかっちゃいないんだから。
 
ということで、タイトル通り。
 

@IT - 連載 改訂版 C#入門:
 第5章 C#のデータ型:
 http://www.atmarkit.co.jp/fdotnet/csharp_abc2/csabc2_005/cs2_005_01.html

 
ざっと読んでスルーしようと思ったらボックス化のところで試してみたくなった。
プリミティブな型(char, int, etc...)は構造体で出来ている。
クラスとの大きな違いは値型か参照型だ、というのだ。
全てのクラスのスーパークラスである object 型に = で代入することが、基本的には出来る。
その際に自動的に行っているのが、ボックス化。
 
値型を object 型に代入する際には、値型を包むクラスを内部的に自動生成し、一時的なインスタンスまでも作成してくれているというのだ。
 
さて、その解説を読んで気になった。
ボックス化は万能ではないという。
なぜなら、値型ではインスタンスのコピーを(今回の例では) object 型に代入するからだ。
クラスのデータを代入する場合は、参照型を参照型に代入するので、コピーではない。
その挙動の違いを理解しておかないと痛い目を見そうだ。
 
今のうちに見ておこう。痛い目を。
ということでサンプルコードを書いてみた。
 

	class Program
	{
		public struct 構造体 { public int i; }
		public class クラス { public int i; }

		static void Main(string[] args)
		{
			/*
			 * 色々なボックス化
			 */
			object[] test = new object[5];
			test[0] = (int)1;
			test[1] = (float)0.1;
			test[2] = (string)"TEST";
			test[3] = new 構造体();
			test[4] = new クラス();
			for (int i = 0; i < 5; i++)
			{
				Console.WriteLine("Class={0}, Value={1}", test[i].GetType().FullName, test[i].ToString());
			}

			Console.WriteLine(); /* 出力にブランクラインを挿入 */

			/*
			 * 構造体をobject型にボックス化
			 */
			構造体 structTest = new 構造体();
			structTest.i = 100;
			Console.WriteLine("structTest.i={0}", structTest.i);
			object objectStructTest = structTest;
			Console.WriteLine("structTest.i={0}, objectStructTest.i={1}", structTest.i, ((構造体)objectStructTest).i);
			structTest.i = 200;
			Console.WriteLine("structTest.i={0}, objectStructTest.i={1}", structTest.i, ((構造体)objectStructTest).i);

			Console.WriteLine(); /* 出力にブランクラインを挿入 */

			/*
			 * クラスをobject型にボックス化
			 */
			クラス classTest = new クラス();
			classTest.i = 100;
			Console.WriteLine("classTest.i={0}", classTest.i);
			object objectClassTest = classTest;
			Console.WriteLine("classTest.i={0}, objectClassTest.i={1}", classTest.i, ((クラス)objectClassTest).i);
			classTest.i = 200;
			Console.WriteLine("classTest.i={0}, objectClassTest.i={1}", classTest.i, ((クラス)objectClassTest).i);
		}
	}

 
どうだろう。
「構造体をobject型にボックス化」と「クラスをobject型にボックス化」の処理。
やっていることはまったく一緒。
結果が異なることを証明してみた。
 
ちょっと昔、C言語をやっていた頃のポインタ勉強時代を思い出した♪
あの頃があって、値と参照の違いがすんなり受け入れられるのだろうと思ったりする。
結果は以下。
 

Class=System.Int32, Value=1
Class=System.Single, Value=0.1
Class=System.String, Value=TEST
Class=Program+構造体, Value=Program+構造体
Class=Program+クラス, Value=Program+クラス
 
structTest.i=100
structTest.i=100, objectStructTest.i=100
structTest.i=200, objectStructTest.i=100
 
classTest.i=100
classTest.i=100, objectClassTest.i=100
classTest.i=200, objectClassTest.i=200

 
若干編集したけど。
構造体とクラスの出力にはProgramの前にnamespaceがついていたが、何となく恥ずかしくて消しておいた。
 
ふーむ、しかしこれは勉強になった。