参照型のキャスト
クラスからnewによって作成したインスタンスも、キャストの対象になる。例えば、下記のサンプルソースでは、ソース内で宣言したクラスであるClass1のインスタンスをobject型にキャストしている。object型は、すべてのクラスに対する共通の基底となるクラスなので、すべてのクラスのインスタンスは、object型にキャストできる。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public String hello;
7: public static int Main(string[] args)
8: {
9: Class1 c = new Class1();
10: c.hello = "Hello!";
11: object o = (object)c;
12: Class1 c2 = (Class1)o;
13: Console.WriteLine("{0},{1}",c.hello,c2.hello);
14: return 0;
15: }
16: }
17: } |
|
クラスをキャストするプログラム |
このソースコードで問題になるのは、12行目だろう。一度object型にキャストしたものを、もう1度キャストして元のClass1型に戻している。もし、object型にキャストされたときに、インスタンスがobject型に変換されているなら、Class1の内部で宣言したhelloという変数はその時点で失われるはずである。そのため、再びClass1型にキャストしても、helloに代入した“Hello!”という文字列は失われているはずである。しかし実行結果は以下のようになる。
 |
プログラムの実行結果 |
いったんobject型にキャストしても、再度元のクラスにキャストすると、元のクラスのインスタンスとして使える。
|
つまり、11行目でobjectへキャストされても、変数helloの値は残っているということである。そして、12行目で再びClass1型にキャストされた時点で、変数helloへのアクセスは再び可能になる。
これが、キャストによってデータが失われたら永遠に戻らない整数型などの値型と、クラスなどの参照型の違いである。参照型は、object型などのより基本的な型にキャストしても、元の型に再キャストすれば、完全に元どおりの機能を取り戻す。
無関係の参照型へのキャスト
上記のソースコードには一カ所弱点がある。それは、12行目で、Class1とは縁もゆかりもない無関係なクラスにキャストしてしまっても、コンパイラはエラーを通知してくれないと言うことだ。実際にそれを確かめてみよう。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class2
5: {
6: public String hello;
7: }
8: public class Class1
9: {
10: public String hello;
11: public static int Main(string[] args)
12: {
13: Class1 c = new Class1();
14: c.hello = "Hello!";
15: object o = (object)c;
16: Class2 c2 = (Class2)o;
17: Console.WriteLine("{0},{1}",c.hello,c2.hello);
18: return 0;
19: }
20: }
21: } |
|
無関係のクラスにキャストするプログラム |
ここでは、まったく無関係だが、たまたま同じhelloという変数を持つClass2というクラスを宣言し、16行目でobjectクラスから元のクラスにキャストし直す際に、この無関係なClass2を指定してみた。実際にビルドしてみると分かるが、明らかに間違っているにもかかわらず、コンパイラは何もエラーを告げてこない。
しかし、コンパイル・エラーが起こらないといっても、プログラムが動作するはずもない。実際に実行すると、以下のようなエラーメッセージで強制終了させられてしまう。
型 'System.InvalidCastException' のハンドルされていない例外が D:\w\test\ConsoleApplication11\bin\Debug\ConsoleApplication11.exe で発生しました |
このように、コンパイル時に判断できないバグを作り込んでしまう可能性があるので、インスタンスのキャストは必要なとき以外は使わない方がよい。一方、どんな型のインスタンスでも扱える便利なクラスを開発する際は、このリスクを考えに入れても、インスタンスをobject型にキャストして使う価値があると言える。
スーパー・クラスへのキャスト
無関係なクラスへのキャストはエラーになるしかないが、スーパー・クラスへのキャストは可能である。例えば、Class2を継承してClass1が作られているとき、Class1のインスタンスをClass2にキャストするのは正しい使い方のうちである。
実際に上記のサンプルを小改造して、試してみよう。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class2
5: {
6: public String hello;
7: }
8: public class Class1 : Class2
9: {
10: //public String hello;
11: public static int Main(string[] args)
12: {
13: Class1 c = new Class1();
14: c.hello = "Hello!";
15: object o = (object)c;
16: Class2 c2 = (Class2)o;
17: Console.WriteLine("{0},{1}",c.hello,c2.hello);
18: return 0;
19: }
20: }
21: } |
|
スーパー・クラスにキャストするプログラム |
このサンプル・ソースのポイントは、Class1のインスタンスとして作ったはずのインスタンスをobjectにキャストしてから、改めてClass2にキャストしていることである。しかし、8行目を見ると分かるとおり、Class2はClass1のスーパー・クラスであるため、このキャストは合法である。実際に、helloという変数は、Class2の中だけに存在するものであり、14行目で代入したものと、17行目で参照している変数helloはすべて同一である。もし、10行目のコメント//を外すと、同じ名前の変数を重複定義しているとコンパイラから怒られる。
これを実行した結果は、以下のようになる。
 |
プログラムの実行結果 |
スーパー・クラスへのキャストは「合法」なので、コンパイルはもちろん、実行時もエラーは起こらない。今回の例では、Class2にしかない変数helloが2つのインスタンス変数から参照され、これらが連続して表示されている。
|
アンボクシングとキャスト
ボクシングしたデータを元のデータ型で取り出すことを「アンボクシング」という。ボクシングもアンボクシングも、中身は変わらなくても見かけ上のデータ型が変わるので、キャストとの関係が発生する。ボクシング/アンボクシングというのは、データ変換の一種だからだ。以下に簡単なボクシング/アンボクシングのサンプル・ソースを示す。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: int i = 123;
9: object o = i;
10: int j = (int)o;
11: Console.WriteLine("{0},{1},{2}",i,o,j);
12: return 0;
13: }
14: }
15: } |
|
ボクシング/アンボクシングを使用するプログラム |
これを実行した結果は、以下のようになる。
 |
プログラムの実行結果 |
ボクシングによりobject型に変換した値を、アンボクシングにより元の型に戻す場合にはキャストが必要となる。
|
9行目で、あっさりと整数をobject型に代入しているが、ここでボクシングが発生する。ボクシングで情報が欠落することはまったくないので、明示的にキャストしなくても問題はない。これに対して、10行目でボクシングした値を整数型変数に代入する時点で、アンボクシングが行われる。この場合には、“(int)”とキャストされていることから分かるとおり、明示的なキャストが必要とされている。ボクシングはキャストはなくてもよいが、アンボクシングはキャストを必須とする、と覚えておくとよいだろう。
まとめ
今回のデータ型の変換は、C/C++やJava経験者には少々退屈だったかもしれない。しかし、C#ならではの特徴もあるので、完全にC/C++/Javaと同一というわけではない。確認のために、1個1個確かめながら読むとよいだろう。
さて、次回はプログラム言語の豊かな表現力を支えるポイントである「式」について解説したいと思う。
それでは次回もLet's See Sharp!
Insider.NET 記事ランキング
本日
月間