はじめに
軽量なステートパターンを作っているときにジェネリックで受けたEnum
をInt
にキャストしたい場面があった。
public void EnumToInt<T>(T target) where T : Enum
{
//EnumからIntに変換したい
}
この場合、(int)
でキャストすることができないため別のアプローチが必要になる。
その1 enum → object → int
見るからに遅いが、キャストできる。
public void EnumToInt<T>(T target) where T : Enum
{
int num = (int)(object)target;
}
もちろんBoxing
その2 Convert.ToInt32
public void EnumToInt<T>(T target) where T : Enum
{
int num = Convert.ToInt32(target);
}
ぱっと見良さそうだが、object
型の引数にenum
を渡しているのでBoxing。
その3 Unsafe.As
Unsafe
なので使うかどうかは状況によりけりだが、爆速でキャストできる。
public void EnumToInt<T>(T target) where T : Enum
{
int num = Unsafe.As<T, int>(ref target);
}
また、IntToEnumも同じようにキャストできる。
安全性の確保
enum
のメンバーはint
型だけではなくshort
, long
, byte
などにもできるので、Unsafe
キャストのみでは不安が残る。
enum State : short
{
Idle,
Running,
}
public void EnumCast()
{
var state = State.Idle;
int num = Unsafe.As<State, int>(ref state);
//この場合、ShortToIntのキャストとなり、不正なメモリ領域が読まれる。
}
int
型はimmutableなので破壊的な動作をすることはなさそうだが、想定外の挙動になるので例外をはいたほうがよさそう。Enumのメンバー定数の型はEnum.GetUnderlyingType
で取得できる。
public void EnumToInt<T>(T target) where T : Enum
{
if (Enum.GetUnderlyingType(typeof(T)) == typeof(int) == false)
throw new InvalidOperationException();
int num = Unsafe.As<T, int>(ref target);
}
不正なメモリ領域を読まなければそれでいいという場合、サイズを比較するやり方でも良さそう
if (Unsafe.SizeOf<T>() == Unsafe.SizeOf<int>() == false)
参考