enum 利用時の注意事項について

enumキーワードを使って列挙型を宣言可能です
コーディングする上で便利な部分も多いですが、注意事項もあります

※以下サンプルの動作はLINQPadを使って確認可能です(Dump拡張メソッドはLINQPadで定義されているもののため、VisualStudioでは使用できません)
LINQPadについては、[こちら](http://qiita.com/kazuhisam3/items/293103991a9f0bb8dcb7)をご参照ください

#列挙型には基になる型があり、その型の範囲内であれば任意の値が割り当て可能

![image](https://qiita-image-store.s3.amazonaws.com/0/52146/a559f28f-23cf-0248-8b5a-7a520d854ac1.png)

```csharp
void Main()
{
	var days = Days.Fri;
	days.Dump();
	// Enum.IsDefinedで定義されている値か、チェック可能。この結果はTrue
	Enum.IsDefined(typeof(Days), days).Dump();
	
	// Daysでcastすると、定義していない値でも代入可能
	days = (Days)128;
	days.Dump();
	
	// 定義済みかチェックするとFalse
	Enum.IsDefined(typeof(Days), days).Dump();
}

// 基になる既定の型は int
enum Days {Sat = 0, Sun = 1, Mon = 2, Tue = 3, Wed = 4, Thu = 5, Fri = 6};

// 既定の型を変更したい場合は、識別子に続けてコロンを使用し、その後に型を記述します
//enum Days : byte {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
```

上記の例のように、enumで定義した値の範囲外でも代入可能となります（変換失敗などの例外は発生しない）
ユーザ入力など外部入力から受け取った値をenumにcastして使用する処理は定義済みの範囲内かチェックする必要があります

また列挙型で許容される型は以下となります
[enum (C# リファレンス)](https://msdn.microsoft.com/ja-jp/library/sbbt4032.aspx)

> 列挙型で許容される型は、byte、sbyte、short、ushort、int、uint、long、ulong です。

#enumの値はコンパイル時に定数に置き換えられる
これはそもそも列挙型が、
[定数 (C# プログラミング ガイド)](https://msdn.microsoft.com/ja-jp/library/ms173119.aspx)

> 組み込みの整数型 (int、uint、long など) の名前付き定数を定義

するからです

そのため、
アセンブリA：enumを定義
アセンブリB：アセンブリAのenumを参照
している場合、アセンブリAでenumの値を変更した場合は、アセンブリBも再コンパイルする必要があります
(アセンブリAの更新されたdllだけ参照してもアセンブリBは古い情報のまま動いてしまうため）
※定数(const定義したもの)全般について同じ事が言えます

以下はLINQPadにて、左側の処理を、ILに変換した結果を右側に表示しています
constで定義した値が直接数値が設定されているのと同様に、enumで定義した値についても直接設定されています

![image](https://qiita-image-store.s3.amazonaws.com/0/52146/7b8d8eb4-6d6b-72a8-a0f6-8dd2c7a7b91f.png)

```csharp
void Main()
{
	var days = Days.Fri;
	var a = 1;
	var b = z;
	var c = (Days)8;
}

private const int z = 1;

enum Days {Sat = 0, Sun = 1, Mon = 2, Tue = 3, Wed = 4, Thu = 5, Fri = 6};
```

#enumにFlags属性を付けた場合、複数のFlagが設定された値を[Enum.IsDefined メソッド](https://msdn.microsoft.com/ja-jp/library/system.enum.isdefined(v=vs.110).aspx)で判定するとFalseとなる
どういう事かは、以下の処理を確認願います

![image](https://qiita-image-store.s3.amazonaws.com/0/52146/0455bd0f-3dfa-488b-d210-bf8bd38c358f.png)

```csharp
[Flags]
public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 }

static void Main()
{
	// 定義済みの値のためTrue
	Enum.IsDefined(typeof(BorderSides), BorderSides.None).Dump("BoderSides IsDefined");
	Enum.IsDefined(typeof(BorderSides), BorderSides.Left).Dump("BoderSides IsDefined");
	Enum.IsDefined(typeof(BorderSides), BorderSides.Right).Dump("BoderSides IsDefined");
	
	// LeftとRightの複合値をIsDefinedで判定するとFalseとなる
	Enum.IsDefined(typeof(BorderSides), (BorderSides.Left | BorderSides.Right)).Dump("BoderSides IsDefined");
}
```

複数のFlagが設定されている場合に、IsDefinedと同等(複数Flagが設定されている場合でも、その中で取り得る値なのであればTrueとしたい)な判定が行いたい場合は、以下で定義した```IsFlagDefined```などによって判定可能となります

```csharp
[Flags]
public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 }

static void Main()
{
	var leftRight = BorderSides.Left | BorderSides.Right;
	
	// こちらはFalse
	Enum.IsDefined(typeof(BorderSides), leftRight).Dump();
	
	// こちらはTrue
	IsFlagDefined(leftRight).Dump();
}

static bool IsFlagDefined(Enum e)
{
	decimal d;
	// Enumとして使用可能な値であれば、ToStringを実施するとEnumの文字列になるが、範囲外であれば数値が返されることを利用して判定する
	return !decimal.TryParse(e.ToString(), out d);
}
```

単純にFlagがセットされているかどうかを判定する場合は、以下のように[Enum.HasFlag メソッド](https://msdn.microsoft.com/ja-jp/library/system.enum.hasflag%28v=vs.110%29.aspx)を使う事で可能です

```csharp
[Flags]
public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 }

void Main()
{
	var borderSides = BorderSides.Left | BorderSides.Right;
	// Flagがセットされているか判定するだけであれば、HasFlagメソッドが使用可能
	// 結果はTrue
	borderSides.HasFlag(BorderSides.Left).Dump();
	
	// 論理積をとって比較(HasFlagがあるのでこういう処理をあえてする事はないと思いますが）
	// 結果はTrue
	((borderSides　& BorderSides.Right) == BorderSides.Right).Dump();
}
```

他にもあれば追記していきます