5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ジェネリックで受けたEnumをIntにキャストする際のBoxingの避け方

Posted at

はじめに

軽量なステートパターンを作っているときにジェネリックで受けたEnumIntにキャストしたい場面があった。

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)

参考

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?