組み込み型の型変換

数値型同士の変換

暗黙的な型変換

プログラミングでは様々なデータ型を扱うので、時には別のデータ型同士の演算を行うこともあります。


short shortNum = 10;
int intNum = shortNum;

short型とint型とではint型のほうが扱える数値の幅が広いので、上記のような代入は問題なく行えます。
このとき、変数shortNumの値は自動的にint型に変換されて代入されます。
このような、特別な構文を必要としないデータ型の変換を暗黙的な型変換といいます。

ではこれを逆にするとどうでしょうか。


int intNum = 10;
short shortNum = intNum; //エラー

このコードはエラーになります。
変数intNumの中身はshort型でも扱える「10」という値ですが、実際の値に関係なく、扱えるデータ幅の大きいほうから小さいほうへの代入はC#では許されていません。

ただし数値リテラル(コード上に直接記述される数値)やconst定数は、代入先のデータ型で扱える範囲の値であれば特別な構文を使用せずに代入が可能です。
これらはコンパイル時に値が決まっているので問題が起こらないことが保証されるからです。

明示的な型変換(キャスト)

サイズの大きいデータ型から小さいデータ型変数への代入を行うには明示的な型変換(キャスト)を行います。


int intNum = 10;

//int型変数をshort型に変換(キャスト)
short shortNum = (short)intNum;

変換したい値の前に丸括弧を書き、目的のデータ型名を記述します。
これをキャスト演算子といいます。
short型のキャスト演算子を使用すると、変数intNum一時的にshort型として扱われます。
値はshort型に変換されているので、short型変数shortNumに値を代入可能となります。

ただし変換される実際の値が変換後のデータ型で扱える範囲以上だった場合、オーバーフローが発生することに注意してください。

キャスト演算子の優先順位

キャスト演算子は算術演算子よりも優先順位が高いため、以下のコードは「short型 + int型」の演算になります。


int intNum = 1;
short shortNum = (short)intNum + 2; //エラー

int型の変数intNumは、先にキャスト演算子が適用され一時的にshort型として扱われます。
そしてその後にint型の値(2)を加算しています。
「short型 + int型」の演算結果はint型となり、それをshort型変数に代入しようとしているためエラーになります。
このような場合は式全体を丸括弧で囲い、それにキャスト演算子を適用します。


int intNum = 1;
short shortNum = (short)(intNum + 2);

異なるデータ型同士の演算の結果のデータ型は数値昇格で説明します。
数値を直接コード上に書いた場合のデータ型はリテラルの項で説明します。

整数型から実数型への変換

整数型から実数型(小数型)への代入はキャストなしでそのまま行えますが、精度の問題でデータの変化が生じる可能性があります。


int intMax = int.MaxValue;
float f = intMax;

Console.WriteLine(intMax);
Console.WriteLine("{0:f}",  f);
2147483647
2147484000.00

実数型から整数型への変換

実数型から整数型への代入はキャストが必要です。
この時、小数点以下は切り捨てられます。


float f = 1234.56f;
int i = (int)f;

Console.WriteLine(f);
Console.WriteLine(i);
1234.56
1234

整数型同士の除算で小数点以下を得る

int型などの整数型同士の演算結果はint型になります。
(サイズがint型以下のデータ型の場合)
int型同士の除算(割り算)の結果もint型となるので、計算結果から小数以下は切り捨てられてしまいます。
小数以下の値も必要な場合はdouble型などの実数型にキャストしてから計算します。


int num1 = 25;
int num2 = 2;

//これは小数点以下が切り捨てられる
double real1 = num1 / num2;

double real2 = (double)num1 / num2;

Console.WriteLine(real1);
Console.WriteLine(real2);
12
12.5

どちらか一方が実数型であれば良いので、キャストするのは片方だけで構いません。

参照型のキャスト

参照型変数の場合にはもうひとつキャスト方法があります。
詳しくは値型と参照型#参照型変数のキャストで説明します。

文字列型、数値型、bool型の相互変換

キャスト演算子はどんなデータ型でも変換できるわけではありません。
組み込み型で変換可能なのは数値型同士(char型も含む)と、object型だけです。

文字列型、数値型、bool型の変換についてはデータ型の相互変換を参照してください。

object型のキャストについて

object型は何でも代入可能なデータ型ですが、代入した値を元の型に復元するときにキャスト演算子を使用します。
値型のデータ型を代入した場合、キャスト演算子に指定するデータ型は元と正確に一致する必要があります。


int n1 = 123;

//object型への代入時はキャストは不要
object o1 = n1;

//明示的にキャストしても問題はない
object o2 = (object)n1;

//object型から復元する場合はキャスト演算子を使用する
int n2 = (int)o1;

//int型が入っているobject型変数を復元するには
//int型のキャスト演算子を使用しなければならない
//以下はコンパイル時にはエラーにはならないが
//実行時に例外が発生する
short s1 = (short)o1;

//互換性のあるデータ型に変換する場合は
//二重にキャストが必要
short s2 = (short)(int)o1;

//参照型の場合は変換が可能なデータ型であれば
//このような制限はない

数値昇格

上でも少し触れましたが、数値型の値に演算子を適用すると一時的にそのデータ型、および評価値のデータ型が変化することがあります。
サイズの小さな型から大きな型に変換されるので、これを数値昇格(numeric promotions)といいます。
これは暗黙的な型変換の一種です。

以下の説明にはまだ説明していない機能が含まれているので、分からない部分は気にしなくても大丈夫です。

単項演算子の昇格

int型よりもサイズが小さいデータ型に対して、単項の+-~演算子を適用すると、そのデータ型はint型として扱われます。
具体的にはsbyte型、byte型、short型、ushort型、char型がint型に昇格します。

二項演算子の昇格

二項演算子(+-*/など他多数)は、以下のルールが適用され、昇格が行われます。

  1. 一方がdecimal型の場合、他方はdecimal型に変換される。
    ただし、float型およびdouble型は変換できない。
  2. 一方がdouble型の場合、他方はdouble型に変換される。
  3. 一方がfloat型の場合、他方はfloat型に変換される。
  4. 一方がulong型の場合、他方はulong型に変換される。
    ただし符号あり型(sbyte型、short型、int型、long型)は変換できない。
  5. 一方がuint型で、他方がsbyte型、short型、int型の場合、両方がlong型に変換される。
  6. 一方がuint型で、他方が上記以外の場合、他方はuint型に変換される。
  7. 1~6以外の場合、両方がint型に変換される。

これらの評価値のデータ型は変換後の値のデータ型です。
ただし関係演算子(<>=など)の場合は従来通りbool型です。

decimal型と実数型(double型、float型)との演算や、ulong型と符号あり型との演算はできません。
明示的にキャストすることで演算は可能ですが、値の変化には注意してください。


decimal decimalNum = 10;
ulong ulongNum = 20;
double doubleNum = 30.0;
int intNum = 40;

//以下はエラー
//var val1 = decimalNum + doubleNum;
//var val2 = ulongNum + intNum;

//明示的にキャストすればOK
var val3 = decimalNum + (decimal)doubleNum;
var val4 = ulongNum + (ulong)intNum;

数値昇格のルールは、以下のような単純な演算でも評価値のデータ型が変化するので注意が必要です。


byte byte1 = 10;
byte byte2 = 20;

//これはint型になる
var val1 = byte1 + byte2;

//これはエラー
//byte val2 = byte1 + byte2;

//これもだめ
//byte val3 = (byte)byte1 + (byte)byte2;

//これはOK
byte val4 = (byte)(byte1 + byte2);