【C#】列挙型(enum)で複数の状態を管理する:Flags属性とビット演算の活用

目次

列挙型で「複数の選択肢」を同時に扱う

通常、列挙型(enum)は「待機中」「実行中」「完了」のように、変数がいずれか一つの状態しか取らない場合に使用されます。

しかし、ファイルシステムの権限(読み取り、書き込み、実行)や、ユーザーの役割(管理者、編集者、閲覧者)のように、「読み取り」かつ「書き込み」ができる、といった複数の状態を組み合わせて持ちたい場合があります。

C#では、[Flags]属性とビット演算を利用することで、これを実現できます。この記事では、フラグとしての列挙型の定義と、ビット演算による操作方法について解説します。


Flags属性を使った列挙型の定義

複数のフラグを組み合わせるための列挙型を定義する際には、以下の2つの重要なルールがあります。

  1. [Flags] 属性を付ける: これにより、ToString()メソッドが複数のフラグ名(例: "Read, Write")をカンマ区切りで表示するようになります。
  2. 値に2のべき乗(1, 2, 4, 8…)を割り当てる: これにより、ビット演算で各フラグが重ならないようになります。

コード例:ユーザー権限の定義

ここでは、データベース操作の権限(CRUD)を管理するUserPrivileges列挙型を定義します。

using System;

// [Flags] 属性を付与する
[Flags]
public enum UserPrivileges
{
    // 0: なし
    None = 0,
    
    // 2進数でビットが重ならないように定義する
    // 1 << 0 は 1 (0b_0001)
    Create = 1 << 0, 
    
    // 1 << 1 は 2 (0b_0010)
    Read   = 1 << 1, 
    
    // 1 << 2 は 4 (0b_0100)
    Update = 1 << 2, 
    
    // 1 << 3 は 8 (0b_1000)
    Delete = 1 << 3,

    // よく使う組み合わせを定義することも可能
    // Create | Read | Update | Delete
    FullControl = Create | Read | Update | Delete
}

フラグの操作方法

定義したフラグ列挙型は、ビット演算子を使って組み合わせたり、判定したりします。

フラグの組み合わせ (OR演算 |)

複数の権限を付与するには、ビットごとのOR演算子 | を使用します。

public class FlagsExample
{
    public static void Main()
    {
        // "作成(Create)" と "読み取り(Read)" の権限を付与
        // 0001 | 0010 = 0011 (3)
        UserPrivileges manager = UserPrivileges.Create | UserPrivileges.Read;

        Console.WriteLine($"マネージャーの権限: {manager}");
        // 出力: Create, Read
        // ([Flags]がないと単に "3" と表示されてしまう)
    }
}

フラグの判定 (AND演算 & または HasFlag)

特定の権限を持っているかを確認するには、HasFlagメソッドを使用するのが最も現代的で読みやすい方法です。

内部的な動作としては、ビットごとのAND演算子 & を使用して「そのビットが立っているか」を確認しています。

    public static void CheckPrivileges()
    {
        var editor = UserPrivileges.Read | UserPrivileges.Update;

        // 1. HasFlag メソッドによる判定 (推奨)
        if (editor.HasFlag(UserPrivileges.Update))
        {
            Console.WriteLine("更新権限があります。(HasFlag)");
        }

        // 2. ビット演算 (&) による判定 (従来の書き方)
        // (editor & UserPrivileges.Delete) の結果が 0 でないか、
        // または対象のフラグと等しいかで判定する
        if ((editor & UserPrivileges.Delete) == UserPrivileges.Delete)
        {
            Console.WriteLine("削除権限があります。");
        }
        else
        {
            Console.WriteLine("削除権限はありません。");
        }
        
        // 「None (権限なし)」かどうかの判定
        var guest = UserPrivileges.None;
        if (guest == UserPrivileges.None)
        {
            Console.WriteLine("ゲストには権限がありません。");
        }
    }

フラグの削除 (ビット反転 ~ と AND演算 &)

特定の権限だけを取り消す(OFFにする)には、取り消したいフラグのビット反転(~)と、現在の状態とのAND(&)を取ります。

    public static void RemovePrivilege()
    {
        // フルコントロール権限
        var admin = UserPrivileges.FullControl;
        Console.WriteLine($"初期状態: {admin}"); // Create, Read, Update, Delete

        // 「削除(Delete)」権限だけを剥奪する
        // Delete のビットを反転(0)させ、それと AND を取ることで Delete だけが 0 になる
        admin = admin & ~UserPrivileges.Delete;

        Console.WriteLine($"削除権限剥奪後: {admin}"); // Create, Read, Update
    }

Flags属性の有無による ToString() の違い

[Flags]属性は、ロジック(ビット演算)そのものには影響しませんが、デバッグやログ出力時のToString()の挙動に大きく影響します。

  • [Flags] あり: 複数のフラグが立っている場合、カンマ区切りで名前を表示します。(例: Create, Read
  • [Flags] なし: 合計値に対応する定義済みの名前がない場合、単なる数値が表示されます。(例: 3

可読性を高めるため、ビット演算を行う列挙型には必ず[Flags]属性を付与することが推奨されます。


まとめ

[Flags]属性とビット演算を使用することで、一つの変数で複数の状態を効率的に管理できます。

  • 定義: [Flags]属性を付け、値を2のべき乗(1, 2, 4, 8…)にする。
  • 追加: | 演算子を使用する。
  • 判定: HasFlag メソッドを使用する。
  • 削除: & ~ 演算子の組み合わせを使用する。

ファイル属性、ユーザー権限、曜日の組み合わせなど、複数の選択肢を同時に保持する必要があるデータの設計において、このパターンは非常に有効です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次