【C#】属性を使って列挙型に文字列を割り当てる

列挙型で定義した列挙子は整数 (ID) が割り当てられており、使い方によっては便利なのですが、文字列を割り当てたいなという場面が出てくると思います。

例えば、マイクラポーション効果を付与したいときにポーション効果一覧をこのように定義します。

public enum Effects
{
    Absorption,
    BadLuck,
    BadOmen,
    Blindness,
    ...
}

これをこのように呼び出します。

Console.WriteLine(Effects.Absorption);
Console.WriteLine(Effects.BadLuck);
Console.WriteLine(Effects.BadOmen);

結果としては

Absorption
BadLuck
BadOmen

と表示されます。確かに文字列では受け取れているのですが、マインクラフト上ではこの文字列は受け付けてくれません。マインクラフトでポーション効果を付与するには ID の文字列が必要です。例えば Absorption (衝撃吸収) は absorption, BadLuck(不幸)は unluck, BadOmen (不吉な予感) は bad_omen などと言ったようにスネークケースになっています。しかし、C#で利用する際にはパスカルケースで利用したいので、このままではIDを一致させることができません。

これを解決する1つの方法は swich 文を使う方法です。

switch (Effect)
{
    case Effects.Absorption:
        return "absorption";
    case Effects.BadLuck:
        return "unluck";
    case Effects.BadOmen:
        return "bad_omen";
    default : ...
}

これを使って返り値を文字列型で受け取れば解決ですね。

absorption
unluck
bad_omen

確かに解決ですが、保守的な面を考えると少し面倒なことになります。もし、マイクラのアップデートで効果が追加された場合、enum に追記した後に switch にも追記しないといけません。変更箇所が複数あると探すのも大変で、どちらかにミスがあっても正しく動作しません。

なので、Attribute (属性)という機能を使って列挙子にタグ付けのようなことをしていきます。

Attribute の自作

今回はマイクラのIDさえタグ付けできればいいので、属性のパラメータとして文字列を受け取れるようなプロパティを定義しておきます。また、Attribute クラスを継承します。

public class MinecraftIDAttribute : Attribute
{
    public string MinecraftID { get; set; }

    public MinecraftIDAttribute(string MinecraftID)
    {
        this.MinecraftID = MinecraftID;
    }
}

public static class EnumToMinecraftID
{
    public static string GetMinecraftID(this Effects Value)
    {
        Type EnumType = Value.GetType();
        FieldInfo FieldInfo = EnumType.GetField(Value.ToString());
        MinecraftIDAttribute[] Attribute = FieldInfo.GetCustomAttributes(typeof(MinecraftIDAttribute), false) as MinecraftIDAttribute[];
        return Attribute[0].MinecraftID;
    }
}

MinecraftIDAttribute では属性の定義がされており、コンストラクタから受け取った文字列 (ID) をプロパティに代入します。EnumToMinecraftID では拡張メソッドが定義されており、列挙子を受け取ります。受け取った列挙子の型とそのフィールド名をもとに属性を取得し、その中に書かれている文字列(プロパティ)を取得しています。GetCustomAttributes の返り値は配列になっているのですが、なぜ配列なのかは分かりません...。

属性をつける

あとは列挙子に属性をつけていきます。

public enum Effects
{
    [MinecraftID("absorption")]
    Absorption,
    [MinecraftID("unluck")]
    BadLuck,
    [MinecraftID("bad_omen")]
    BadOmen,
    ...
}

このようにすれば、アップデートが来るたびに属性をつけた列挙子を定義するだけで済みます。

IDを呼び出す

拡張メソッドを作ったので、そのままくっつけるだけで呼び出せます。

Console.WriteLine(Effects.Absorption.GetMinecraftID());
Console.WriteLine(Effects.BadLuck.GetMinecraftID());
Console.WriteLine(Effects.BadOmen.GetMinecraftID());

実行結果は switch のときと同じなので省略します。

参考

参考にしたサイトです。ありがとうございます。

qiita.com