ここでは列挙体の基本とその操作について、また列挙体の暗黙の基底クラスであるEnumクラスについて見ていきます。
列挙体を用いると関連のある一連の数値定数をひとまとめに扱うことができます。 .NET Frameworkにおいては、列挙体は個々の言語に特有な機能としてではなく、フレームワークでその基本的な機能が提供されています。 なお、列挙型という用語が用いられる場合がありますが、ここでは.NET Frameworkのドキュメントに合わせて列挙体に統一します。
列挙体の基本
列挙体の様々な機能を説明するのに先立って、もっとも基本的な列挙体の使い方の例を挙げてみます。
using System;
// 方角を表す列挙体
enum Direction {
North,
West,
South,
East,
}
class Sample {
static void Main()
{
Direction d = Direction.North;
if (d == Direction.North) Console.WriteLine( "d は北向きです" );
}
}
Imports System
' 方角を表す列挙体
Enum Direction
North
West
South
East
End Enum
Class EnumSample
Shared Sub Main()
Dim d As Direction = Direction.North
If d = Direction.North Then Console.WriteLine("d は北向きです")
End Sub
End Class
実行結果については省略しますが「d は北向きです」と表示されるはずです。 この例のように、東西南北という一連の方角を表す定数は、それぞれバラバラに存在するよりも一つのグループとして存在しているべきです。
もちろん、何らかの理由で単なる定数としてあつかった方がよい場合もありますが、一方で、列挙体の場合は値としても名前(つまり値の意味)としても表記でき、それらを結びつけておくことが出来ます。 定数では、定数として与えられているその値しか表記できません。
using System;
// 方角を表す列挙体
enum Direction {
North,
West,
South,
East,
}
class Sample {
static void Main()
{
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);
}
}
Imports System
' 方角を表す列挙体
Enum Direction
North
West
South
East
End Enum
Class EnumSample
Shared Sub Main()
Dim d As Direction = Direction.North
Console.WriteLine("d の方角は {0} です。", d)
Console.WriteLine("d の持つ値は {0} です。", CInt(d))
Console.WriteLine("Direction.North の持つ値は {0} です。", CInt(Direction.North))
Console.WriteLine("Direction.West の持つ値は {0} です。", CInt(Direction.West))
Console.WriteLine("Direction.South の持つ値は {0} です。", CInt(Direction.South))
Console.WriteLine("Direction.East の持つ値は {0} です。", CInt(Direction.East))
End Sub
End Class
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;
// 上下方向を表す列挙体
enum UpAndDown {
Up = 1,
Down = -1,
None = 0,
}
class Sample {
static void Main()
{
int locationY = 5;
UpAndDown upDown = UpAndDown.Down;
Console.WriteLine("upDown の方向は {0} です。", upDown);
locationY += (int)upDown;
Console.WriteLine("locationY の位置は {0} です。", locationY);
}
}
Imports System
' 上下方向を表す列挙体
Enum UpAndDown
Up = 1
Down = -1
None = 0
End Enum
Class EnumSample
Shared Sub Main()
Dim locationY As Integer = 5
Dim upDown As UpAndDown = UpAndDown.Down
Console.WriteLine("upDown の方向は {0} です。", upDown)
locationY += CInt(upDown)
Console.WriteLine("locationY の位置は {0} です。", locationY)
End Sub
End Class
upDown の方角は Down です。 locationY の位置は 4 です。 Press any key to continue
この例では上下方向を表す列挙体UpAndDownを宣言し、その各定数に値を与えています。 そして、upDownが上下(または方向なし)のうちどこを向いているかによってY座標の位置をずらしています。 このように、列挙体定数の値を直接利用することができます。
値を与える場合、複数の列挙体定数に同じ値を設定することができますが、その場合は次の例で示すような現象が発生します。
using System;
// 同じ値を持つ列挙体
enum SameValueEnum {
ValueA = 0,
ValueB = 1,
ValueC = ValueA,
}
class Sample {
static void Main()
{
SameValueEnum e = SameValueEnum.ValueB;
Console.WriteLine("e: {0}", e);
e = SameValueEnum.ValueC;
Console.WriteLine("e: {0}", e);
}
}
Imports System
' 同じ値を持つ列挙体
Enum SameValueEnum
ValueA = 0
ValueB = 1
ValueC = ValueA
End Enum
Class EnumSample
Shared Sub Main()
Dim e As SameValueEnum = SameValueEnum.ValueB
Console.WriteLine("e: {0}", e)
e = SameValueEnum.ValueC
Console.WriteLine("e: {0}", e)
End Sub
End Class
e: ValueB e: ValueA Press any key to continue
この例ではSameValueEnumのValueCにValueAと同じ値を指定しています。 実行結果を見てわかるとおり、一度目の表示では正しく ValueBが表示されていますが、二度目の表示ではeにValueCを割り当てたはずなのにValueAと出力されてしまっています。 これは列挙体の定数は内部では結局数値として扱われてしまうのでこのようになるのです。 ValueAとValueCに相当する定数が互いに別名であるような場合には問題ありませんが、この例のようにValueCと出力されることを期待する場合には注意が必要です。
列挙体定数の型
先の例のように列挙体定数の値を直接利用するようになると、列挙体定数の値の型が気になってきます。 そのような場合、列挙体の値の型を明示的に指定することができます。 指定できる型は、列挙体の性質からchar型以外の整数型に限られます。 また、なにも指定しなかった場合は既定でint(Integer)になります。 次の例は列挙体定数の型をshortにした例です。
using System;
// 上下方向を表す列挙体
enum UpAndDown : short {
Up = 1,
Down = -1,
None = 0,
}
class Sample {
static void Main()
{
short locationY = 5;
UpAndDown upDown = UpAndDown.Down;
Console.WriteLine("upDown の方向は {0} です。", upDown);
locationY += (short)upDown;
Console.WriteLine("locationY の位置は {0} です。", locationY);
}
}
Imports System
' 上下方向を表す列挙体
Enum UpAndDown As Short
Up = 1
Down = -1
None = 0
End Enum
Class EnumSample
Shared Sub Main()
Dim locationY As Short = 5
Dim upDown As UpAndDown = UpAndDown.Down
Console.WriteLine("upDown の方向は {0} です。", upDown)
locationY += CShort(upDown)
Console.WriteLine("locationY の位置は {0} です。", locationY)
End Sub
End Class
列挙体に対する操作
列挙体は単純な定数に比べ様々な機能を持っています。 また、列挙体に対する操作もいろいろあります。 列挙体の暗黙の基底クラスであるEnum型には様々な静的メソッドが存在します。
値の名前の取得 (GetName)
GetNameメソッドを使用すると、指定された値から該当する数値をもつ列挙体の定数の名前を取得することが出来ます。
using System;
// 方角を表す列挙体
enum Direction : int {
North = -1,
West = -2,
South = 1,
East = 2,
None = 0,
}
class Sample {
static void Main()
{
string name;
// --3 から 3 までの値が示すDirectionの名前を表示する
for (int i = -3; i <= 3; i++) {
// iの値と同じ値を持つ列挙体定数の名前を取得
name = Enum.GetName(typeof(Direction), i);
Console.WriteLine("{0} means {1}", i, name);
}
}
}
Imports System
' 方角を表す列挙体
Enum Direction As Integer
North = -1
West = -2
South = 1
East = 2
None = 0
End Enum
Class EnumSample
Shared Sub Main()
Dim name As String
' --3 から 3 までの値が示すDirectionの名前を表示する
For i As Integer = -3 To 3
' iの値と同じ値を持つ列挙体定数の名前を取得
name = System.Enum.GetName(GetType(Direction), i)
Console.WriteLine("{0} means {1}", i, name)
Next
End Sub
End Class
-3 means -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)
単一の値から名前を取得するのではなく、列挙体に含まれるすべての定数の名前を取得するにはGetNamesメソッドメソッドを、すべての定数の値を取得するには GetValuesメソッドを使用します。 この例を次に示します。
using System;
// 方角を表す列挙体
enum Direction : int {
North = -1,
West = -2,
South = 1,
East = 2,
None = 0,
}
class Sample {
static void Main()
{
// Directionの持つ名前
string[] names = Enum.GetNames(typeof(Direction));
Console.WriteLine("'Direction' has following names");
foreach (string name in names) {
Console.WriteLine(" {0}", name);
}
// Directionの持つ値
Array values = Enum.GetValues(typeof(Direction));
Console.WriteLine("'Direction' has following values");
foreach (int val in values) {
Console.WriteLine(" {0}", val);
}
}
}
Imports System
' 方角を表す列挙体
Enum Direction As Integer
North = -1
West = -2
South = 1
East = 2
None = 0
End Enum
Class EnumSample
Shared Sub Main()
' Directionの持つ名前
Dim names As String() = System.Enum.GetNames(GetType(Direction))
Console.WriteLine("'Direction' has following names")
For Each name As String In names
Console.WriteLine(" {0}", name)
Next
' Directionの持つ値
Dim values As Array = System.Enum.GetValues(GetType(Direction))
Console.WriteLine("'Direction' has following values")
For Each val As Integer In values
Console.WriteLine(" {0}", val)
Next
End Sub
End Class
'Direction' has following names None South East West North 'Direction' has following values 0 1 2 -2 -1 Press any key to continue
Enum.GetValues()メソッドが実際に返す配列の型は、列挙体の値の型と同じものになります。 int(Integer)型の列挙体の場合は、int(Integer)型の配列が返されます。
値が定数として宣言されているか (IsDefined)
任意の値が列挙体定数として宣言されているか否かを知るには、IsDefinedメソッドを使用します。
using System;
// 方角を表す列挙体
enum Direction : int {
North = -1,
West = -2,
South = 1,
East = 2,
None = 0,
}
class Sample {
static void Main()
{
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);
}
}
}
}
Imports System
' 方角を表す列挙体
Enum Direction As Integer
North = -1
West = -2
South = 1
East = 2
None = 0
End Enum
Class EnumSample
Shared Sub Main()
Dim t As Type = GetType(Direction)
For i As Integer = -3 To 3
' その値は定数として宣言されているか
If System.Enum.IsDefined(t, i) Then
Console.WriteLine("{0} is defined in {1} as {2}.", i, t.Name, System.Enum.GetName(t, i))
Else
Console.WriteLine("{0} is not defined in {1}.", i, t.Name)
End If
Next
End Sub
End Class
-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)
文字列(もしくは文字列化された数値)から列挙体の値を取得するには、Parseメソッドを使用します。 このメソッドでは、大文字・小文字の違いを無視するかどうかを指定することも出来ます。
using System;
// 方角を表す列挙体
enum Direction : int {
North = -1,
West = -2,
South = 1,
East = 2,
None = 0,
}
class Sample {
static void Main()
{
Type t = typeof(Direction);
Direction d;
// 大文字小文字を区別して"South"と名付けられた値を取得する
d = (Direction)Enum.Parse(t, "South", false);
Console.WriteLine(d);
// 大文字小文字を無視して"north"と名付けられた値を取得する
d = (Direction)Enum.Parse(t, "north", true);
Console.WriteLine(d);
// 指定された値から列挙体の値を取得する
d = (Direction)Enum.Parse(t, "-2", true);
Console.WriteLine(d);
// 指定された値が見つからない場合はArgumentExceptionがスローされる
try {
Enum.Parse(t, "南", true);
}
catch (ArgumentException ex) {
Console.WriteLine(ex.Message);
}
}
}
Imports System
' 方角を表す列挙体
Enum Direction As Integer
North = -1
West = -2
South = 1
East = 2
None = 0
End Enum
Class EnumSample
Shared Sub Main()
Dim t As Type = GetType(Direction)
Dim d As Direction
' 大文字小文字を区別して"South"と名付けられた値を取得する
d = CType(System.Enum.Parse(t, "South", false), Direction)
Console.WriteLine(d)
' 大文字小文字を無視して"north"と名付けられた値を取得する
d = CType(System.Enum.Parse(t, "north", true), Direction)
Console.WriteLine(d)
' 指定された値から列挙体の値を取得する
d = CType(System.Enum.Parse(t, "-2", true), Direction)
Console.WriteLine(d)
' 指定された値が見つからない場合はArgumentExceptionがスローされる
Try
System.Enum.Parse(t, "南", true)
Catch ex As ArgumentException
Console.WriteLine(ex.Message)
End Try
End Sub
End Class
South North West 要求された値 南 が見つかりませんでした。 Press any key to continue
文字列への変換 (ToString)
列挙体の値を文字列化するには、、ToStringメソッドを使用します。 引数で文字列化する際の書式を指定することも出来ます。
using System;
// 方角を表す列挙体
enum Direction : int {
North = -1,
West = -2,
South = 1,
East = 2,
None = 0,
}
class Sample {
static void Main()
{
for (int i = -3; i <= 3; i++) {
Direction d = (Direction)i;
// 列挙体の値を文字列化する
Console.WriteLine("{0,2}: {1,-8} {2,-8} {3,-8} {4,-8}",
i, d.ToString(), d.ToString("G"), d.ToString("D"), d.ToString("X"));
}
}
}
Imports System
' 方角を表す列挙体
Enum Direction As Integer
North = -1
West = -2
South = 1
East = 2
None = 0
End Enum
Class EnumSample
Shared Sub Main()
For i As Integer = -3 To 3
Dim d As Direction = CType(i, Direction)
' 列挙体の値を文字列化する
Console.WriteLine("{0,2}: {1,-8} {2,-8} {3,-8} {4,-8}", _
i, d.ToString(), d.ToString("G"), d.ToString("D"), d.ToString("X"))
Next
End Sub
End Class
-3: -3 -3 -3 FFFFFFFD -2: West West -2 FFFFFFFE -1: North North -1 FFFFFFFF 0: None None 0 00000000 1: South South 1 00000001 2: East East 2 00000002 3: 3 3 3 00000003
列挙体に対して指定できる書式文字列については書式指定子 §.列挙型の書式指定子で詳しく解説しています。
値が特定のフラグを持つか (HasFlags)
.NET Framework 4より、FlagsAttributeの付いた列挙体の値が特定のフラグを持つかどうか調べるためのメソッドHasFlagが追加されています。 FlagsAttributeについては、列挙体とフラグで詳しく解説します。
HasFlagメソッドの説明によると、このメソッドは次のビット演算と同じ結果を返すとされています。
The HasFlag method returns the result of the following Boolean expression.
thisInstance And flag = flag
Enum.HasFlag Method (Enum)
HasFlagsメソッドとビット演算を使った場合の記述を比較すると次のようになります。 上記のとおり、以下のどちらも同等の処理となります。
using System;
using System.IO;
class Sample {
static void Main()
{
FileAttributes attrs = FileAttributes.Archive | FileAttributes.ReadOnly;
if (attrs.HasFlag(FileAttributes.Archive))
Console.WriteLine("archive");
if ((attrs & FileAttributes.Archive) == FileAttributes.Archive)
Console.WriteLine("archive");
}
}
Imports System
Imports System.IO
Class EnumSample
Shared Sub Main()
Dim attrs As FileAttributes = FileAttributes.Archive Or FileAttributes.ReadOnly
If attrs.HasFlag(FileAttributes.Archive) Then
Console.WriteLine("archive")
End If
if (attrs And FileAttributes.Archive) = FileAttributes.Archive Then
Console.WriteLine("archive")
End If
End Sub
End Class
HasFlagメソッドを使うとコードは見やすくなる一方で、ビット演算を使った場合と比べてパフォーマンスは劣化します。 そのため、速度が優先される場合や繰り返しフラグの比較を行う状況ではあまり使いやすいものではありません。 参考までに、HasFlagsメソッドとビット演算での速度を比較した結果を示します。
Microsoft Windows NT 6.0.6002 Service Pack 2 4.0.30319.1 Enum.HasFlag: 00:00:14.3554873 bitwise : 00:00:00.0125018
Unix 2.6.32.25 4.0.30319.1 Enum.HasFlag: 00:00:08.2939831 bitwise : 00:00:00.0170639
using System;
using System.Diagnostics;
using System.IO;
class Test {
static void Main()
{
Console.WriteLine(Environment.OSVersion);
Console.WriteLine(Environment.Version);
Console.WriteLine();
const int act = 10 * 1000 * 1000;
var attrs = FileAttributes.Archive | FileAttributes.ReadOnly;
var sw1 = Stopwatch.StartNew();
for (var i = 0; i < act; i++) {
var isArchive = attrs.HasFlag(FileAttributes.Archive);
var isSystem = attrs.HasFlag(FileAttributes.System);
}
sw1.Stop();
Console.WriteLine("Enum.HasFlag: {0}", sw1.Elapsed);
var sw2 = Stopwatch.StartNew();
for (var i = 0; i < act; i++) {
var isArchive = (attrs & FileAttributes.Archive) == FileAttributes.Archive;
var isSystem = (attrs & FileAttributes.System) == FileAttributes.System;
}
sw2.Stop();
Console.WriteLine("bitwise : {0}", sw2.Elapsed);
}
}