忍ばないノート

UnityやC#をメインに、開発記やTipsを書いています

【C#】enumをビットフラグとしていい感じに使えるようにするクラス

はじめに

今回はenumをビットフラグとして利用し、設定や確認を楽にするクラスを紹介したいと思います。

ビットフラグ

ビットフラグとは、2進数の0, 1をフラグとして利用するものです。
ビット演算を行い、フラグを立てたり消したりしていきます。
詳しくは、こちらの記事が非常に参考になるかと思います。
(新人時代、大変お世話になりました...!)

yas-hummingbird.blogspot.com

めんどくさい

ビットフラグ、単純にメモリの節約だったりキャラクターの状態異常フラグ等としてよく使うのですが、 種類が多くなってくるとenumの様な名前付きの値で管理したくなります。実際楽です。
しかし、何度もビット演算を行ってフラグを立てたり消したり確認したりするのが個人的にめんどくさいし、同じようなコードを書きたくないと思うのです。
例えばこんな、、、

public class Player
{
    enum EState
    {
        None      = 0,
        Poisoned  = 1,          // 毒
        Confusion = ( 1 << 1 ), // 混乱
        Darkness  = ( 1 << 2 ), // 暗闇
    }

    EState flag;

    void Hoge ()
    {
        // 毒と暗闇になった
        flag = EState.Poisoned | EState.Darkness;

        // 諸々あって毒解除
        flag &= ~EState.Poisoned;

        // 本当に消えてる?
        bool hasPoison = ( flag & EState.Poisoned ) != 0;
        
        // 混乱追加
        flag |= EState.Confusion;

        // 暗闇解除
        flag &= ~EState.Darkness;
    }
}

楽する

じゃあクラス化して楽しよう(゚∀゚)
とりあえず、ソースを。
32bit用, 64bit用を用意してみました。

BitFlag

通常用
こんんなのとか

enum EState
{
    None = 0,
    Poisoned,  // 毒
    Confusion, // 混乱
    Darkness,  // 暗闇
}

BitFlag.cs

使い方
enum EState
{
    None = 0,
    Poisoned,  // 毒
    Confusion, // 混乱
    Darkness,  // 暗闇
}

var flag = new BitFlag<EState> ();

// 毒と暗闇になった
flag.SetFlag ( EState.Poisoned );
flag.SetFlag ( EState.Darkness );

// 諸々あって毒解除
flag.RemoveFlag ( EState.Poisoned );

// 本当に消えてる?
bool hasPoison = flag.HasFlag ( EState.Poisoned );

// 混乱追加
flag.SetFlag ( EState.Confusion );

// 暗闇解除
flag.RemoveFlag ( EState.Darkness );

BitEnumFlag

2の累乗値で定義しているもの向け。
(良いクラス名が思いつかなかった。。。後で更新するかもです)
こんなのとか

enum EState
{
    None      = 0,
    Poisoned  = 1,          // 毒
    Confusion = ( 1 << 1 ), // 混乱
    Darkness  = ( 1 << 2 ), // 暗闇
}

BitEnumFlag.cs

使い方
enum EState
{
    None      = 0,
    Poisoned  = 1,          // 毒
    Confusion = ( 1 << 1 ), // 混乱
    Darkness  = ( 1 << 2 ), // 暗闇
}

var flag = new BitEnumFlag<EState> ();

// 毒と暗闇になった
flag.SetFlag ( EState.Poisoned | EState.Darkness );

// 諸々あって毒解除
flag.RemoveFlag ( EState.Poisoned );

// 本当に消えてる?
bool hasPoison = flag.HasFlag ( EState.Poisoned );

// 混乱追加
flag.SetFlag ( EState.Confusion );

おわりに

普段はBitEnumFlagを使いつつ、明示的に値を割り当てていないenumに対してはBitFlagを使うと良いかなと思います。
あと、各クラスのジェネリックメソッド内ではBoxingが発生します。
パフォーマンスを気にするのであれば、int や long にキャストしてからSetFlag() に投げると良いかなと思います。

参考

ufcpp.net