連載

C#入門

第7 キャストとデータ変換

(株)ピーデー
川俣 晶
2001/06/26


参照型のキャスト

 クラスから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!End of Article


 INDEX
  C#入門 第7回 キャストとデータ変換
    1.目に見えない変換
    2.実数でもキャスト
  3.参照型のキャスト

「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間