列挙型

enumは、名前付き定数値を定義できる機能である。

マジック定数がコードの品質に与える影響

次のコードは、整数と算術演算の章の演習問題の解答に載っていたものである。

if (operation == 1) {
    result = first + second;

} else if (operation == 2) {
    result = first - second;

} else if (operation == 3) {
    result = first * second;

} else if (operation == 4) {
    result = first / second;
}
D

そのコード内の整数リテラル123、および4は、マジック定数と呼ばれる。これらのリテラルがプログラム内で何を意味するかを判断するのは容易ではない。1が加算を意味し2が減算を意味するなど、各スコープ内のコードを調べなければならない。上記のコードでは、すべてのスコープが1行のみのため、この作業は比較的簡単である。しかし、他のほとんどのプログラムでは、マジック定数の意味を解読するのははるかに困難だ。

マジック定数は、ソースコードの2つの重要な特性、すなわち可読性と保守性を損なうため、使用は避けるべきだ。

enum このような定数に名前を付けることを可能にし、その結果、コードの読みやすさと保守性を向上させる。以下のenum定数を使用すれば、各条件はすぐに理解できるだろう:

	if (operation == Operation.add) {
        result = first + second;

    } else if (operation == Operation.subtract) {
        result = first - second;

    } else if (operation == Operation.multiply) {
        result = first * second;

    } else if (operation == Operation.divide) {
        result = first / second;
    }
D

上記のenumOperationは、マジック定数123、および4の必要性を排除し、次のように定義することができる。

enum Operation { add = 1, subtract, multiply, divide }
D
enumの構文

enumの一般的な定義は次の通りだ:

enum TypeName { ValueName_1, ValueName_2, /* etc. */ }
D

値の実際の型 (基本型) も指定する必要がある場合がある。

enum TypeName : base_type { ValueName_1, ValueName_2, /* etc. */ }
D

これらがどのように使用されるかは、次のセクションで説明する。

TypeNameは、定数の集合が意味するところを定義する。enum型のメンバー定数はすべて、中括弧で囲んで列挙する。以下に例を示す。

enum HeadsOrTails { heads, tails }
enum Suit { spades, hearts, diamonds, clubs }
enum Fare { regular, child, student, senior }
D

各定数のセットは、個別の型の一部になる。例えば、headsおよびtailsは、型HeadsOrTailsのメンバーになる。新しい型は、変数を定義する際に他の基本型と同様に使用できる。

HeadsOrTails result;           // デフォルトで初期化
auto ht = HeadsOrTails.heads;  // 推論された型
D

上記のコードで見たように、enum型のメンバーは、常にそのenum型の名前で指定される。

if (result == HeadsOrTails.heads) {
    // ...
}
D
実際の値と基本型

enum型のメンバー定数は、デフォルトではint型の値として実装される。つまり、表面的にはheadstailsのような名前で表示されるが、数値も持つ。(注釈:必要に応じて、int以外の型を選択することも可能だ。)

プログラマが明示的に指定しない限り、enumメンバーの数値は0から始まり、メンバーごとに1ずつ増加する。例えば、HeadsOrTails enumの2つのメンバーの数値は0と1になる。

writeln("heads is 0: ", (HeadsOrTails.heads == 0));
writeln("tails is 1: ", (HeadsOrTails.tails == 1));
D

出力:

headsは0true
tailsは1true

enumの任意の場所で、値を手動で (再) 設定することができる。これは、上記でOperation.addの値を1に指定した場合と同じだ。次の例では、新しいカウントを2回手動で設定している。

enum Test { a, b, c, ç = 100, d, e, f = 222, g, ğ }
writefln("%d %d %d", Test.b, Test.ç, Test.ğ);
D

出力:

Test.bTest.çTest.ğ
1100224

intenumの値の基底型として適さない場合、enumの名の後に基底型を明示的に指定することができる。

enum NaturalConstant : double { pi = 3.14, e = 2.72 }
enum TemperatureUnit : string { C = "Celsius", F = "Fahrenheit" }
D
enum enum型ではない値

マジック定数を避け、代わりにenum機能を利用することが重要であることを説明した。

しかし、名前付き定数を使用するためだけに、enum型の名前を考えるのは不自然な場合もある。1日の秒数を表す名前付き定数が必要だとしよう。この定数値を含む enum型も定義する必要はない。必要なのは、その名前で参照できる定数値だけだ。このような場合、enumの型名と中括弧は省略する。

enum secondsPerDay = 60 * 60 * 24;
D

定数の型は明示的に指定することができる。これは、右辺から型を推測できない場合に必要である。

enum int secondsPerDay = 60 * 60 * 24;
D

参照するenum型がないため、このような名前付き定数は、その名前だけでコードで使用できる。

totalSeconds = totalDays * secondsPerDay;
D

enumは、他の型の名前付き定数を定義するためにも使用できる。例えば、次の定数の型はstringになる。

enum fileName = "list.txt";
D

このような定数はr値であり明示定数と呼ばれる。

配列や連想配列の明示的定数も作成できる。ただし、後で不変性章で見るように、enumの配列や連想配列には隠れたコストがある可能性がある。

プロパティ

.minおよび.maxプロパティは、enum型の最小値と最大値である。enum型の値が連続している場合、これらの制限の範囲内で、forループで反復処理することができる。

enum Suit { spades, hearts, diamonds, clubs }

for (auto suit = Suit.min; suit <= Suit.max; ++suit) {
    writefln("%s: %d", suit, suit);
}
D

フォーマット指定子 "%s""%d"は異なる出力を生成する:

spades0
hearts1
diamonds2
clubs3

その範囲をループするforeachでは、.maxの値は反復処理から除外されることに注意。

foreach (suit; Suit.min .. Suit.max) {
    writefln("%s: %d", suit, suit);
}
D

出力:

spades: 0
hearts: 1
diamonds: 2
               ← clubsが欠落している

そのため、enumのすべての値を反復する正しい方法は、std.traitsモジュールにあるEnumMembersテンプレートを使うことだ。

import std.traits;
// ...
    foreach (suit; EnumMembers!Suit) {
        writefln("%s: %d", suit, suit);
    }
D

注釈:上記の!文字は、テンプレートのインスタンス化のために使用されている。これについては、後の章で説明する。

spades: 0
hearts: 1
diamonds: 2
clubs: 3       ← clubsが存在する
基本型からの変換

上記のフォーマットされた出力で見たように、enumの値は、その基本型(intなど)に自動的に変換される。逆の変換は自動的には行われない。

Suit suit = 1;       // ← コンパイルエラー
D

その理由のひとつは、無効なenum値になることを避けるためだ。

suit = 100;          // ← は無効な列挙型値になる
D

特定のenum型の有効なenum値に対応することがわかっている値は、明示的な型キャストによってその型に変換することができる。

suit = cast(Suit)1;  // 今はhearts
D

明示的な型キャストを使用する場合、値の有効性を確認するのはプログラマーの責任だ。型キャストについては、後の章で説明する。

演習

整数と算術演算の章の演習問題の計算機プログラムを修正し、ユーザーがメニューから算術演算を選択できるようにしよう。

このプログラムは、少なくとも以下の点で以前のプログラムと異なるものにしよう: