enumを便利に使うメソッド(C#)

#はじめに
C#でEnumを使うとき、int値ではなく、文字に変換して使いたいということがあります。
これをどのように実現するか、一つの実例を書かせていただきます。
またほかにも便利になりそうなメソッドも記載しています。
ちなみにここではサンプルなのでコンソールで作っています。

コメントをいただいて、拡張メソッドのコードの追記をしました(2021/7/25)。
コメントいただいた@albireo さんありがとうございます。

#enumサンプルコード
とりあえず、こんな感じのenumで試していきます。

```C#
    public enum SampleEnum
    {
        Tokyo,
        Nagoya,
        Sapporo,
        Osaka
    }
```

#ありがちな方法
このままenum値を表示させるとこんな感じ

```C#
        static void Main(string[] args)
        {
            Console.WriteLine("enum値のままで表示させた場合");
            Console.WriteLine(SampleEnum.Tokyo);
            Console.WriteLine(SampleEnum.Nagoya);
            Console.WriteLine(SampleEnum.Sapporo);
            Console.WriteLine(SampleEnum.Osaka);

            Console.ReadLine();
        }
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/98de7509-d03b-205c-1a93-a2e826324c98.png)



勉強をはじめたばかりだと、enumは分かるけど、実際にView(Window)に反映させたり、ComboBoxのItesSourceに使うときには、文字で表現したいけど、どうしたらいいかわからないということがあります。自分もそうでした。
たとえば、こんな感じでいちいち文字に変換したりとか...

```C#
        static void Main(string[] args)
        {

            Console.WriteLine("enum値のままで表示させた場合");
            Console.WriteLine(SampleEnum.Tokyo);
            Console.WriteLine(SampleEnum.Nagoya);
            Console.WriteLine(SampleEnum.Sapporo);
            Console.WriteLine(SampleEnum.Osaka);

            Console.WriteLine();

            Console.WriteLine("enum値をCase変換してみた");
            Console.WriteLine(GetName(SampleEnum.Tokyo));
            Console.WriteLine(GetName(SampleEnum.Nagoya));
            Console.WriteLine(GetName(SampleEnum.Sapporo));
            Console.WriteLine(GetName(SampleEnum.Osaka));

            Console.ReadLine();
        }

        /// <summary>
        /// enum値を日本語に変換します
        /// </summary>
        /// <param name="sampleEnum"></param>
        /// <returns></returns>
        static string GetName(SampleEnum sampleEnum)
        {
            switch (sampleEnum)
            {
                case SampleEnum.Tokyo:
                    return "東京";

                case SampleEnum.Nagoya:
                    return "名古屋";

                case SampleEnum.Sapporo:
                    return "札幌";

                case SampleEnum.Osaka:
                    return "大阪";

                default:
                    return null;
            }
        }
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/786cf427-1e22-f7d6-26c5-aecb4acc1d65.png)


これでも別にいいのですが、ただ、このenumが複数のところで使われていて、変更があった時などは、面倒だったりします。
特に変換メソッドが複数のところで書かれてしまうと、エラーの原因になります。

#Descriptionを使いましょう
Microsoft Documentに[DescriptionAttribute クラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.componentmodel.descriptionattribute?view=net-5.0)というものがあります
このDescriptionはプロパティやイベントの説明文として使うものですが、これを読み込んで、stringに変換して使ってしまおうということです。
この辺りはググるとたくさんのサンプルが出てくるので、調べるだけでも勉強になると思います。
記事書きながら検索したらたとえばこんなのがありました

* [Convert Enum to String in C#](https://www.delftstack.com/howto/csharp/csharp-enum-to-string/)
* [Enum Description to String](https://stackoverflow.com/questions/21883458/enum-description-to-string)
* [[C#] enum値に文字列情報を紐付ける方法](http://gacken.com/blog/program/csharp-308_20180710/)

で、さきほどのサンプルコードにDescriptionを付加するとこんな感じになります。
ちなみに、この後使用するメソッドがわかりやすくなるように値をセットしています

```C#
using System.ComponentModel;   //これが必要です

    public enum SampleEnum
    {
        [Description("東京")]
        Tokyo = 1,

        [Description("名古屋")]
        Nagoya = 3,

        [Description("札幌")]
        Sapporo = 5,

        [Description("大阪")]
        Osaka = 7

    }
```

#enum用のメソッドを作る
ここに紹介するメソッドは自分に使いやすいようにしたものですので、使い勝手は人によると思います。ご自身の開発に合わせていろいろと修正をしていただければと思います。

* enum値からDescriptionを取得するメソッド
* Descriptionからenum値を取得するメソッド
* int値からDescriptionを取得するメソッド
* int値からenum値を取得するメソッド
* enum値のリストを取得するメソッド
* Descriptionのリストを取得するメソッド

をヘルパークラスとしてまとめています。メソッドで型指定も行っています。

```C#
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace EnumSample
{
    public static class EnumHelper
    {
        /// <summary>
        /// 列挙体フィールドのDescriptionを取得する。
        /// </summary>
        /// <param name="value">列挙体値</param>
        /// <returns>Description文字列</returns>
        public static string GetEnumDescriptionFromValue<T>(object value)
        {
            var type = typeof(T);
            if (!type.IsEnum)
                throw new ArgumentException();

            string description = null;

            if (value != null)
            {
                string strValue = value.ToString();

                description = strValue;

                if (strValue.Length > 0)
                {
                    FieldInfo fieldInfo = type.GetField(strValue);

                    // Descriptionが複数ある場合はこちらのコードにします
                    //var attrebutes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    //var dcs = attrebutes.Select(x => x.Description).FirstOrDefault();
                    //if(dcs != null)
                    //{
                    //    description = dcs;
                    //}

                    Attribute attribute = Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute));
                    if (attribute != null)
                    {
                        DescriptionAttribute descriptionAttribute = (DescriptionAttribute)attribute;
                        description = descriptionAttribute.Description;
                    }
                }
            }

            return description;
        }


        /// <summary>
        /// Descriptionから列挙体の値を取得する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="description"></param>
        /// <returns></returns>
        public static T GetEnumValueFromDescription<T>(string description)
        {
            var type = typeof(T);
            if (!type.IsEnum)
                throw new ArgumentException();
            FieldInfo[] fields = type.GetFields();
            var field = fields
                            .SelectMany(f => f.GetCustomAttributes(typeof(DescriptionAttribute), false),
                                        (f, a) => new { Field = f, Att = a })
                            .Where(a => ((DescriptionAttribute)a.Att)
                                .Description == description).SingleOrDefault();
            return field == null ? default(T) : (T)field.Field.GetRawConstantValue();
        }

        /// <summary>
        /// int値からDescriptionを取得する
        /// 逆は(int)enum value で取得できます
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetEnumDescriptionFromInt<T>(int value)
        {
            var type = typeof(T);
            if (!type.IsEnum)
                throw new ArgumentException();

            var enumValue = (T)Enum.ToObject(type, value);

            return GetEnumDescriptionFromValue<T>(enumValue);

        }

        /// <summary>
        /// int値からenum value値を取得する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T GetEnumValueFromInt<T>(int value)
        {
            var type = typeof(T);
            if (!type.IsEnum)
                throw new ArgumentException();

            return (T)Enum.ToObject(type, value);
        }

        /// <summary>
        /// enumの値リストを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<T> GetEnumList<T>()
        {
            var type = typeof(T);
            if (!type.IsEnum)
                throw new ArgumentException();

            List<T> values = new List<T>();
            foreach (T value in Enum.GetValues(type))
            {
                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// enumのDescripntionリストを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<string> GetEnumDescriptionList<T>()
        {
            var type = typeof(T);
            if (!type.IsEnum)
                throw new ArgumentException();

            List<string> descriptions = new List<string>();
            foreach (T value in Enum.GetValues(type))
            {
                var description = GetEnumDescriptionFromValue<T>(value);
                descriptions.Add(description);
            }
            return descriptions;
        }
    }
}

```

#確認用コード
実際にメソッドを使って、コーディングしてみます

```C#
        static void Main(string[] args)
        {
            Console.WriteLine();

            Console.WriteLine("enum値のままで表示させた場合");
            Console.WriteLine(SampleEnum.Tokyo);
            Console.WriteLine(SampleEnum.Nagoya);
            Console.WriteLine(SampleEnum.Sapporo);
            Console.WriteLine(SampleEnum.Osaka);
            Console.WriteLine();

            Console.WriteLine("enum値をCase変換してみた");
            Console.WriteLine(GetName(SampleEnum.Tokyo));
            Console.WriteLine(GetName(SampleEnum.Nagoya));
            Console.WriteLine(GetName(SampleEnum.Sapporo));
            Console.WriteLine(GetName(SampleEnum.Osaka));
            Console.WriteLine();

            Console.WriteLine("GetEnumDescriptionFromValueを使ってTokyoのDescriptionを取得");
            var tokyo = EnumHelper.GetEnumDescriptionFromValue<SampleEnum>(SampleEnum.Tokyo);
            Console.WriteLine(tokyo);
            Console.WriteLine();

            Console.WriteLine("GetEnumValueFromDescriptionを使ってDescriptionからenum値を取得");
            var nagoya = EnumHelper.GetEnumValueFromDescription<SampleEnum>("名古屋");
            Console.WriteLine(nagoya);
            Console.WriteLine();

            Console.WriteLine("GetEnumDescriptionFromIntを使ってint値からDescriptionを取得");
            var sapporo = EnumHelper.GetEnumDescriptionFromInt<SampleEnum>(5);
            Console.WriteLine(sapporo);
            Console.WriteLine();

            Console.WriteLine("GetEnumValueFromIntを使ってint値からenum値を取得");
            var osaka = EnumHelper.GetEnumValueFromInt<SampleEnum>(7);
            Console.WriteLine(osaka);
            Console.WriteLine();

            Console.WriteLine("GetEnumListを使ってenumの値リストを取得");
            var enums = EnumHelper.GetEnumList<SampleEnum>();
            foreach(var e in enums)
                Console.WriteLine(e);
            Console.WriteLine();

            Console.WriteLine("GetEnumDescriptionListを使ってDescriptionリストを取得");
            var descriptions = EnumHelper.GetEnumDescriptionList<SampleEnum>();
            foreach (var d in descriptions)
                Console.WriteLine(d);
            Console.WriteLine();

            Console.ReadLine();

        }
```

結果がこんな感じでうまく動いてくれているのがわかります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/eb9769ce-a5b1-c65d-0d04-f26b0c90d4e1.png)


#追記　コード大幅変更しました
@albireoさんにコメントでご指摘していただいたwhere Tつけて、さらに拡張メソッドを作ってみました。
コメントがとても勉強になり、大感謝です！

###GetEnumDescriptionFromValue -> 拡張メソッド(EnumExtention)

```C#
    public static class EnumExtention
    {
        /// <summary>
        /// EnumのValueからDescriptionを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetDescriptionFromValue<T>(this T value) where T : Enum //where T : Enum とすることで Tがenumでない場合はコンパイル時にエラーにしてくれる
        {
            //valueはenum型確定なので空文字が返ることはない
            string strValue = value.ToString();

            var description =
                typeof(T).GetField(strValue)    //FiledInfoを取得
                .GetCustomAttributes(typeof(DescriptionAttribute), false)   //DescriptionAttributeのリストを取得
                .Cast<DescriptionAttribute>()   //DescriptionAttributeにキャスト
                .FirstOrDefault()               //最初の一つを取得、なければnull
                ?.Description;                  //DescriptionAttributeがあればDescriptionを、なければnullを返す

            return description ?? strValue;     //descriptionがnullならstrValueを返す
        }
    }
```
コメントで書いていただいたまんまを拡張メソッドにしただけですww
で、これをもとに、拡張メソッドを勉強したので、ほかのものも書き直したのが以下となります。

###GetEnumValueFromDescription -> 拡張メソッド(StringExtention)

```C#
    public static class StringExtention
    {
        /// <summary>
        /// EnumのDescriptionからValueを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="description"></param>
        /// <returns></returns>
        public static T GetEnumValueFromDescription<T>(this string description) where T : Enum
        {
            var value =
                typeof(T).GetFields()
                .SelectMany(x => x.GetCustomAttributes(typeof(DescriptionAttribute), false),
                    (f, a) => new { field = f, attribute = a })
                .Where(x => ((DescriptionAttribute)x.attribute).Description == description)
                .FirstOrDefault()
                ?.field.GetRawConstantValue();

            //// 値が見つからない場合にエラーとする場合はこちら
            //return (T)(value ?? throw new ArgumentNullException());

            return (T)(value ?? default(T));
        }
    }
```

###GetEnumDescriptionFromInt, GetEnumValueFromInt -> 拡張メソッド(IntExtention)

```C#
    public static class IntExtention
    {
        /// <summary>
        /// int値からEnumのDescription取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string GetEnumDescriptionFromInt<T>(this int value) where T : Enum
        {
            var enumValue = (T)Enum.ToObject(typeof(T), value);

            return enumValue.GetDescriptionFromValue();
        }

        /// <summary>
        /// int値からEnumのValueを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T GetEnumValueFromInt<T>(this int value) where T : Enum
        {
            return (T)Enum.ToObject(typeof(T), value);
        }
    }
```
var enumValue = (T)value;がコンパイルエラー(CS0030)となってしまい、うまくいかなかったので、(T)Enum.ToObject(typeof(T), value);のままとしています。

###GetEnumList, GetEnumDescriptionList -> 拡張メソッド(TypeExtention)

```C#
    public static class TypeExtention
    {
        /// <summary>
        /// enumの値リストを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<T> GetEnumList<T>(this Type type) where T : Enum
        {
            return Enum.GetValues(typeof(T)).Cast<T>().ToList();
        }

        /// <summary>
        /// enumのDescripntionリストを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static IEnumerable<string> GetEnumDescriptionEnumerable<T>(this Type type) where T : Enum
        {
            foreach (T value in Enum.GetValues(typeof(T)))
                yield return value.GetDescriptionFromValue();
        }

        /// <summary>
        /// enumのDescripntionリストを取得
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<string> GetEnumDescriptionList<T>(this Type type) where T : Enum
        {
            List<string> descriptionList = new List<string>();
            foreach (T value in Enum.GetValues(typeof(T)))
                descriptionList.Add(value.GetDescriptionFromValue());

            return descriptionList;
        }
    }

```

###確認用コード

```C#
        static void Test2()
        {
            Console.WriteLine("EnumExtention  GetDescriptionFromValueを使ってTokyoのDescriptionを取得");
            Console.WriteLine(SampleEnum.Tokyo.GetDescriptionFromValue());
            Console.WriteLine();

            Console.WriteLine("StringExtention  GetEnumValueFromDescriptionを使ってDescriptionからenum値を取得");
            Console.WriteLine("名古屋".GetEnumValueFromDescription<SampleEnum>());
            Console.WriteLine();

            Console.WriteLine("IntExtention  GetEnumDescriptionFromIntを使ってint値からDescriptionを取得");
            var sapporo = ((int)5).GetEnumDescriptionFromInt<SampleEnum>();
            Console.WriteLine(sapporo);
            Console.WriteLine();

            Console.WriteLine("IntExtention  GetEnumValueFromIntを使ってint値からenum値を取得");
            var osaka = ((int)7).GetEnumValueFromInt<SampleEnum>();
            Console.WriteLine(osaka);
            Console.WriteLine();

            Console.WriteLine("TypeExtention  GetEnumListを使ってenumの値リストを取得");
            var enumValues = typeof(SampleEnum).GetEnumList<SampleEnum>();
            foreach (var e in enumValues)
                Console.WriteLine(e);

            Console.WriteLine();

            Console.WriteLine("TypeExtention  GetEnumDescriptionListを使ってDescriptionリストを取得");
            var enumDescriptions = typeof(SampleEnum).GetEnumDescriptionList<SampleEnum>();
            foreach (var e in enumDescriptions)
                Console.WriteLine(e);

            Console.WriteLine();

            Console.WriteLine("TypeExtention  GetEnumDescriptionEnumerableを使ってDescriptionリストを取得");
            var enumDescriptionsEnumerable = typeof(SampleEnum).GetEnumDescriptionEnumerable<SampleEnum>();
            foreach (var e in enumDescriptionsEnumerable)
                Console.WriteLine(e);

            Console.ReadLine();
        }
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/e92773d3-5d8d-18bb-9133-e254bcc71038.png)

拡張メソッドを自分ではじめて作りました。
これであっているのか自信がありませんが、とてもよい勉強となりました。