列挙型

列挙型とは

列挙型とは、複数の数値に名前を付けてまとめることができる機能です。
定数を一度に定義する、と考えることもできます。

例えば引数で受け取った整数値で処理を分岐するメソッドを作ってみます。


static void Main(string[] args)
{
    ShowDayOfWeek(0);
    ShowDayOfWeek(3);

    Console.ReadLine();
}

static void ShowDayOfWeek(int dayOfWeek)
{
    switch(dayOfWeek)
    {
        case 0:
            Console.WriteLine("今日は日曜日です");
            break;
        case 1:
            Console.WriteLine("今日は月曜日です");
            break;
        case 2:
            Console.WriteLine("今日は火曜日です");
            break;
        case 3:
            Console.WriteLine("今日は水曜日です");
            break;
        case 4:
            Console.WriteLine("今日は木曜日です");
            break;
        case 5:
            Console.WriteLine("今日は金曜日です");
            break;
        case 6:
            Console.WriteLine("今日は土曜日です");
            break;
        default:
            Console.WriteLine("曜日が不明です");
            break;
    }
}
今日は日曜日です
今日は水曜日です

ShowDayOfWeekメソッドは、引数の数値に応じて曜日を表示するメソッドです。
0なら日曜日、1なら月曜日…と処理を振り分けます。

これでも一応機能します。
しかし「0」が日曜日、「1」が月曜日…というのはこのコードを書いたプログラマが勝手に決めた法則に過ぎません。
他人が読んだときに非常にわかりづらいものとなっています。
このようなコード中に現れる意味のわかりづらい数値をマジックナンバーといい、避けるべきとされています。

仮に自分しか使わないような個人的なコードでも、日が経つとこういった「約束事」は結構忘れてしまうものです。
コメントに残すという手もありますが、このような場合には列挙型を使用すべきです。

上記コードを列挙型を用いて書き直したのが以下のコードです。


enum DayOfWeek
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

static void Main(string[] args)
{
    ShowDayOfWeek(DayOfWeek.Sunday);
    ShowDayOfWeek(DayOfWeek.Wednesday);

    Console.ReadLine();
}

static void ShowDayOfWeek(DayOfWeek dayOfWeek)
{
    switch(dayOfWeek)
    {
        case DayOfWeek.Sunday:
            Console.WriteLine("今日は日曜日です");
            break;
        case DayOfWeek.Monday:
            Console.WriteLine("今日は月曜日です");
            break;
        case DayOfWeek.Tuesday:
            Console.WriteLine("今日は火曜日です");
            break;
        case DayOfWeek.Wednesday:
            Console.WriteLine("今日は水曜日です");
            break;
        case DayOfWeek.Thursday:
            Console.WriteLine("今日は木曜日です");
            break;
        case DayOfWeek.Friday:
            Console.WriteLine("今日は金曜日です");
            break;
        case DayOfWeek.Saturday:
            Console.WriteLine("今日は土曜日です");
            break;
        default:
            Console.WriteLine("曜日が不明です");
            break;
    }
}
今日は日曜日です
今日は水曜日です

メソッドの呼び出し側を見ると、日曜日の処理を呼び出すために引数に「DayOfWeek.Sunday」という値を指定しています。
数値の「0」を引数に渡す場合と比べると名前がついているので意味が分かりやすくなっています。

列挙型の定義は以下の形式で行います。


enum 列挙型の名前
{
    要素1,
    要素2,
    要素3,
    ...
}

enumというのが列挙型を意味するキーワードです。
記述する場所はメソッドの定義の時と同じく、Mainメソッドの隣です。

「enum」に続き、列挙型の名前を記述します。
名前は変数名やメソッド名の時と同じく自由です。
先頭に数値(0~9)を使用できないというのも同じです。

続くブロック内には定義したい名前をコンマ(,)で区切って記述していきます。
要素はいくつでも自由です。

ここで定義した列挙型は、Mainメソッド内などから「列挙型名.要素名」の形式で使用できます。

列挙型は新しいデータ型を定義するのと同じことです。
つまりその列挙型の変数を作ることができます。
上記のサンプルコードではメソッドの引数として列挙型を利用しています。

列挙型は何をしているのか?

上記コードを読んでも「いったい何をしているのか?」とピンとこないかもしれません。
列挙型は、最初に説明した通り「定数の定義」と考えるとわかりやすいです。

上記の列挙型の定義は実は省略した記述方法です。
省略せずに記述すると以下のようになります。


enum DayOfWeek
{
    Sunday      = 0,
    Monday      = 1,
    Tuesday     = 2,
    Wednesday   = 3,
    Thursday    = 4,
    Friday      = 5,
    Saturday    = 6
}

「Sunday」は「0」、「Monday」は「1」…と、数値に順番に名前を与えているのです。
コード中に「DayOfWeek.Wednesday」と書けば、それは内部的には「3」と書いたことと同じになります。
ただし、普通の数値ではなく「DayOfWeek型」の値になるので、そのままでは足し算に使用したりはできません。
(数値に変換する方法は後述)

数値の指定を省略して記述すると、先頭から順に0、1、2…という数値が割り振られます。

列挙型の数値指定

列挙型の値はかなり自由に設定できます。
例えば、


enum DayOfWeek
{
    Sunday = -2,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

とすれば、上から順に「-2、-1、0…」と自動的に割り振られます。


enum DayOfWeek
{
    Sunday,
    Monday,
    Tuesday = 5,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

とすれば、上から順に「0、1、5、6、7…」という値が割り振られます。
列挙型は、

  • 最初の要素の数値指定を省略すれば「0」、指定があればその数値
  • 数値は「1」ずつ加算していく
  • 途中で数値指定があればその数値に変更
  • 以降も「1」ずつ加算

というルールで数値が割り振られます。


enum DayOfWeek
{
    Sunday,
    Monday,
    Tuesday     = -1,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

上記のような列挙型も作れますが、値が重複するので限定的な用法となるでしょう。
(SundayとWednesdayが0になるので、数値で判定できなくなる)

また、列挙型は少なくとも「0」となる値を含めておくことが推奨されます。
これは既定値が0であるためです。

列挙型のデータ型指定

列挙型は何も指定しなければint型が使用されます。
データ型はbyte、sbyte、short、ushort、int、uint、long、ulong のいずれかを指定できます。

例えばlong型に変更するには以下のように、列挙型名の後ろにコロン(:)を記述し、その後にデータ型を指定します。


enum DayOfWeek : long
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

数値を順に割り当てる場合はint型のサイズで不足することはありませんが、ビットフィールドとして利用する場合に大きな値が必要になる場合もあります。

列挙型の数値を得る

列挙型で定義されている数値を得るには、列挙型をキャストします。


//列挙型の定義は省略

DayOfWeek dayOfWeek = DayOfWeek.Wednesday;

int numDayOfWeek = (int)dayOfWeek;

Console.WriteLine(numDayOfWeek);
3

以下のように直接数値に変換することもできます。


int numDayOfWeek = (int)DayOfWeek.Wednesday;

列挙型の文字列を得る

列挙型で定義されている要素名の文字列を得るにはToStringメソッドを使用します。


//列挙型の定義は省略

DayOfWeek dayOfWeek = DayOfWeek.Wednesday;

string strDayOfWeek1 = dayOfWeek.ToString();

//直接文字列に変換する場合
string strDayOfWeek2 = DayOfWeek.Thursday.ToString();

Console.WriteLine(strDayOfWeek1);
Console.WriteLine(strDayOfWeek2);
Wednesday
Thursday

列挙型の数値を一挙に得る

列挙型で定義されている数値をすべて得るにはEnum.GetValuesメソッドを使用します。


//列挙型の定義は省略

Array arrayDayOfWeeks = Enum.GetValues(typeof(DayOfWeek));

foreach(int n in arrayDayOfWeeks)
{
    Console.WriteLine(n);
}
0
1
2
3
4
5
6

Enum.GetValuesメソッドの引数ではtypeofというものが使用されています。
これは指定したデータ型からTypeクラスというデータ型を取得するための演算子です。
Typeクラスにはそのデータ型の様々な情報が格納されています。
Enum.GetValuesメソッドは引数にType型が必要なので、typeof演算子でDayOfWeek列挙型のType型を取り出しています。

クラスとは、複数の変数や関数をひとまとめにして扱うことができる機能、と考えてください。

まだ詳しく説明していない機能なので、あまり深く考えずにサンプルコードの記述を「定型句」としてそのまま覚えるだけでも構いません。

Enum.GetValuesメソッドはArrayというクラスのデータ型を戻り値として返します。
これは配列と同じくデータの集合を扱うのですが、データはobject型に変換して保存されます。
データを取り出す場合はobject型から目的の型にキャストする必要があり、普通に使うには面倒なのであまり使われることがないクラスです。

Arrayクラスをforeach文で使用する場合、繰り返し変数のデータ型にvar(型推論)を使用するとobject型で取得することになります。
取得後にキャスト演算子で型変換するのでも良いですが、最初に型推論ではなく目的のデータ型を指定すれば変換された型で繰り返し変数を使用できます。


foreach(var n in Enum.GetValues(typeof(DayOfWeek)))
{ 
    //変数nはobject型
    //毎回キャストが必要になる
    int num = (int)n;
}

foreach(int n in Enum.GetValues(typeof(DayOfWeek)))
{ 
    //変数nはint型
}

列挙型の文字列を一挙に得る

列挙型で定義されている文字列をすべて得るにはEnum.GetNamesメソッドを使用します。


//列挙型の定義は省略

string[] strDayOfWeeks = Enum.GetNames(typeof(DayOfWeek));

foreach(var s in strDayOfWeeks)
{
    Console.WriteLine(s);
}
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Enum.GetNamesメソッドの引数はEnum.GetValuesの時と同じです。

Enum.GetNamesメソッドはstring型配列を返しますので、従来通りの方法で使用できます。
foreach文で型推論を使用しても問題ありません。

数値を列挙型に変換する

数値を列挙型に変換するには、列挙型でキャストします。


//列挙型の定義は省略

DayOfWeek dayOfWeek = (DayOfWeek)3;

Console.WriteLine(dayOfWeek.ToString());
Wednesday

ただし、列挙型で定義されていない数値であってもキャストできてしまいます。


//列挙型の定義は省略

DayOfWeek dayOfWeek = (DayOfWeek)10;

Console.WriteLine(dayOfWeek.ToString());
10

列挙型にキャスト可能な数値かを判定する

数値が列挙型で定義済みかをチェックするにはEnum.IsDefinedメソッドを使用します。


//列挙型の定義は省略

int num = 10;
DayOfWeek dayOfWeek;
if (Enum.IsDefined(typeof(DayOfWeek), num))
{
    dayOfWeek = (DayOfWeek)num;
}
else
{
    //定義されていない値の場合はとりあえず最初の要素で初期化
    dayOfWeek = DayOfWeek.Sunday;
}

Console.WriteLine(dayOfWeek.ToString());
Sunday

Enum.IsDefinedメソッドの第一引数はEnum.GetValuesと同じです。
第二引数にチェックしたい値を指定します。
戻り値はbool型で、定義済みならば真を、定義されていなければ偽を返します。

列挙型の項目数を得る

C#には列挙型の項目数を得るメソッドは存在しないため、Enum.GetValuesEnum.GetNamesで項目を一括取得し、Lengthプロパティで要素数を得ます。


//どちらも「7」
int count1 = Enum.GetValues(typeof(DayOfWeek)).Length;
int count2 = Enum.GetNames(typeof(DayOfWeek)).Length;

Enum.GetValuesのほうが戻り値のサイズが小さいことがほとんどだと思うので、こちらのほうが若干高速かもしれません。

列挙型を自分で定義する場合で、項目数を増減する可能性が全くないのならばconst定数などに項目数を格納してしまうのも手ですが、Enum.GetValuesを使用しても大した負荷ではないと思うのでお好きな方を使用してください。