インドカレーファンクラブ

パソコン、カメラ

【C#】Enumの列挙子から情報を得る

抽象的な書き方だけど例えばenum AnimalAnimal.Dogから名称"イヌ"だとか鳴き声"わんわん"を得るだとか、そういうことをする

よくある書き方としては2パターンあると思う

  • 属性を使う
  • 拡張メソッドを使う

どっちかというと前者の方が綺麗だけど、値の取得に複雑な処理が介在するなら後者の方がいいかなと思う

ググれば出てくる気がする話だけど自分用にメモ

属性を使う

こんな感じ
Reflectionを使うのでちょっと複雑

public enum Animal
{
    [Name("イヌ")]
    Dog,
    [Name("ネコ")]
    Cat,
    Giraffe,
}

// AllowMultiple = false: 重複定義はだめ
// Inherited = false    : 属性の継承はさせない
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class NameAttribute : Attribute
{
    public NameAttribute(string value) => Value = value;
    public string Value { get; private set; }
}

public static class EnumExtensions
{
    public static string ToDisplayName(this Enum self)
    {
        FieldInfo fieldInfo = self.GetType().GetField(self.ToString());
        NameAttribute attributes = fieldInfo.GetCustomAttribute<NameAttribute>();
        // NameAttributeが設定されていなかったらエラー (空文字を返すとかでもいいけど)
        if (attributes == null) { throw new InvalidOperationException("NameAttribute isn't set!"); }
        return attributes.Value;
    }
}

void Main()
{
    var dog = Animal.Dog.ToDisplayName(); // イヌ
    //var giraffe = Animal.Giraffe.ToDisplayName(); // NameAttributeが設定されていないからエラー!
    Console.WriteLine(dog);
}

ちょっと説明

NameAttribute : Attribute

名前定義用の属性
標準のDisplayAttributeを使うこともあるけど、
あっちはAttributeUsageの指定の都合でクラスだとかにも持たせられるので新規に属性を作ったほうが綺麗だと思う

で、これをenumのフィールドに付与する

EnumExtensions.ToDisplayName()

NameAttributeの値を取得するためのコードを用意する

※ここについてはキャッシュを持たせてると高速化できて良いみたい
 上記のコードではやってない
 参考:https://qiita.com/soi/items/80a59cb5a533eef82c9f

ここまでのコードはそこそこ汎用的なのでライブラリとかUtilsに持たせることが多いハズ

ということで汎用的なクラスをライブラリとかに押し込めばEnum定義だけに情報を持たせられるので綺麗

拡張メソッドを使う

属性よりはだいぶ分かりやすい書き方だと思う

void Main()
{
    var signal = Signal.Blue.ToDisplayName(); // 青
    Console.WriteLine(signal);
}

public enum Signal
{
    Blue,
    Yellow,
    Red
}

public static class SignalExtensions
{
    public static string ToDisplayName(this Signal self)
    {
        # C# 8.0から導入されたかっこいい書き方
        return self switch
        {
            Signal.Blue   => "青",
            Signal.Yellow => "黄",
            Signal.Red    => "赤",
            _ => throw new InvalidOperationException("Undefined Signal!"),
        };
    }
}

属性の方ではEnumExtensions.ToDisplayName(this Enum self)だったのが
SignalExtensions.ToDisplayName(this Signal self)になっているのが目立つポイント

Signalの振る舞いとして拡張メソッドを用意して、そこで名称を返すよう定義する感じ

ちょっと面倒くさいけど、最初に書いたとおり値の取得に複雑な処理が介在するならこの書き方の方が楽だと思う

(そして本当はこっちもキャッシュさせたほうが良いのでしょう...)