C++では、あらかじめ定義しておいた定数をOr演算で組み合わせて使用するという方法がよく用いられています。
.NET Frameworkでも、列挙体定数に対してこの例と同じようにOr演算による組み合わせの操作を行えるようになっています。 .NET Frameworkでは、このような操作を行う列挙体に対してFlagsAttributeを適用することで、列挙体の値がフラグの組み合わせとして使用できることを明示出来るようになっています。
Flags属性を適用した列挙体
Flags属性は列挙体に対して適用し、その列挙体の各定数が組み合わせ可能なフラグであることを明示します。 早速この属性を用いた例を見てみたいと思います。
実行結果を見ると、先ほどのC++のコードと同様の操作を行えるようになっていることがわかります。 ただ、if文でAnd演算によって定数が指定されているかを確かめる場合、C++では条件式の値が0以外であれば真とされたのに対し、C#/VB.NETでは条件式の値はbool型とならなければならないので、C++のように簡潔な記述はできなくなっています。 そのため、値が0で何も指定されていないことを示すDirection.Noneメンバを用意しておき、これと比較する必要があります。
Flags属性を適用すべき/すべきでない場合
Flags属性を適用すれば定数を組み合わせることを明示することはできますが、方角を表すDirection型でこのような組み合わせを可能にするのは不適当でないかと考えられます。 なぜなら次のコードのように、北と西を組み合わせた場合は北西を表すようにすることはできますが、同時に北と南のフラグを組み合わせることも可能になるからです。
このように、単なる値としては有効ですが、意味としては無効という矛盾した値が作成されてしまう可能性があります。 よって、列挙体で北西のような値を表したい場合は、Flags属性を適用した列挙体を使用してOrによって組み合わせるのではなく、Flags属性を適用していない列挙体を使用し、新たに専用のメンバを増やした方が適切であると考えられます。
この方法では北北西を表す必要に迫られた場合にはさらにメンバを追加しなければならないと言う不便性もありますが、「東と北西と北北東」のような無効といえる組み合わせがなされる可能性がないことと比較するとそれほど問題にならないと思われます。
整理すると、Flags属性を指定するべきかどうかの判断基準は次のようになります。
- Flags属性を指定すべき場合
- チェックボックスのように複数の項目を同時に選択することができる場合。
組み合わせることによって無効な値(事象)が現れないようなものを列挙体で表現する場合。 - Flags属性を指定すべきでない場合
- ラジオボタンのように常に一つだけの項目しか選択できない場合。
組み合わせると無効な組み合わせが生じるものやそもそも組み合わせる必要性のないものを列挙体で表現する場合。
では、Flags属性を指定すべき列挙体にはどのようなものがあるのか、その例を見てみたいと思います。 ひとつの例として、ファイルの属性があります。 ファイルの属性には「読み取り専用、隠し、アーカイブ、システム」などの属性が存在します。 これらの属性は組み合わせることが可能で、なおかつ組み合わせによっても無効な値が生じません。
次の例は、ファイルの属性を表す列挙体であるFileAttributes列挙体を実際に作成して使用しています。 何も値が指定されていない場合、つまり属性が指定されていない場合は通常のファイルであることを示すNormalを取るようにしています。
ちなみに、この例で使用したFileAttributes列挙体と同様の機能を持った列挙体がSystem.IO名前空間に存在します(System.IO.FileAttributes)。 こちらのFileAttributes型はこの例で使用したものよりも様々な属性を表すことができるようになっています。
Flags属性を適用した列挙体のガイドライン
Flags属性を適用する場合、各メンバにどのような値を指定すべきか、またどのような名前にすべきか、などのガイドラインがEnumクラスおよびFlagsAttributeクラスでまとめられています。