【C#】列挙体とは?列挙体を使ってマジックナンバーをなくそう

C#

ども、村正です!

今回は、列挙体について紹介していきたいと思います。

列挙体とは?

列挙体(Enum)とは、特定のグループに属する種類のものを、名前が付いたいくつかの選択肢で表現する仕組み(構造)です。よく例として挙げられるのは「曜日を表現する列挙体」でしょう。

以下は曜日を表す列挙体の定義例です。

/// <summary>
/// 曜日を表現します
/// </summary>
public enum DayOfWeek
{
    Sunday,         // 0
    Monday,         // 1
    Tuesday,        // 2
    Wednesday,      // 3
    Thursday,       // 4
    Friday,         // 5
    Saturday        // 6
}

曜日というグループには月曜日火曜日といった種類が属しています。このような構造を列挙体DayOfWeekではMondayTuesdayという種類(列挙体メンバーと呼びます)を持つことで表現しています。

実際には、列挙体は数値(0, 1, 2など)を名前(MondayTuesdayなど)で管理する構造になっています。Sunday~Saturdayという名前の定数に、数値0~6が格納されているイメージです。

列挙体を使うメリット

列挙体を使うメリットは大きく2つあります。

  • 可読性と保守性の向上
  • コンパイル時の不正値チェック

先ほどの「曜日」を例にメリットを見ていきましょう。

まず、列挙体を使うことで各曜日を表す値を0,1,2のようなマジックナンバーではなくSunday,Monday,Tuesdayという意味のある名前で表現(記述)することができるようになります。これによりコードの可読性や保守性が向上します。

さらに、列挙体には定義された種類(列挙体メンバー)以外の値を入れることができません。曜日をint型の変数で表した場合、整数であれば好きな数字を格納できてしまいます。これでは存在しない曜日を表す値を格納できてしまいます。対して列挙体型の変数には定義されていない種類(列挙体メンバー)を格納しようとするとコンパイルエラーが発生するようになっています。これにより存在しない曜日表す値を格納してしまうといったコーディング上の間違いを防ぐことができます。

列挙体をつかうことで可読性と保守性が上がり、さらにコーディング上のミスを防いでよりよいコードが書けるようになるというわけです。

列挙体の使い方

1. 列挙体の定義

列挙体を使うときはまず、列挙体を定義する必要があります。

列挙体を定義するときはenumキーワードを使用します。ちょうどクラスを定義するときにclassキーワードを使用するのと同じ感覚です。

以下のコードでは「曜日を表現する列挙体」を定義しています。

/// <summary>
/// 曜日を表現します
/// </summary>
public enum DayOfWeek
{
    Sunday,         // 0
    Monday,         // 1
    Tuesday,        // 2
    Wednesday,      // 3
    Thursday,       // 4
    Friday,         // 5
    Saturday        // 6
}

※ちなみにこのSundayMondayなどのことを「列挙体メンバー」と呼びます。

コメントで各列挙体メンバーに割り振られている値を書いてみました。コメントを見るとわかるように特に何も指定してしいないと各列挙体メンバーには自動的にint型の整数値が0から順番に割り振られます。つまり、Sundayにはint型の整数値1が、Fridayにはin型の整数値5が振られています。

この自動的に割り振られる値を自分で設定することができます。

以下では列挙体DayOfWeekの各列挙体メンバーに割り振られる値の型を、byte型(0~255の整数値)になるようにしています。つまりTuesdayにはbyte型の8が割り振られているわけです。

/// <summary>
/// 曜日を表現します
/// </summary>
public enum DayOfWeek : byte
{
    Sunday = 2,         // 2
    Monday = 4,         // 4
    Tuesday = 8,        // 8
    Wednesday = 16,     // 16
    Thursday = 32,      // 32
    Friday = 64,        // 64
    Saturday = 128      // 128
}

なお、以下のように1番目の列挙体メンバー(Sunday)にのみ数値を指定すると、ほかの列挙体メンバには自動的に連番が割り振られます。

/// <summary>
/// 曜日を表現します
/// </summary>
public enum DayOfWeek
{
    Sunday = 100,     // 100
    Monday            // 101
    Tuesday,          // 102
    Wednesday,        // 103
    Thursday,         // 104
    Friday,           // 105
    Saturday          // 106
}

2. 列挙体の利用

クラスと同様、列挙体は定義しただけでは何の意味もありません。列挙体を型として持つ変数を作って利用することで意味を成します。

/// <summary>
/// 曜日を表現します
/// </summary>
public enum DayOfWeek
{
    Sunday,         // 0
    Monday,         // 1
    Tuesday,        // 2
    Wednesday,      // 3
    Thursday,       // 4
    Friday,         // 5
    Saturday        // 6
}

以下のコードでは列挙体DayOfWeekを型として持つ変数dayOfWeekを作成(定義)しています。

// 列挙体型の変数"dayOfWeek"を定義
DayOfWeek dayOfWeek;

ここで「列挙体型の変数…。」とつまづいてしまう方がいるかもしれません。そんな時はクラス型の変数を定義するときのことを思い出しましょう。列挙体型の変数の定義も、クラス型の変数を定義するのと同じような感覚です。

さて、この変数dayOfWeekには列挙体DayOfWeekの列挙体メンバーを格納することができます。列挙体メンバーは「列挙体名.名前」のように指定します。クラスのプロパティと同じ感覚ですね。

以下のコードでは「月曜日」を表すDayOfWeek.Mondayを列挙体DayOfWeek型の変数dayOfWeekに格納しています。

// 列挙体型の変数"dayOfWeek"を定義
DayOfWeek dayOfWeek;

// 月曜日を表す値"DayOfWeek.Monday"を格納
dayOfWeek = DayOfWeek.Monday;

前述したようにDayOfWeek.Mondayには1というint型の整数値が振られています。そのためDayOfWeek.Mondayではなく1という値を格納することでも同じように「月曜日を表す値」を格納することができます。

// 列挙体型の変数"dayOfWeek"を定義
DayOfWeek dayOfWeek;

// DayOfWeek列挙体では「DayOfWeek.Monday = 1」であるため、「1」を格納しても「DayOfWeek.Monday」を格納していることと同じ
dayOfWeek = 1;

これは「月曜日をあらわす値としてDayOfWeek.Mondayでも1でもどちらを使っても良い」ということを表しています。しかし1を使ってしまうとマジックナンバーが発生してしまい、読み手が「1ってなに??なにを表しているの???」となってしまう危険があります。このような惨劇を回避するために基本的に1ではなくDayOfWeek.Mondayを使うようにしましょう

// 列挙体型の変数"dayOfWeek"を定義
DayOfWeek dayOfWeek;

// NG(「1」がマジックナンバー)
dayOfWeek = 1;

// OK(「月曜日をあらわす値」が格納されていることがぱっと見でわかる)
dayOfWeek = DayOfWeek.Monday;

さて、断片的な使いかたみてもいまいちわかりにくいかと思います。ということで実際に列挙体を使ってなんやかんやするコードを以下に示します。

public class Program
{
    static void Main(string[] args)
    {
        // 列挙体型の変数を定義
        DayOfWeek dayOfWeek;

        // 月曜日を表す値"DayOfWeek.Monday"を格納
        dayOfWeek = DayOfWeek.Monday;

        // 月曜日もしくは金曜日?
        Console.WriteLine(IsMondayOrFriday(dayOfWeek));                         // True

        // 土曜日を表す値"DayOfWeek.Saturday"を格納
        dayOfWeek = DayOfWeek.Saturday;

        // 月曜日もしくは金曜日?
        Console.WriteLine(IsMondayOrFriday(dayOfWeek));                         // False

        // 土曜日を表す値"DayOfWeek.Friday"を格納
        dayOfWeek = DayOfWeek.Friday;

        // 月曜日もしくは金曜日?
        Console.WriteLine(IsMondayOrFriday(dayOfWeek));                         // True

        // 値に応じた表示
        switch (dayOfWeek)
        {
            case DayOfWeek.Saturday:
                Console.WriteLine("日曜日です");
                break;
            case DayOfWeek.Monday:
                Console.WriteLine("月曜日です");
                break;
            default:
                Console.WriteLine("日曜日でもなければ月曜日でもありません");       // マッチ!
                break;
        }

        // 値に応じた変数dayOfWeekValueを初期化(switch式の利用)
        int dayOfWeekValue = dayOfWeek switch
        {
            DayOfWeek.Sunday    => 0,
            DayOfWeek.Monday    => 1,
            DayOfWeek.Tuesday   => 2,
            DayOfWeek.Wednesday => 3,
            DayOfWeek.Thursday  => 4,
            DayOfWeek.Friday    => 5,                                           // マッチ
            DayOfWeek.Saturday  => 6,
            _ => -99,   //あてはならない場合は-99で変数dayOfWeekValueを初期化
        };
        Console.WriteLine(dayOfWeekValue);                                      // 5

    }

    /// <summary>
    /// 月曜日もしくは金曜日であるかをチェックします。
    /// </summary>
    /// <returns></returns>
    public static bool IsMondayOrFriday(DayOfWeek dayOfWeek)
    {
        if (dayOfWeek == DayOfWeek.Monday || dayOfWeek == DayOfWeek.Friday)
        {
            return true;
        }

        return false;
    }

    /// <summary>
    /// 曜日を表現します
    /// </summary>
    public enum DayOfWeek
    {
        Sunday,         // = 0
        Monday,         // = 1
        Tuesday,        // = 2
        Wednesday,      // = 3
        Thursday,       // = 4
        Friday,         // = 5
        Saturday        // = 6
    }
}

True
False
True
日曜日でもなければ月曜日でもありません
5

最後に

今回は列挙体について紹介していきました。

列挙体を使う主な目的はマジックナンバーの発生を防ぐところにあります。

列挙体を使えばすべてのマジックナンバーに対処できるというわけではありませんが、「曜日」や「実行モード」など、なにかしらの「状態」を表現するときは列挙体を使うチャンスです。積極的に列挙体を使用しマジックナンバーの発生を防ぎましょう!

■列挙体を使わないケース

int DayOfWeek = 1;     // 読者「1ってなに??(困惑)」
(略)
DayOfWeek = 6;         // 読者「6ってなんなの!!(激怒)」
(略)
DayOfWeek = 4;         // 読者「4って...なん...なの......(昇天)」

■列挙体(DayOfWeek)を使うケース

DayOfWeek dayOfWeek = DayOfWeek.Monday;     // 読者「月曜日ね!(ほくほく顔)」
(略)
dayOfWeek = DayOfWeek.Saturday;             // 読者「火曜日ね!(ほくほく顔)」
(略)
dayOfWeek = DayOfWeek.Thursday;             // 読者「木曜日ね!(ほくほく顔)」

コメント