目に見えない変換
C#には、表現可能な精度、つまり桁数が異なるデータ型がいろいろある。例えば、sbyte型は127までしか表現できないが、short型は32767まで表現できる、といった違いがある。そのことから考えると、表現力が小さい変数の内容を、表現力の大きい変数に移し替えるのは、何ら問題がないように思える。事実、プログラムを書いてみると、すんなりと、違うデータ型の変数に代入できる。
実際に試した例を以下に示す。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: sbyte b = 123;
9: short s = b;
10: int i = s;
11: long l = i;
12: Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
13: return 0;
14: }
15: }
16: } |
|
自身より表現力の大きい型の変数に値を代入するプログラム |
これは、8行目で123と言う小さな整数をsbyte型の変数に代入し、それから順次、より多くの桁数を表現できる変数に代入を続け、結果を12行目で一気に表示するというものだ。実際に実行した結果は以下のようになる。
|
プログラムの実行結果 |
当然ながら、自身よりも桁数の多い型への代入では、元の値(123)がそのまま各変数に引き継がれる。
|
このような、誰が考えても納得できるような単純な変換は、いちいち何かの命令を書き込んで示す必要はない。C#が暗黙のうちに変換してくれる。
暗黙の変換ができないとき
さて、上記サンプルは桁数が小さい変数から、桁数が大きい変数に代入したから何の文句も出ないのだが、すべての場合がそうとは限らない。例えば、桁数が大きい変数から桁数が小さい変数に代入するようなプログラムを書いたら何が起こるだろうか。実際に書いてみたのが以下のソースコードだ。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: long l = 123;
9: int i = l;
10: short s = i;
11: sbyte b = s;
12: Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
13: return 0;
14: }
15: }
16: } |
|
自身より表現力が小さい変数に値を代入するプログラム |
このソースコードは、コンパイル時にエラーになる。
d:\w\test\consoleapplication11\class1.cs(9,12): エラー CS0029: 型 'long' を型 'int' に暗黙的に変換できません。
d:\w\test\consoleapplication11\class1.cs(10,14): エラー CS0029: 型 'int' を型 'short' に暗黙的に変換できません。
d:\w\test\consoleapplication11\class1.cs(11,14): エラー CS0029: 型 'short' を型 'sbyte' に暗黙的に変換できません。
|
このソースコードでは、たまたま、123という非常に小さな値を扱っているので、問題なく動くように見える。しかし、一般論で言えば、桁数が大きい変数から桁数が小さい変数に代入するようなプログラムでは、大きな値が入っていたら、桁数が溢れて代入できないかもしれない。これに対処する方法は2種類ある。1つは、実行時に桁が溢れたらエラーにする方法で、BASICインタプリタなどによく見られる方法である。もう1つは、コンパイル時にそのような代入を警告したり、あるいはエラーにしたりする方法である。実行時にいちいちオーバーフローをチェックすると処理速度がスローダウンしてしまうので、効率重視なら、後者の方がベターである。C#は、そのような考え方により、コンパイルする段階で、たと例え扱う数字が小さくても(変数に保持された値の大小とは無関係に)、また、バグを未然に防ぐために、桁数のより小さなデータ型への変換はエラー扱いにする。
もっとも、何が起きるのかプログラマが承知しているなら、桁数のより小さなデータ型への変換も記述することができる。そのためには、すでに紹介したキャストを使用する。キャストはデータ型の名前を括弧「( )」でくくったもので、変数や定数の先頭に付けることで、キャストに記述したデータ型にデータを変換することを明示的に示す。キャストを使って、代入されるデータと、代入する変数のデータ型を一致させるように書き直したものが、以下のソースコードである。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: long l = 123;
9: int i = (int)l;
10: short s = (short)i;
11: sbyte b = (sbyte)s;
12: Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
13: return 0;
14: }
15: }
16: } |
|
キャストを使って正しくコンパイルできるようにしたプログラム |
これを実行すると以下のようになり、問題なく数値の受け渡しができていることが分かる。つまり、キャストによってデータ変換が適切に行われているのである。
|
プログラムの実行結果 |
キャストを使用して、自身よりも表現力の小さい変数に値を代入した。コンパイル・エラーも発生しないし、この例では最も表現力の小さいsbyte型でも格納可能な123を代入しているため、処理自体も正常に行われている。
|
キャストがデータを壊すとき
上記の例は、扱う数値がどのデータ型にも収まるほど小さかったので、問題なく処理できた。だが、いつも数値が十分に小さいわけではない。桁数が多いデータを、それが収まりきらないほど桁数が小さいデータ型の変数に入れてしまう場合もあるだろう。実際に、キャストを使えばそれは可能である。だが、桁数が足りない以上、元の値を保つことが不可能である。
この状況を体験するために、上記のサンプルソースに、非常に大きい値を入れて試してみよう。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: long l = 1844674407370955161;
9: int i = (int)l;
10: short s = (short)i;
11: sbyte b = (sbyte)s;
12: Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
13: return 0;
14: }
15: }
16: } |
|
変数に格納できない大きな値をキャストで強制的に代入するプログラム |
結果は以下のようになる。一目瞭然で、同じ値を示していない。しかも、正数だったはずが負数扱いされてしまう場合もある。
|
プログラムの実行結果 |
格納不能な数値をキャストによって強制的に変換して代入したため、数値が破壊されてしまった。
|
10進数ではバラバラの値に見えるが、内部的にはビットイメージの桁数を切りつめる処理が行われているだけだ。しかし、10進数で見るとまったく予測できない値に変化してしまう。あくまで10進数の値が重要である場合には、値が大きく狂うような状況が発生しないようにプログラミングしなければならない。キャストをなるべく使わないように書けば、このような問題を自動的に回避できる。キャストしなければならない必然性が特にない場所でキャストを多用するようなソースコードは、潜在的にバグが入り込む可能性が大きい。だから、事前に何桁の数値が必要になるかを見積もった上で、使うデータ型を統一し、キャストをできるだけ少なくするようにプログラミングしよう。
Insider.NET 記事ランキング
本日
月間