C#7.3 列挙体に関するヘルパーメソッド


C# 7.3 では型制約に Enum を指定することができるようになり、列挙体に対するジェネリックメソッドを実装しやすくなりました。列挙値に対する付加情報を保持して値の変換などを行うクラスを実装してみました。

## 実装した機能 ##

* 列挙値に対する表示名を取得する機能。
* 列挙値に対して表示順を設定し、表示順でソートされた列挙値リストを取得する機能。
* 列挙値に対して文字列型のエイリアスを設定し、互いに変換する機能。
* 表示名やエイリアスなどは列挙値に対して属性を定義するか、実行時に任意の変換メソッドを登録します。
* （列挙値, 列挙値に関連付けられた表示名やエイリアスなどの情報）の組み合わせは、ディクショナリを用いた汎用的なメタデータクラスで管理しています。

## ソースコード ##

[GitHub](https://github.com/mxProject/EnumHelper) で公開しています。

## サンプルコード ##

### サンプル列挙体の定義 ###

SampleEnum, SampleFlag という二つの列挙体を定義しました。

* どちらもビットフラグで表される同じ値を持ちます。
* LeftRight と RightLeft は同じ値（Left | Right）です。同じ値の列挙値が複数定義されている場合、最初に見つかった定義が有効になります。
* SampleEnum には FlagsAttribute を付与せず、SampleEnum には FlagsAttribute を付与しています。

```c#
enum SampleEnum
{
    [EnumAlias("N"), EnumHidden]
    None = 0,

    [EnumDisplay("左", 2), EnumAlias("L")]
    Left = 1,

    [EnumDisplay("右", 1), EnumAlias("R")]
    Right = 2,

    [EnumDisplay("中央", 3), EnumAlias("C")]
    Center = 4,

    [EnumDisplay("左右", 4), EnumAlias("LR"), EnumHidden]
    LeftRight = Left | Right,

    [EnumDisplay("右左", 5), EnumAlias("RL"), EnumHidden]
    RightLeft = Right| Left,

    [EnumHidden]
    All = Left | Right | Center,
}

[Flags]
enum SampleFlag
{
    [EnumAlias("N"), EnumHidden]
    None = 0,

    [EnumDisplay("左", 2), EnumAlias("L")]
    Left = 1,

    [EnumDisplay("右", 1), EnumAlias("R")]
    Right = 2,

    [EnumDisplay("中央", 3), EnumAlias("C")]
    Center = 4,

    [EnumDisplay("左右", 4), EnumAlias("LR"), EnumHidden]
    LeftRight = Left | Right,

    [EnumDisplay("右左", 5), EnumAlias("RL"), EnumHidden]
    RightLeft = Right | Left,

    [EnumHidden]
    All = Left | Right | Center,
}
```

### 列挙値に対する属性 ###

列挙値に付与している属性の役割は次の通りです。

| 型 | 役割 |
|:--|:--|
| EnumAlias | 列挙値に対する文字列値のエイリアスを定義します。 |
| EnumDisplay | 列挙値の表示名と表示順を定義します。 |
| EnumHidden | 非表示の値であることを定義します。 |

### 実行サンプル（EnumAlias） ###

```c#

// エイリアスから列挙値へ変換
Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("N"));
Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("L"));
Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("R"));
Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("C"));
Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("LR"));

> None
> Left
> Right
> Center
> LeftRight

Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("N"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("L"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("R"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("C"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("LR"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("L,R"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("R,L"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("L,C"));
Debug.WriteLine(EnumHelper.FromAlias<SampleFlag>("L,R,C"));

> None
> Left
> Right
> Center
> LeftRight
> LeftRight
> LeftRight
> Left, Center
> All

// 存在しないエイリアスから列挙値への変換
Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("X"));

> ArgumentException がスローされる

Debug.WriteLine(EnumHelper.TryFromAlias("X", out SampleEnum value));

> False

// 列挙値をエイリアスに変換
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.None));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.Left));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.Right));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.Center));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.LeftRight));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.RightLeft));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.Left | SampleEnum.Center));
Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.All));

> N
> L
> R
> C
> LR
> (null)
> (null)

Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.None));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.Left));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.Right));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.Center));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.RightLeft));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.Left | SampleFlag.Center));
Debug.WriteLine(EnumHelper.GetAlias(SampleFlag.All));

> N
> L
> R
> C
> LR
> LR
> L,C
> L,R,C,LR

// 列挙値をエイリアスに変換（拡張メソッド版）
Debug.WriteLine(SampleEnum.Left.GetAlias());
Debug.WriteLine(SampleFlag.Right.GetAlias());

> L
> R

// 任意の変換メソッドを登録した変換
EnumHelper.RegistAlias<SampleEnum>(enumValue =>
{
    // 定義名を大文字に変換したものをエイリアスとする
    return new EnumAliasInfo(enumValue.ToString().ToUpper());
}
);

Debug.WriteLine(EnumHelper.FromAlias<SampleEnum>("LEFT"));
> Left

Debug.WriteLine(EnumHelper.GetAlias(SampleEnum.Right));
> RIGHT
```

### 実行サンプル（EnumDisplay, EnumHidden） ###

```c#

// 表示名を取得
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.None));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.Left));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.Right));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.Center));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.LeftRight));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.RightLeft));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.Left | SampleEnum.Center));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.All));

> None
> 左
> 右
> 中央
> 左右
> 左右
> (null)
> All

Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.None));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.Left));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.Right));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.Center));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.RightLeft));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.Left | SampleFlag.Center));
Debug.WriteLine(EnumHelper.GetDisplayName(SampleFlag.All));

> None
> 左
> 右
> 中央
> 左右
> 左右
> 左,中央
> All

// 表示名を取得（拡張メソッド版）
Debug.WriteLine(SampleEnum.Left.GetDisplayName());
Debug.WriteLine(SampleFlag.Right.GetDisplayName());

> 左
> 右

// 表示順で列挙値を取得
IList<SampleEnum> values = EnumHelper.GetOrderedValues<SampleEnum>(false);
for (int i = 0; i < values.Count; ++i)
{
    Debug.WriteLine(string.Format("[{0}] {1}", i, values[i]));
}

> [0] None
> [1] All
> [2] Right
> [3] Left
> [4] Center
> [5] RightLeft
> [6] RightLeft

// 表示順で列挙値を取得（非表示を除く）
IList<SampleEnum> values = EnumHelper.GetOrderedValues<SampleEnum>(true);
for (int i = 0; i < values.Count; ++i)
{
    Debug.WriteLine(string.Format("[{0}] {1}", i, values[i]));
}

> [0] Right
> [1] Left
> [2] Center

// 任意の変換メソッドを登録した変換
EnumHelper.RegistDisplayInfo<SampleEnum>(enumValue =>
{
    // 定義名を大文字に変換したものを表示名とする
    // 数値に変換したものを表示順とする
    return new EnumDisplayInfo(enumValue.ToString().ToUpper(), (int)enumValue);
}
);

Debug.WriteLine(EnumHelper.GetDisplayName(SampleEnum.Right));
> RIGHT

Debug.WriteLine(EnumHelper.GetDisplayOrder(SampleEnum.Right));
> 2
```

### 実行サンプル（数値文字列） ###

```c#

// 数値文字列へ変換
Debug.WriteLine(EnumHelper.ToNumericString(SampleEnum.Left));
Debug.WriteLine(EnumHelper.ToNumericString(SampleEnum.Right, "d2"));
Debug.WriteLine(EnumHelper.ToNumericString(SampleEnum.Left | SampleEnum.Center));
Debug.WriteLine(EnumHelper.ToNumericString(SampleEnum.All));

> 1
> 02
> 5
> 7

// 数値文字列へ変換（拡張メソッド版）
Debug.WriteLine(SampleEnum.Left.ToNumericString());
Debug.WriteLine(SampleEnum.Right.ToNumericString("d2"));
Debug.WriteLine((SampleEnum.Left | SampleEnum.Center).ToNumericString());
Debug.WriteLine(SampleEnum.All.ToNumericString());

> 1
> 02
> 5
> 7

// 数値文字列から変換
Debug.WriteLine(EnumHelper.FromNumericString<SampleEnum>("1"))
Debug.WriteLine(EnumHelper.FromNumericString<SampleEnum>("02"))
Debug.WriteLine(EnumHelper.FromNumericString<SampleEnum>("5"))
Debug.WriteLine(EnumHelper.FromNumericString<SampleEnum>("7"))

> Left
> Right
> 5
> All

Debug.WriteLine(EnumHelper.FromNumericString<SampleFlag>("1"))
Debug.WriteLine(EnumHelper.FromNumericString<SampleFlag>("02"))
Debug.WriteLine(EnumHelper.FromNumericString<SampleFlag>("5"))
Debug.WriteLine(EnumHelper.FromNumericString<SampleFlag>("7"))

> Left
> Right
> Left, Center
> All
```

### 実行サンプル（論理演算） ###

汎用処理内で使用することを想定したジェネリックメソッドです。値のキャストには Expression を使用していますが、少ながらずオーバーヘッドは発生します。

```c#

// EnumHelper.And<TEnum>(TEnum target, TEnum value) where TEnum : Enum
Debug.WriteLine(EnumHelper.And(SampleFlag.Left, SampleFlag.Right));
Debug.WriteLine(EnumHelper.And(SampleFlag.LeftRight, SampleFlag.Right));
Debug.WriteLine(EnumHelper.And(SampleFlag.Right, SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.And(SampleFlag.All, SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.And(SampleFlag.All, SampleFlag.RightLeft));

> None
> Right
> Right
> LeftRight
> LeftRight

// EnumHelper.Or<TEnum>(TEnum target, TEnum value) where TEnum : Enum
Debug.WriteLine(EnumHelper.Or(SampleFlag.Left, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Or(SampleFlag.LeftRight, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Or(SampleFlag.Right, SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.Or(SampleFlag.Center, SampleFlag.Left));
Debug.WriteLine(EnumHelper.Or(SampleFlag.Center, SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.Or(SampleFlag.All, SampleFlag.RightLeft));

> LeftRight
> LeftRight
> LeftRight
> Left, Center
> All
> All

// EnumHelper.Xor<TEnum>(TEnum target, TEnum value) where TEnum : Enum
Debug.WriteLine(EnumHelper.Xor(SampleFlag.Left, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Xor(SampleFlag.LeftRight, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Xor(SampleFlag.Right, SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.Xor(SampleFlag.LeftRight, SampleFlag.RightLeft));
Debug.WriteLine(EnumHelper.Xor(SampleFlag.All, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Xor(SampleFlag.All, SampleFlag.RightLeft));

> LeftRight
> Left
> Left
> None
> Left, Center
> Center

// EnumHelper.Remove<TEnum>(TEnum target, TEnum value) where TEnum : Enum
Debug.WriteLine(EnumHelper.Remove(SampleFlag.Left, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Remove(SampleFlag.LeftRight, SampleFlag.Right));
Debug.WriteLine(EnumHelper.Remove(SampleFlag.Right, SampleFlag.LeftRight));
Debug.WriteLine(EnumHelper.Remove(SampleFlag.Left | SampleFlag.Center, SampleFlag.Left));
Debug.WriteLine(EnumHelper.Remove(SampleFlag.All, SampleFlag.RightLeft));
Debug.WriteLine(EnumHelper.Remove(SampleFlag.All, SampleFlag.Left));

> Left
> Left
> None
> Center
> Center
> Right, Center
```