今更列挙型とは何かという初歩的なことを詳しくは説明しませんが、列挙型を用いるとはっきりとした意味を持った一連の数値定数を扱うことができます。 列挙型がなくても定数を使うことも当然できますが、定数では互いに関連する一連の数値定数でもバラバラに扱われ、単なる数値(または文字列など)の置き換え程度の機能しか持ち得ません。
列挙型の様々な機能を説明するのに先立って、もっとも基本的な列挙型の使い方の例を挙げてみます。
using System; namespace AboutEnumType { // 方角を表す列挙型 enum Direction { North, West, South, East, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { Direction d = Direction.North; if ( d == Direction.North ) Console.WriteLine( "d は北向きです" ); } } }
実行結果については省略しますが「d は北向きです」と表示されるはずです。 この例のように、東西南北という一連の方角を表す定数は、それぞれバラバラに存在するよりも一つのグループとして存在しているべきです。 もちろん、何らかの理由で単なる定数としてあつかった方がよい場合もあります。
ただ、列挙型の値は文字列や数値として、列挙型定数の値の持つ意味を表すこともできます。 定数では、定数として与えられているその値しか持ち得ません。 その例を次に挙げます。
using System; namespace AboutEnumType { // 方角を表す列挙型 enum Direction { North, West, South, East, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { Direction d = Direction.North; Console.WriteLine( "d の方角は {0} です。", d ); Console.WriteLine( "d の持つ値は {0} です。", (int)d ); Console.WriteLine( "Direction.North の持つ値は {0} です。", (int)Direction.North ); Console.WriteLine( "Direction.West の持つ値は {0} です。", (int)Direction.West ); Console.WriteLine( "Direction.South の持つ値は {0} です。", (int)Direction.South ); Console.WriteLine( "Direction.East の持つ値は {0} です。", (int)Direction.East ); } } }
dの方角は North です。 dの持つ値は 0 です。 Direction.North の持つ値は 0 です。 Direction.West の持つ値は 1 です。 Direction.South の持つ値は 2 です。 Direction.East の持つ値は 3 です。 Press any key to continue
このように、列挙型の値を文字列として表記することも可能ですし、値が内部的に保持している数値として表記することも可能です。 このように、列挙型の値が数値を持つだけでなく、意味も同時に持つことができる点で定数より優れているといえます。
列挙型の定数一つ一つを宣言する際に、値を指定することができます。 なにも指定しない場合は、上から順に0, 1, 2...となっていきます。 その定数に特別な数値を与える場合、数値が意味を持つ場合には数値を指定することができます。
using System; namespace AboutEnumType { // 上下方向を表す列挙型 enum UpAndDown { Up = 1, Down = -1, None = 0, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { int locationY = 5; UpAndDown upDown = UpAndDown.Down; Console.WriteLine( "upDown の方向は {0} です。", upDown ); locationY += (int)upDown; Console.WriteLine( "locationY の位置は {0} です。", locationY ); } } }
upDown の方角は Down です。 locationY の位置は 4 です。 Press any key to continue
この例では上下方向を表す列挙型UpAndDownを宣言し、その各定数に値を与えています。 そして、upDownが上下(または方向なし)のうちどこを向いているかによってY座標の位置をずらしています。 このように、列挙型定数の値を直接利用することができます。 また、複数の列挙型定数に同じ値を設定することができますが、その場合は次の例で示すような現象が発生します。
using System; namespace AboutEnumType { // 列挙型 enum SameValueEnum { ValueA = 0, ValueB = 1, ValueC = ValueA, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { SameValueEnum e = SameValueEnum.ValueB; Console.WriteLine( "e: {0}", e ); e = SameValueEnum.ValueC; Console.WriteLine( "e: {0}", e ); } } }
e: ValueB e: ValueA Press any key to continue
この例ではSameValueEnumのValueCにValueAと同じ値を指定しています。 実行結果を見てわかるとおり、一度目の表示では正しく ValueBが表示されていますが、二度目の表示ではeにValueCを割り当てたはずなのにValueAと出力されてしまっています。 これは列挙型の定数は内部では結局数値として扱われてしまうのでこのようになるのです。 ValueAとValueCに相当する定数が互いに別名であるような場合には問題ありませんが、この例のようにValueCと出力されることを期待する場合には注意が必要です。
さらに、このように列挙型定数の値を直接利用するようになると、列挙型定数の値の型が気になってきます。 そのような場合、列挙型の値の型を明示的に指定することができます。 指定できる型は、列挙型の性質からchar型以外の整数型に限られます。 また、なにも指定しなかった場合は既定でintになります。 次の例は列挙型定数の型をshortにした例です。
using System; namespace AboutEnumType { // 上下方向を表す列挙型 enum UpAndDown : short { Up = 1, Down = -1, None = 0, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { short locationY = 5; UpAndDown upDown = UpAndDown.Down; locationY += (short)upDown; Console.WriteLine( "locationY の位置は {0} です。", locationY ); } } }
列挙型は定数に比べ様々な機能を持っています。 また、列挙型に対する操作もいろいろあります。 列挙型の暗黙の基底クラスであるEnum型には様々な静的メソッドが存在します。 次の例はその内のGetName()メソッドを使用したものです。 このメソッドは列挙型と数値から、その列挙型にあってその数値をもつ定数の名前を取得するものです。
using System; namespace AboutEnumType { // 方角を表す列挙型 enum Direction : int { North = -1, West = -2, South = 1, East = 2, None = 0, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { string name; // -2 から 3 までの値が示すDirectionの名前を表示する for ( int i = -2; i <= 3; i++ ) { // iの値と同じ値を持つ列挙型定数の名前を取得 name = Enum.GetName( typeof( Direction ), i ); Console.WriteLine( "{0} means {1}", i, name ); } } } }
-2 means West -1 means North 0 means None 1 means South 2 means East 3 means Press any key to continue
このように、指定された数値に対応する定数の名前を取得することができます。 また、対応する定数がない場合はnullが返されます。 単一の値から名前を取得するのではなく、列挙型内に含まれるすべての定数の名前を取得するにはGetNames()メソッドを、すべての定数の値を取得するには GetValues()メソッドを使用します。 この例を次に示します。
using System; namespace AboutEnumType { // 方角を表す列挙型 enum Direction : int { North = -1, West = -2, South = 1, East = 2, None = 0, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { // Directionの持つ名前 string[] names = Enum.GetNames( typeof( Direction ) ); Console.WriteLine( "'Direction' has following names" ); foreach ( string name in names ) { Console.WriteLine( "\t{0}", name ); } // Directionの持つ値 Array values = Enum.GetValues( typeof( Direction ) ); Console.WriteLine( "'Direction' has following values" ); foreach ( int val in values ) { Console.WriteLine( "\t{0}", val ); } } } }
'Direction' has following names
None
South
East
West
None
'Direction' has following values
0
1
2
-2
-1
Press any key to continue
さらに、任意の値が列挙型定数として宣言されているか否かを知ることもできます。
using System; namespace AboutEnumType { // 方角を表す列挙型 enum Direction : int { North = -1, West = -2, South = 1, East = 2, None = 0, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { Type t = typeof( Direction ); for ( int i = -3; i <= 3; i++ ) { // その値は定数として宣言されているか if ( Enum.IsDefined( t, i ) ) { Console.WriteLine( "{0} is defined in {1} as {2}.", i, t.Name, Enum.GetName( t, i ) ); } else { Console.WriteLine( "{0} is not defined in {1}.", i, t.Name ); } } } } }
-3 is not defined in Direction. -2 is defined in Direction as West. -1 is defined in Direction as North. 0 is defined in Direction as None. 1 is defined in Direction as South. 2 is defined in Direction as East. 3 is not defined in Direction. Press any key to continue
最後に、Parse()メソッドを用いれば、単純に文字列(または文字列化された数値)から目的の列挙型の値を取得することもできます。
using System; namespace AboutEnumType { // 方角を表す列挙型 enum Direction : int { North = -1, West = -2, South = 1, East = 2, None = 0, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { Type t = typeof( Direction ); Direction d; // 大文字小文字を区別して"South"と名付けられた値を取得する d = (Direction)Enum.Parse( t, "South", true ); Console.WriteLine( d ); // 大文字小文字を無視して"north"と名付けられた値を取得する d = (Direction)Enum.Parse( t, "north", true ); Console.WriteLine( d ); // 指定された値から列挙型の値を取得する d = (Direction)Enum.Parse( t, (-2).ToString(), true ); Console.WriteLine( d ); // 指定された値が見つからない場合はArgumentExceptionがスローされる try { Enum.Parse( t, "南", true ); } catch ( ArgumentException ex ) { Console.WriteLine( ex.Message ); } } } }
South North West 要求された値 南 が見つかりませんでした。 Press any key to continue
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; }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でサポートされている列挙型では、列挙型定数に対してこれと同じような操作を行えるようになっています。 それを行うためには、列挙型に対してFlagsAttributeを適用する必要があります。 この属性は列挙型に対して適用し、その列挙型の各定数が組み合わせ可能なフラグであることを明示します。 早速この属性を用いた例を見てみたいと思います。
using System; namespace AboutEnumType { // 方角を表す列挙型 [Flags()] enum Direction : int { North = 0x00000001, West = 0x00000002, South = 0x00000004, East = 0x00000008, None = 0x00000000, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { Direction d; 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#では条件式の値がtrueとbool型で判断されるので、C++の用に簡潔な記述はできなくなっています。 そのため、何も指定されていないことを示すDirection.Noneメンバを活用する必要があります。
このように、Flags属性を使用すれば定数を組み合わせることは可能になるのですが、方角を表すDirection型でこのような組み合わせを可能にするのは不適当でないかと考えられます。 なぜなら次のコードのように、北と西を組み合わせた場合は北西を表すようにすることはできますが、同時に北と南のフラグを組み合わせることも可能になります。 この場合、単なる値としては問題ありませんが、意味としては無意味な値が作成されてしまいます。
[STAThread] static void Main(string[] args) { Direction d; // 北と西を組み合わせることで、北西を表すことはできる d = Direction.North | Direction.West; // ただし、方角を表すのに、北と南が同時に指定されているのは不適切 d = Direction.North | Direction.South; // しかし、単なる値としては有効 Console.WriteLine( d ); }
つまり、この場合の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属性を指定すべき列挙型にはどのようなものがあるのか、その例を見てみたいと思います。 たとえば、ファイルの属性には「読み取り専用、隠し、アーカイブ、システム」などの属性が存在します。 これらの属性は組み合わせることが可能で、なおかつ組み合わせによって無効な値が生じる心配もありません。
次の例はファイルの属性を表す列挙型であるFileAttributes列挙型を実際に作成して使用しています。 何も値が指定されていない場合、つまり属性が指定されていない場合は通常のファイルであることを示すNormalを取るようにしています。
using System; namespace AboutEnumType { // ファイルの属性を表す列挙型 [Flags()] enum FileAttributes : int { ReadOnly = 0x00000001, Hidden = 0x00000002, Archive = 0x00000004, System = 0x00000008, Normal = 0x00000000, } // アプリケーションのエントリーポイントを提供するクラス class AboutEnumType { [STAThread] static void Main(string[] args) { FileAttributes fa; // ファイルから属性を取得したとする fa = FileAttributes.Hidden | FileAttributes.System; // 属性を表示 Console.WriteLine( "ファイルの属性は、{0} です。", fa ); } } }
ファイルの属性は、Hidden, Systemです。 Press any key to continue
ちなみに、この例で使用したFileAttributes列挙型と同様の機能を持った列挙型がSystem.IO名前空間に存在します。 こちらのFileAttributes型はこの例で使用したものよりも様々な属性を表すことができるようになっています。