C++では、あらかじめ定義しておいた定数をOr演算で組み合わせて使用するという方法がよく用いられています。

定数の組み合わせ
#include <iostream>

using namespace std;

#define DIRECTION_NORTH 0x00000001
#define DIRECTION_WEST  0x00000002
#define DIRECTION_SOUTH 0x00000004
#define DIRECTION_EAST  0x00000008
#define DIRECTION_NONE  0x00000000

int main()
{
    int d;

    d = DIRECTION_NORTH | DIRECTION_WEST;

    if ( d & DIRECTION_NORTH ) cout << "North";
    if ( d & DIRECTION_WEST  ) cout << "West";
    if ( d & DIRECTION_SOUTH ) cout << "South";
    if ( d & DIRECTION_EAST  ) cout << "East";

    cout << endl;

    return 0;
}

.NET Frameworkでも、列挙体定数に対してこの例と同じようにOr演算による組み合わせの操作を行えるようになっています。 .NET Frameworkでは、このような操作を行う列挙体に対してFlagsAttributeを適用することで、列挙体の値がフラグの組み合わせとして使用できることを明示出来るようになっています。

§1 Flags属性を適用した列挙体

Flags属性は列挙体に対して適用し、その列挙体の各定数が組み合わせ可能なフラグであることを明示します。 早速この属性を用いた例を見てみたいと思います。

FlagsAttributeを適用した列挙体
using System;

// 方角を表す列挙体
[Flags]
enum Direction : int {
  North = 0x00000001,
  West  = 0x00000002,
  South = 0x00000004,
  East  = 0x00000008,
  None  = 0x00000000,
}

class Sample {
  static void Main()
  {
    Direction d = Direction.North | Direction.West;

    // 各定数とのAndをとってその定数が指定されているかを確かめる
    if ((d & Direction.North) != Direction.None) Console.Write("North");
    if ((d & Direction.West ) != Direction.None) Console.Write("West");
    if ((d & Direction.South) != Direction.None) Console.Write("South");
    if ((d & Direction.East ) != Direction.None) Console.Write("East");

    Console.WriteLine();

    // d の値を直接表示
    Console.WriteLine(d);
  }
}
実行結果
NorthWest
North, West
Press any key to continue

実行結果を見ると、先ほどのC++のコードと同様の操作を行えるようになっていることがわかります。 ただ、if文でAnd演算によって定数が指定されているかを確かめる場合、C++では条件式の値が0以外であれば真とされたのに対し、C#/VB.NETでは条件式の値はbool型とならなければならないので、C++のように簡潔な記述はできなくなっています。 そのため、値が0で何も指定されていないことを示すDirection.Noneメンバを用意しておき、これと比較する必要があります。



§2 Flags属性を適用すべき/すべきでない場合

Flags属性を適用すれば定数を組み合わせることを明示することはできますが、方角を表すDirection型でこのような組み合わせを可能にするのは不適当でないかと考えられます。 なぜなら次のコードのように、北と西を組み合わせた場合は北西を表すようにすることはできますが、同時に北と南のフラグを組み合わせることも可能になるからです。

無意味なフラグの組み合わせ
using System;

// 方角を表す列挙体
[Flags]
enum Direction : int {
  North = 0x00000001,
  West  = 0x00000002,
  South = 0x00000004,
  East  = 0x00000008,
  None  = 0x00000000,
}

class Sample {
  static void Main()
  {
    Direction d;

    // 北と西を組み合わせることで、北西を表すことはできる
    d = Direction.North | Direction.West;

    // ただし、方角を表すのに、北と南が同時に指定できるのは不適切
    d = Direction.North | Direction.South;

    // それでも、単なる値としては有効
    Console.WriteLine(d);
  }
}

このように、単なる値としては有効ですが、意味としては無効という矛盾した値が作成されてしまう可能性があります。 よって、列挙体で北西のような値を表したい場合は、Flags属性を適用した列挙体を使用してOrによって組み合わせるのではなく、Flags属性を適用していない列挙体を使用し、新たに専用のメンバを増やした方が適切であると考えられます。

方角を適切に表現するDirection型
// 方角を表す列挙体
enum Direction : int {
  North     = 1,
  NorthWest = 2,
  West      = 3,
  SouthWest = 4,
  South     = 5,
  SouthEast = 6,
  East      = 7,
  NorthEast = 8,

  None      = 0,
}

この方法では北北西を表す必要に迫られた場合にはさらにメンバを追加しなければならないと言う不便性もありますが、「東と北西と北北東」のような無効といえる組み合わせがなされる可能性がないことと比較するとそれほど問題にならないと思われます。

整理すると、Flags属性を指定するべきかどうかの判断基準は次のようになります。

Flags属性を指定すべき場合
チェックボックスのように複数の項目を同時に選択することができる場合。
組み合わせることによって無効な値(事象)が現れないようなものを列挙体で表現する場合。
Flags属性を指定すべきでない場合
ラジオボタンのように常に一つだけの項目しか選択できない場合。
組み合わせると無効な組み合わせが生じるものやそもそも組み合わせる必要性のないものを列挙体で表現する場合。

では、Flags属性を指定すべき列挙体にはどのようなものがあるのか、その例を見てみたいと思います。 ひとつの例として、ファイルの属性があります。 ファイルの属性には「読み取り専用、隠し、アーカイブ、システム」などの属性が存在します。 これらの属性は組み合わせることが可能で、なおかつ組み合わせによっても無効な値が生じません。

次の例は、ファイルの属性を表す列挙体であるFileAttributes列挙体を実際に作成して使用しています。 何も値が指定されていない場合、つまり属性が指定されていない場合は通常のファイルであることを示すNormalを取るようにしています。

ファイルの属性を表す列挙体
using System;

// ファイルの属性を表す列挙体
[Flags]
enum FileAttributes : int {
  ReadOnly = 0x00000001,
  Hidden   = 0x00000002,
  Archive  = 0x00000004,
  System   = 0x00000008,

  Normal   = 0x00000000,
}

class Sample {
  static void Main()
  {
    // faの値はファイルから取得したとする
    FileAttributes fa = FileAttributes.Hidden | FileAttributes.System;

    Console.WriteLine("ファイルの属性は、{0} です。", fa);
  }
}
実行結果
ファイルの属性は、Hidden, Systemです。
Press any key to continue

ちなみに、この例で使用したFileAttributes列挙体と同様の機能を持った列挙体がSystem.IO名前空間に存在します(System.IO.FileAttributes)。 こちらのFileAttributes型はこの例で使用したものよりも様々な属性を表すことができるようになっています。

§3 Flags属性を適用した列挙体のガイドライン

Flags属性を適用する場合、各メンバにどのような値を指定すべきか、またどのような名前にすべきか、などのガイドラインがEnumクラスおよびFlagsAttributeクラスでまとめられています。