// 元の値(123)がそのまま各変数に引き継がれる。
{
sbyte b = 123;
short s = b;
int i = s;
long l = i;
Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
return 0;
}
{
long l = 123;
int i = l;
short s = i;
sbyte b = s;
Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
return 0;
}
キャストを使用して、自身よりも表現力の小さい変数に値を代入した。コンパイル・エラーも発生しないし、
POINT: Cast( 型変換 )は Data を壊す場合がある.
キャストがデータを壊すとき
上記の例は、扱う数値がどのデータ型にも収まるほど小さかったので、問題なく処理できた。
いつも数値が十分に小さいわけではない。
桁数が多いデータを、それが収まりきらないほど桁数が小さいデータ型の変数に入れてしまう場合もあるだろう。
キャストを使えばそれは可能である。
桁数が足りない以上、元の値を保つことが不可能である。
{
using System;
public class Class1
{
public static int Main(string[] args)
{
long l = 1844674407370955161;
int i = (int)l;
short s = (short)i;
sbyte b = (sbyte)s;
Console.WriteLine("{0},{1},{2},{3}",b,s,i,l);
return 0;
}
}
}
正数だったはずが負数扱いされてしまう場合もある。
格納不能な数値をキャストによって強制的に変換して代入したため
数値が破壊されてしまった。
POINT
bitiamge では 切り詰める作業をしているだけ. -> しかし整数としてみると,まったく異なる値になる.
10進数ではバラバラの値に見えるが
内部的にはビットイメージの桁数を切りつめる処理が行われているだけ
しかし、10進数として見るとまったく予測できない値に変化する。
あくまで10進数の値が重要である場合には、値が大きく狂うような状況が発生しないようにプログラミングしなければならない。
キャストをなるべく使わないように書けば
このような問題を自動的に回避できる。
キャストしなければならない必然性が特にない場所でキャストを多用するようなソースコードは
潜在的にバグが入り込む可能性が大きい。
POINT
だから、事前に何桁の数値が必要になるかを見積もった上で
使うデータ型を統一し、キャストをできるだけ少なくするようにプログラミングすること。
■ 実数でもキャスト
データ変換は、整数だけでなく、実数でも必要とされる。
C#には、桁数の多いdouble型と桁数の少ない
float型という2種類の浮動小数点実数のデータ型がある。
桁数の多いdouble型の値を桁数の少ないfloat型に代入する場合は
整数と同じくキャストが必要。
{
double d = 0.123456789012345;
float f = (float)d;
Console.WriteLine("{0},{1}",d,f);
return 0;
}
POINT
double型をfloat型にキャストすると、数値の下の桁が切り捨てられていることが分かる。
前出の整数の場合のように、とんでもない値になってしまうわけではない。
桁数の少ないデータ型に変換されたときに切り捨てられるのは下の桁である。
値のおおまかな値は変化しない。精度が落ちるだけ。
その意味で、整数をキャストする場合に比べれば比較的被害は少ない。
ちなみに、上記画像の最後が“91”で終わっているが、変換元の値を見れば、ここは“89”になるべき場所である。このような食い違いが生じるのは、
2進数の浮動小数点演算が、10進数の表記を正確に表記できないことによって起こる誤差であり、原理的に不可避である。
これを避けるには、2進数ではなく10進数で処理するdecimal型を用いるが、処理速度や効率はfloat型やdouble型の方が勝る。
定数値にキャスト
誤差の出ない計算を行うなら、decimal 型が有利。
{
using System;
public class Class1
{
public static int Main(string[] args)
{
decimal e = 0.123456789;
Console.WriteLine("{0}",e);
return 0;
}
}
}
このソースコードは、コンパイルを通らない。
d:\w\test\consoleapplication11\class1.cs(8、16): エラー CS0029: 型 'double' を型 'decimal' に暗黙的に変換できません。
これは、何気なく書いた“0.123456789”という値が、実はコンパイラ内部でdouble型の値として認識されていることにより発生したエラー。
つまりコンパイラは、double型の値をdecimal型に代入すると認識して、エラー扱いしたのである。
キャストを用いて“(decimal)0.123456789”と書いてエラーを避けることはできるが、
せっかく誤差を避けるためにdouble型を使わないようにしているのに
ここでdouble型が入り込むのは美しくない。
{
using System;
public class Class1
{
public static int Main(string[] args)
{
decimal e = 0.123456789m;
Console.WriteLine("{0}",e);
return 0;
}
}
}
POINT
decimal 型定数は m をつけて CMP に通知する.
“0.123456789”の後ろに“m”を付けて、“0.123456789m”と記述したことである。
この“m”は、「その値がdecimal型だよ」と言うことをコンパイラに伝える機能を持った文字である。
“u”は符号なし
“l”はlong型
“f”はfloat型
“d”はdouble型
といった機能をもった文字を数値の後ろに付けることができる。
無意味なキャストを減らすためにも、これらの文字は有効
定数に型指定文字を追加したら
コンパイルも問題なく通過し、実行結果も期待どおりのものになった。
■ 符号の有無は要注意
キャストを用いる際には
符号ありのデータ型と、符号なしのデータ型の違いに注意を払う必要がある。
例えば
同等のビット数で表現されるデータ型の間で変換を行う場合
確かに情報が欠落せずに変換はできる
しかし
それが指し示す値が同じとは限らない。
{
using System;
public class Class1
{
public static int Main(string[] args)
{
short i = -1;
ushort u = (ushort)i;
// A: -1, 65536
Console.WriteLine("{0},{1}",i,u);
return 0;
}
}
}
負の数を符号なしのデータ型に代入するプログラム
POINT
符号つき変数は( CMP くんの )解釈が異なる.
ビットとしての情報は失われていないものの、符号付き変数では値の解釈が異なるため、
POINT:
bit Level の情報は失われていない.
変数iと変数uは、機械語レベルで格納されるビットの値としてはまったく同一であり、
ビット単位で処理する演算子を用いるとまったく同じ結果になる。
しかし、[ 数値としての値 ]は同じではないことを、肝に銘じておく必要がある。
言い換えれば、符号付きと符号なしのデータ型は違うものだと認識して、
混用はなるべく避ける方がよい。
POINT
整数型は少数を表現できない
切り捨ては同一符号 && 絶対値が超えない値になる
小数の切り捨て
整数型には小数を表現できない。
キャストによって実数型から整数型に変換させることはできるが、
小数点以下の値はすべて失われる。
{
for( float f=-2; f< 2; f += 0.25f )
{
int i = (int)f;
Console.WriteLine("{0},{1}",f,i);
}
}
POINT
浮動小数点数値を整数型変数にキャストした場合には、
同一符号で絶対値が本来の値(浮動小数点値)を超えない最大の整数値に変換される。
WARNING
四捨五入に慣れたVisual BASICプログラマなどは要注意
■ 参照型のキャスト
クラスからnewによって作成したインスタンスも、キャストの対象になる。
ソース内で宣言したクラスであるClass1のインスタンスをobject型にキャストしている。
object型は、すべてのクラスに対する共通の基底となるクラスなので
すべてのクラスのインスタンスは、object型にキャストできる。
{
using System;
public class Class1
{
public String hello;
public static int Main(string[] args)
{
Class1 c = new Class1();
c.hello = "Hello!";
object o = (object)c;
Class1 c2 = (Class1)o;
Console.WriteLine("{0},{1}",c.hello,c2.hello);
return 0;
}
}
}
一度object型にキャストしたものを、
もう1度キャストして元のClass1型に戻している。
もし、object型にキャストされたときに、
インスタンスがobject型に変換されているなら
Class1の内部で宣言したhelloという変数はその時点で失われるはず。
そのため、再びClass1型にキャストしても
helloに代入した“Hello!”という文字列は失われているはず。
POINT
参照型の Cast は元に戻せば, 元のクラスの Instance として利用可能.
キャストによってデータが失われたら永遠に戻らない整数型などの値型と、
クラスなどの参照型の違いである
このあたりが C と通ずる。
参照型は、object型などのより基本的な型にキャストしても、
元の型に再キャストすれば、完全に元どおりの機能を取り戻す。
■ 無関係の参照型へのキャスト
Class1とは縁もゆかりもない無関係なクラスにキャストしてしまっても、
コンパイラはエラーを通知してくれないと言うこと
{
using System;
public class Class2
{
public String hello;
}
public class Class1
{
public String hello;
public static int Main(string[] args)
{
Class1 c = new Class1();
c.hello = "Hello!";
object o = (object)c;
Class2 c2 = (Class2)o;
Console.WriteLine("{0},{1}",c.hello,c2.hello);
return 0;
}
}
}
POINT: Instance の Cast は必要なときのみすること.
ここでは、まったく無関係だが、たまたま同じ hello という変数を持つClass2というクラスを宣言し、
objectクラスから元のクラスにキャストし直す際に、この無関係なClass2を指定してみた。
実際にビルドしてみると分かるが、明らかに間違っているにもかかわらず
コンパイラは何もエラーを告げてこない。
しかし、コンパイル・エラーが起こらないといっても、プログラムが動作するはずもない。
実際に実行すると、以下のようなエラーメッセージで強制終了させられてしまう
。型 'System.InvalidCastException' のハンドルされていない例外が
D:\w\test\ConsoleApplication11\bin\Debug\ConsoleApplication11.exe で発生しました
このように、コンパイル時に判断できないバグを作り込んでしまう可能性があるので、
インスタンスのキャストは必要なとき以外は使わない方がよい。
一方、どんな型のインスタンスでも扱える便利なクラスを開発する際は、
このリスクを考えに入れても、インスタンスをobject型にキャストして使う価値があると言える。
スーパー・クラスへのキャスト
無関係なクラスへのキャストはエラーになるしかないが、
スーパー・クラスへのキャストは可能。
Class2を継承してClass1が作られているとき、
Class1のインスタンスをClass2にキャストするのは正しい
{
using System;
public class Class2
{
public String hello;
}
// 継承した.
public class Class1 : Class2
{
//public String hello;
public static int Main(string[] args)
{
Class1 c = new Class1();
c.hello = "Hello!";
object o = (object)c;
Class2 c2 = (Class2)o;
Console.WriteLine("{0},{1}",c.hello,c2.hello);
return 0;
}
}
}
POINT
SuperClass への Cast は合法
このサンプル・ソースのポイントは、Class1のインスタンスとして作ったはずのインスタンスを
objectにキャストしてから
改めてClass2にキャストしていることである
Class2はClass1のスーパー・クラスであるため、このキャストは合法である。
helloという変数は、Class2の中だけに存在するものであり
代入したものと、参照している変数helloはすべて同一である。
コメント//を外すと、同じ名前の変数を重複定義しているとコンパイラから怒られる。
スーパー・クラスへのキャストは「合法」なので
コンパイルはもちろん、実行時もエラーは起こらない。
今回の例では、Class2にしかない変数helloが2つのインスタンス変数から参照され、
これらが連続して表示されている。
アンボクシングとキャスト
ボクシングしたデータを元のデータ型で取り出すことを「アンボクシング」という。
ボクシングもアンボクシングも、中身は変わらなくても見かけ上のデータ型が変わるので、
キャストとの関係が発生する。
ボクシング/アンボクシングというのは、データ変換の一種。
{
public class Class1
{
public static int Main(string[] args)
{
int i = 123;
object o = i;
int j = (int)o;
Console.WriteLine("{0},{1},{2}",i,o,j);
return 0;
}
}
}
POINT
unboxing には Cast が必要, Boxing は Cast 不要.
ボクシングによりobject型に変換した値を、
アンボクシングにより元の型に戻す場合にはキャストが必要となる。
あっさりと整数をobject型に代入しているが、ここでボクシングが発生する。
ボクシングで情報が欠落することはまったくないので、明示的にキャストしなくても問題はない。
これに対して
ボクシングした値を整数型変数に代入する時点で、アンボクシングが行われる。
“(int)”とキャストされていることから分かるとおり、明示的なキャストが必要。
ボクシングはキャストはなくてもよいが、アンボクシングはキャストを必須とする
と覚えておくこと。
まとめ
しかし、C#ならではの特徴もあるので、完全にC/C++/Javaと同一というわけではない。