C#のEnumを使いやすくしよう

## C# 固有のEnumの問題点
### 1 - 拡張性の無さ 
JavaのEnumは数値以外に様々な付加情報を入れられるのに対し、基本的にC#のEnumは数値しか保持できない。
```java
// JavaのenumはC#のEnumより後発に出来たから色々出来て羨ましい
protected enum Fruits {
    Banana("Okinawa", 32)
    Orange("Ehime", 100),
    Apple("Aomori", 324);

    private String _madein;
    private int _price
    private Fruit(String madein, int price) {
        _madein = madein;
        _price  = price;
    }
}
```

### 2 - 型安全じゃない
中身はただの数値型で保持しているので、不整合な値入れ放題。
DDDとか意識した設計にするともう少し型安全に列挙型を使いたくなるよね。
```c#:test
public class EnumTest {
    [Fact]
    public void Test()
    {
        // システムを破壊する可能性があるありえない数値入れ放題!!
        TestEnum test = (TestEnum)3232;
        _testOutputHelper.WriteLine($"結果は{test}だよFOO!!");
    }      
}
public enum TestEnum
{
    Numu = 0,
    Ami = 1,
    Dabutu = 2,
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456434/572f7a5c-17d5-564d-e540-9558768f408d.png)

## ベースクラス

```c#:AbstractEnum.cs
    /// <summary>
    /// 列挙型の基底クラス
    /// </summary>
    public abstract record AbstractEnum<T>
        where T : AbstractEnum<T>
    {
        /// <summary>
        /// Id
        /// </summary>
        public int Id { get; }

        /// <summary>
        /// 表示名
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        protected AbstractEnum(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public override string ToString()
        {
            return Name;
        }

        /// <summary>
        /// IDによる検索
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>要素</returns>
        /// <exception cref="ArgumentException">IDが見つからない場合</exception>
        public static T FindById(int id)
        {
            var findData = GetAll()
                .FirstOrDefault(e => e.Id == id);

            if (findData is null)
            {
                throw new ArgumentException("Idが見つかりませんでした");
            }

            return findData;
        }

        /// <summary>
        /// すべての要素を取得
        /// </summary>
        /// <returns>列挙された要素</returns>
        public static IEnumerable<T> GetAll()
        {
            return typeof(T)
                .GetFields(
                    BindingFlags.Public |
                    BindingFlags.Static |
                    BindingFlags.DeclaredOnly)
                .Select(f => f.GetValue(null))
                .Cast<T>();
        }

    }
```
別にId/Name以外に含めたい項目があったら継承先でいっぱい含めちゃおう。
recordで作ってるから == で比較とかもできるよ。


## 継承先

```c#:Size.cs
    /// <summary>
    /// サイズ
    /// </summary>
    public record Size : AbstractEnum<Size>
    {
        /// <summary>
        /// 普通
        /// </summary>
        public static readonly Size Normal = new(1, "普通");

        /// <summary>
        /// 小さい
        /// </summary>
        public static readonly Size Small = new(2, "小さい");

        /// <summary>
        /// 大きい
        /// </summary>
        public static readonly Size Large = new(3, "でかい");

        private Size(int id, string name)
            : base(id, name)
        {
        }
    }
```


## 使用例(#Blazor WASM)
Enumで定義された内容をプルダウンでそのまま使いたいときってけっこうありますよね。

```c#:
// [普通、小さい、大きい] でプルダウンを表示
<select @onchange="HandleChange">
    @foreach (var size in Size.GetAll())
    {
       <option value="@size.Id">@size.Name</option>
    }
<select>

@code {
    private void HandleChange(ChangeEventArgs args){
        var selectedSize = Size.FindById(int.Parse((string)args.Value));
        Console.WriteLine($"{selectedSize.Name}が選択されたお！");
    }
}

```