データ型

C#で扱う「値」の種類

変数のページでも軽く説明しましたが、C#で扱う「値」にはいくつかの種類があります。
整数を扱うなら整数型、文字列を扱うなら文字列型、といった具合です。
これを詳しく見ていきます。

基本的なデータ型

bool
論理型(ブーリアン型)。
「true」か「false」かのどちらかの値のみ扱う。
要は「ある」か「ない」かを表す。
byte
符号なし1バイト整数型
0~255の値を扱う。
sbyte
符号あり1バイト整数型
-128~127の値を扱う。
short
符号あり2バイト整数型
-32,768~32,767の値を扱う。
ushort
符号なし2バイト整数型
0~65,535の値を扱う。
int
符号あり4バイト整数型
-2,147,483,648~2,147,483,647の値を扱う。
(21億)
uint
符号なし4バイト整数型
0~4,294,967,295の値を扱う。
(42億)
long
符号あり8バイト整数型
-9,223,372,036,854,775,808~9,223,372,036,854,775,807の値を扱う。
(922京)
ulong
符号なし8バイト整数型
0~18,446,744,073,709,551,615の値を扱う。
(1844京)
float
32ビット浮動小数点数。
-3.402823×10の38乗~3.402823×10の38乗の値を扱う。
double
64ビット浮動小数点数。
-1.79769313486232×10の308乗~1.79769313486232×10の308乗の値を扱う。
decimal
128ビット10進浮動小数点数。
-79228162514264337593543950335~79228162514264337593543950335の値を扱う。
整数型のように見えるが小数も扱える。
(doubleよりも高精度)
char
Unicode 16ビット文字型。
内部的には0~65535の数値。
(2バイト)
string
文字列型。

int以降は値が大きすぎてわけがわかりませんが、あまり気にする必要はありません。
普通に整数値を扱いたい場合はint型を使用しておけばまず間違いはありません。
他の整数型はint型では扱いきれないような大きな数値が必要な場合や、特定の関数で使用する場合など、用途が限られます。

bool型は「true」か「false」かのどちらかの状態を取るデータ型です。
例えばチェックボックスがオンかオフか、関数の処理に成功したか失敗したか、などでよく使われる型です。

float型、double型は小数(実数)を扱うことができるデータ型です。
int型などの整数型の変数に小数を含む値を代入すると値が変化してしまいます。
(小数点以下が切り捨てられる)

decimal型は10進数の数値を表現するデータ型です。
double型よりも大きな128ビットで表すので、double型よりもより大きく精密な値を扱うことができます。
その分多くのメモリを消費し、計算速度が劣ります。

小数値の誤差

C#をはじめとした多くのプログラミング言語では、小数同士の計算時に「誤差」が発生することがあります。
計算誤差は精密な演算を行う場合に発生するのではなく、割と頻繁に発生します。

これは値を保存しておくメモリ領域が有限であることが原因です。
普通に紙と鉛筆で計算した場合と、プログラミングで計算した場合とでは計算結果が一致しないこともあるのです。
double型はfloat型よりも精度の高い値を扱えますが、それでも微妙に誤差がある可能性を考慮しなければなりません。

ビットとバイト

コンピュータで扱えるデータの最小単位をビットといいます。

1ビットは「0」と「1」の2通りの情報を扱えます。
2ビットあれば「00」「01」「10」「11」の4通りの情報を扱えます。
(2の2乗)
3ビットで8通り(2の3乗)、4ビットで16通り(2の4乗)です。

8ビットで256通りの情報を扱えます。
これで1バイトとなります。
1バイトでアルファベットの大文字小文字すべてと、主要な記号や命令を一通り扱えるだけの情報量となります。

C言語では「文字型(char)」は1バイトですが、C#では日本語などを扱えるように2バイトとなっています。
ただし2バイト(65536通り)でも漢字をすべて表すことはできず、char型ひとつでは扱えない文字も存在します。

「符号なし」と「符号あり」

整数のデータ型には符号なし符号ありという分類があります。
「符号」というのは「マイナス記号」のことです。
符号なしのデータ型はマイナスを付けられないので、ゼロ未満の数値を扱うことはできません。
その代わりにプラス側で扱える範囲が符号ありのデータ型の倍になっています。

「byte型」は符号なしであることに注意してください。
符号ありなしを表す接頭語がbyte型だけが「s」で、この接頭語を付けると「符号あり」になります。
int型などとは感覚的に逆になっています。

「uint」「ulong」などの接頭語「u」は「unsigned」の略です。
「sbyte」の接頭語「s」は「signed」の略です。

「sign(サイン)」というのは「符号」の意味です。
「+2」「-5」などの数値の手前に付けられる記号を符号といいます。
つまり「signed」は「符号あり」、「unsigned」は符号なしを意味します。

MaxValue、MinValue

数値型のデータ型は、それぞれが扱える最大値と最低値があらかじめ定義されています。


int intMax = int.MaxValue;
int intMin = int.MinValue;

float floatMax = float.MaxValue;
float floatMin = float.MinValue;

Console.WriteLine(intMax);
Console.WriteLine(intMin);
Console.WriteLine(floatMax);
Console.WriteLine(floatMin);
214748647
-214748648
3.402823E+38
-3.402823E+38

データ型名に続いて「.」(ドット)を記述し、MaxValueで最大値、MinValueで最低値が得られます。

オーバーフロー(桁あふれ)

数値型のデータ型には扱える値の上限と下限があります。
では、その範囲を超えた値を扱おうとするとどうなるでしょうか。


sbyte sb = sbyte.MaxValue; //127
sb++;
Console.WriteLine(sb);
-128

正の値に1を加算したのに、結果はなんとマイナス値になってしまいます。
「-128」はsbyte型が表せる最低値で、sbyte型の最大値を超えると値が一巡してしまうのです。
これをオーバーフロー(桁あふれ)といいます。

オーバーフローを見落とすと値が意図しないものとなり、バグの原因となってしまいます。
意図的なものを除いてオーバーフローは避けるべきです。

checkedキーワード

オーバーフローが起こりそうな計算をする場合、もしオーバーフローが発生したらエラー(正しくは例外)を発生させることができます。


sbyte sb = sbyte.MaxValue;
checked
{
    sb++; //例外発生
}

Console.WriteLine(sb);

checkedブロックの中の演算でオーバーフローが発生すると、そこで例外が発生します。
例外が発生するとプログラムは停止しますが、適切に処理することでプログラムの実行を停止させず、かつデータの予期せぬ変化に対応することができます。


sbyte sb = sbyte.MaxValue;
try
{
    checked
    {
        sb++; //例外発生
    }
}
catch (Exception e) { }

Console.WriteLine(sb);

その他、すべての演算でオーバーフローチェックをするようにコンパイラの設定を変更することも可能です。
その場合に特定の演算だけオーバーフローのチェックを省くためのuncheckedキーワードもあります。

例外については例外を参照してください。

object型

上記のリストには掲載しませんでしたが、C#にはもう一つobject型というデータ型があります。
object型は、一言で言えば「何でも入れられるデータ型」です。


static void Main(string[] args)
{
    object obj;

    obj = 123;
    obj = "abc";
    obj = new int[] { 1, 2, 3 };
}

上記コードは同じ変数に数値、文字列、配列、と異なるデータ型のデータを次々に代入していますが、エラーにはなりません。
このような通常ではありえない変数を実現するのがobject型です。

何でも代入可能というと便利に聞こえますが、逆に言えば「何が入っているかわからない」ということです。
中身は数値かもしれないし、文字列かもしれないし、それ以外かもしれません。

データ型というのは、扱える値を制限する代わりに「できる事」を明確にするメリットがあります。
例えば整数型ならば四則計算が可能なことが保障されていますし、文字列型ならば文字列操作が可能なことが保障されています。

値をobject型で扱うということは、このようなメリットを捨ててしまうということです。
例えばobject型のままでは中身に数値が入っていたとしても足し算すらできません。
中身が「足し算ができるデータ型」なのかが不明なためです。


object obj1 = 123;
object obj2 = 456;

//エラー
int num = obj1 + obj2;

object型は、扱うデータ型をあらかじめ特定できない場合に使用します。
中身のデータ型が明確な場合はキャストという方法で目的のデータ型に変換することができます。


object obj1 = 123;
object obj2 = 456;

int num = (int)obj1 + (int)obj2;

配列型

データ型はすべて配列にして扱うことができます。
この時の配列変数のデータ型は配列型というデータ型になります。

配列型は参照型という扱いになります。
詳しくは値型と参照型で説明します。

その他のデータ型

上記のデータ型はすべてC#にあらかじめ用意されているデータ型で、これらは組み込み型と言います。
C#は自分で新しいデータ型を作ることもでき、これをユーザー定義型と言います。

ユーザー定義型には列挙型構造体クラスなどが該当します。

既定値

データ型には既定値というものが存在します。

変数は基本的に任意の値で初期化してからでないと使用できませんが、例えば配列の各要素は任意の値で初期化せずとも値を取り出すことができます。


int num;

//初期化しないと使用できない
//Console.WriteLine(num);

int[] nums = new int[3];

//OK
Console.WriteLine(nums[0]);
Console.WriteLine(nums[1]);
Console.WriteLine(nums[2]);
0
0
0

これは配列の要素は自動的に既定値がセットされるようになっているためです。
(初期化リストを使用すればその値がセットされます)

既定値は

  • 数値型は「0」
  • bool型は「false」
  • char型は「\0」(Null文字)
  • 参照型は「null」

となっています。

参照型については値型と参照型を参照してください。

型推論

C#では型推論という記法が導入されています。
これは、変数の宣言と同時に初期化を行う時、初期化に用いる値のデータ型が明らかならばデータ型の指定をvarというキーワードで代用できる、というものです。


static void Main(string[] args)
{
    int num1 = 10;
    double num2 = 1.23;

    var num3 = num1;  //int型
    var num4 = num2;  //double型
    var str = Func(); //string型

    Console.ReadLine();
}

static string Func()
{
    return "abc";
}

6~8行目はすべて「var」というキーワードで変数の宣言および初期化を行っています。
右辺値(初期化する値)のデータ型に応じて、それぞれ「int型」「double型」「string型」の変数が作成されます。
あくまでも「型を類推して決定する」機能で、「var」という何でも入れられるデータ型が作れるわけではありません。

以下のように変数の宣言のみでは型が決定できないのでエラーになります。


var num;

int型よりも小さい値の場合、型推論はint型となります。
これは多くのCPUでint型が最も効率良く計算できるように設計されているためです。


short num1 = 10;
short num2 = 20;

var num3 = num1 + num2; //int型

単に整数値を記述した場合は「int型」、小数値の場合は「double型」になります。


var num1 = 10; //int型
var num2 = 1.23; //double型

詳しくはリテラルの項で説明します。

使用できるのはローカル変数のみ

型推論を使用できるのはローカル変数のみで、フィールド(メンバー変数)には使用できません。


class Program
{
    //これはエラー
    //var m_num = 0;

    static void Main(string[] args)
    {
        //これはOK
        var num = 0;
    }
}

ローカル変数とフィールド(メンバー変数)に関してはまだ説明していません。
詳しくはローカル変数とフィールド(メンバー変数)を参照してください。

匿名型

変数のデータ型はint型やstring型、その他自分であらかじめ定義したデータ型を使用しますが、匿名型という特殊なデータ型を使用することもできます。


var person = new { ID = 1, Name = "A山B太" };

Console.WriteLine(person.ID);
Console.WriteLine(person.Name);
1
A山B太

匿名型はvarというキーワードで宣言します。

このように記述すると「person」は内部に「IDというint型の値」と「Nameというstring型の値」を持っている型となります。
「ID」「Name」などはプロパティといいます。
この型はその場限りのデータ型で、具体的な名前を持たないので匿名型というわけです。

匿名型はそれ単体ではあまり使うことはありませんが、LINQなどの機能では時々使用されます。

匿名型を配列にする場合は以下のようにします。


var persons = new[]
{
    new { ID = 1, Name = "A山B太"},
    new { ID = 2, Name = "C谷D男"},
    new { ID = 3, Name = "E下F子"}
};

Console.WriteLine(persons[0].ID);
Console.WriteLine(persons[2].Name);

値は変更できない

匿名型の内部の値は読み取り専用で、値の変更はできません。


var person = new { ID = 1, Name = "A山B太" };

//エラー
person.ID = 2;

プロパティ名の指定の省略

匿名型のプロパティに既存の変数を指定する場合、プロパティ名の指定は省略できます。


int ID = 1;
string Name = "A山B太";

var person = new { ID, Name };

Console.WriteLine(person.ID);
Console.WriteLine(person.Name);
1
A山B太

プロパティ名には変数名がそのまま使用されます。