[C#] 何故 enum に拘りたくなるのか?

enum 値に任意の名称やその他の情報を保持する方法について | Masa’s Lab
http://blog.masa1115.com/?p=1062
どうして enum に拘ってるのか… | Masa’s Lab
http://blog.masa1115.com/?p=1078

のところをざっと見て、私なりに考えると、

使ってはいけない定数定義の一例 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2589

でも書いていて…ないか、もっと別なところかもしれませんが、基本はC言語の「#define」と「enum」の違いからです。

■#define は数値、enum は型

1
2
3
4
#define WEEKDAY_SUN 0
#define WEEKDAY_MON 1
#define WEEKDAY_TUE 2
...

1
2
3
4
5
6
enum WEEKDAY {
    SUN = 0,
    MON,
    TUE,
...
};

の違いは、#define のほうは単なる置き換えなので数値あるいは文字列として扱うのですが、enum は「enum WEEKDAY」という型になります。なので、コンパイルするときに、enum WEEKDAY 型にしないとエラーになるという便利な(つーか大抵の場合は面倒くさい)ものがあります。まあ、面倒なので、int 型として比較してしまいますがね。

C# の場合は、これを継承して

1
2
3
4
5
public enum WEEKDAY {
    SUN = 0,
    MON,
    TUE,
}

と書けます。当時 Java で書こうとすると

1
2
3
4
5
6
class WEEKDAY
{
    public static final int SUN = 0;
    public static final int MON = 1;
    public static final int TUE = 2;
}

あたりでげんなりしてまったのですが…まあ、最近のjava2 では enum があるようです。げんなりしていたのは、2000年前後ですね。

そうそう、C# の時になぜ「文字列」や別の型を許さなかったのか?と疑問なのですが、よくわかりませんね。string が使えると便利なのに。

1
2
3
4
5
public enum WEEKDAY {
    SUN = "Sunday",
    MON = "Monday",
    TUE = "Tuesday",
}

リフレクションがあるから大丈夫?ってことなんでしょうか?

■enumを使わずに実装する

「WeekDay.SUN」という値に、数値と英単語と日本語の3つを割り当てようとすると、なかなか大変なので、大抵は下記のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/// <summary>
/// 列挙がいらない場合
/// </summary>
public class WeekDay
{
    // プロパティ定義
    public int Value { get; set; }
    public string Name { get; set; }
    public string NameJa { get; set; }
 
    // enum の代わり
    static public WeekDay SUN { get { return new WeekDay(0, "Sunday", "日曜日"); }}
    static public WeekDay MON { get { return new WeekDay(1, "Monday", "月曜日"); }}
    static public WeekDay TUE { get { return new WeekDay(2, "Tuesday", "火曜日");}}
    static public WeekDay WED { get { return new WeekDay(3, "Wednesday", "水曜日"); } }
    static public WeekDay THU { get { return new WeekDay(4, "Thursday","木曜日");}}
    static public WeekDay FRI { get { return new WeekDay(5, "Friday", "金曜日"); }}
    static public WeekDay SAT { get { return new WeekDay(6, "Satday", "土曜日"); }}
 
    protected WeekDay(int value, string name, string nameJa)
    {
        this.Value = value;
        this.Name = name;
        this.NameJa = nameJa;
    }
}
 
/// <summary>
/// 列挙が必要な場合はリストを作る
/// </summary>
public class WeekDayList : List<WeekDay>
{
    protected WeekDayList() { }
 
    static public WeekDayList Create()
    {
        var lst = new WeekDayList();
             
        lst.Add(WeekDay.SUN);
        lst.Add(WeekDay.MON);
        lst.Add(WeekDay.TUE);
        lst.Add(WeekDay.WED);
        lst.Add(WeekDay.THU);
        lst.Add(WeekDay.FRI);
        lst.Add(WeekDay.SAT);
        return lst;
    }
    public static WeekDayList List
    {
        get { return WeekDayList.Create(); }
    }
}

foreach が使えるように WeekDayList を定義するかどうかは別なのですが、WeekDay クラスみたいなのをだらだらと作っていく訳です。
0,1,2 といったマジックナンバーを記述するのが嫌なのですが、これはデータベース上にあるマスターデータの定数などが入るため、enum を使っても似たような感じになるでしょう。

で、テストコードがこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
/// UnitTest1 の概要の説明
/// </summary>
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestNormal()
    {
        Assert.AreEqual(0, WeekDay.SUN.Value);
        Assert.AreEqual("Sunday", WeekDay.SUN.Name);
        Assert.AreEqual("日曜日", WeekDay.SUN.NameJa);
    }
 
    [TestMethod]
    public void TestNormalList()
    {
        var days = WeekDayList.Create();
        Assert.AreEqual(7, days.Count);
        // 最初の要素
        var day = days.GetEnumerator();
        day.MoveNext();
        Assert.AreEqual("日曜日", day.Current.NameJa);
    }
 
    [TestMethod]
    public void TestNormalList2()
    {
        // foreach で列挙
        string s = "";
        foreach (var d in WeekDayList.List)
        {
            s += string.Format("{0}", d.NameJa[0]);
        }
        Assert.AreEqual("日月火水木金土", s);
    }
}

プロパティで扱えるのと、foreach で取り出せるほうがよいかと。

難点を云えば「enum の代わり」でしかないので、諸々の機能は実装していく必要があります。こんなのが 2,30あると面倒臭くてやっていけないので、Excel VBA を使って自動生成すると楽です。ちなみに、以前の仕事では、Excel VBA を使ってデータベース仕様書から 1万行 の型付Listとenumを自動生成させました。

■enum を使って実装する

Masa さんのエントリーに準じて、enum に属性を付ける方法をやってみましょう。
enum に属性を付けて、適当な文字列をあらかじめ設定しておくというのは良くやる手段なのですが、どうやって取り出すのかが不明ですよね。確かに、以前、私も考えていたもののうまい方法がなくて断念していました。

で、先のエントリーで enum の拡張メソッドを使えばなんとかなる、ってことが分かったので以下ように実装します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/// <summary>
/// Enum で実装する場合
/// </summary>
public enum WeekDayEnum
{
    [WeekDay("Sunday","日曜日")]    SUN  = 0,
    [WeekDay("Monday","月曜日")]    MON,
    [WeekDay("Tuesday","火曜日")]    TUE,
    [WeekDay("Wednesday","水曜日")]    WED,
    [WeekDay("Thursday","木曜日")]    THU,
    [WeekDay("Friday","金曜日")]    FRI,
    [WeekDay("Satday","土曜日")]    SAT,
}
 
/// <summary>
/// 属性設定
/// </summary>
public class WeekDayAttribute : Attribute
{
    internal protected static List<WeekDayAttribute> _lst = null;
 
    public string Name { get; set; }
    public string NameJa { get; set; }
    public WeekDayAttribute(string name, string nameJa)
    {
        this.Name = name;
        this.NameJa = nameJa;
    }
}
 
public static class WeekDayEnumExtentions
{
    public static int Value(this WeekDayEnum self)
    {
        return (int)self;
    }
    public static WeekDayAttribute GetCustomAttribute(WeekDayEnum self)
    {
        Type t = typeof(WeekDayEnum);
        var mi = t.GetMember(self.ToString())[0];
        return Attribute.GetCustomAttribute(mi, typeof(WeekDayAttribute)) as WeekDayAttribute;
    }
    public static string Name(this WeekDayEnum self)
    {
        return WeekDayEnumExtentions.GetCustomAttribute(self).Name;
    }
    public static string NameJa(this WeekDayEnum self)
    {
        return WeekDayEnumExtentions.GetCustomAttribute(self).NameJa;
    }
}

カスタム属性 WeekDayAttribute を作るのと、リフレクションを利用した WeekDayEnumExtentions による拡張メソッドです。
リフレクションのあたりのエラー処理をさぼっていますが、まぁ結構すんなりと書けたかと。これだけ短いと、修正するのも楽でしょう、多分。

で、テストコードはこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[TestClass]
public class UnitTest2
{
    [TestMethod]
    public void TestNormal()
    {
        WeekDayEnum day = WeekDayEnum.MON;
 
        Assert.AreEqual(1,(int)day );
        Assert.AreEqual("Monday",day.Name());
        Assert.AreEqual("月曜日", day.NameJa());
    }
 
    [TestMethod]
    public void TestNormal2()
    {
        Assert.AreEqual("Monday", WeekDayEnum.MON.Name());
        Assert.AreEqual("月曜日", WeekDayEnum.MON.NameJa());
    }
 
    [TestMethod]
    public void TestNormalList2()
    {
        // foreach で列挙
        string s = "";
        foreach (WeekDayEnum d in Enum.GetValues(typeof(WeekDayEnum)))
        {
            s += string.Format("{0}", d.NameJa()[0]);
        }
        Assert.AreEqual("日月火水木金土", s);
    }
}

基本的に一緒なんですが、「拡張メソッド」なので「Name()」や「NameJa()」のように括弧をつけないといけないのいまいちですね。ってのは、Masa さんに同感。

カテゴリー: C# パーマリンク