Enum(列挙型)


POINT 列挙型とは数値( 整数 )に名前を割り当てたもの. enum は C, C++ と同じ. 数値に別名をつけたもの. ( define の連続番. ) POINT  プログラムを作成していると、 文字列なのだが出現する可能性のある綴りが限定されている というデータに出会うことがある 例えば 日本の年号は「明治」「大正」「昭和」「平成」と続いているが、 これは通常の文字列とは性格が異なる。 文字列なら「太郎」や「花子」を代入してもよいが、それらは年号ではない。 かといって 「明治」「大正」「昭和」「平成」を0から3までの整数に割り当ててコーディングしたら 意味不明になる。 ある年が「大正」かどうかを判定するために、1かどうか判定する式を書くことになるが、 この1が「大正」であることは容易には読みとれない。 そこで、数値に名前を割り当ててコーディングしたりするわけだが、 C#では、このような目的に適する列挙型(enum)というデータ型が用意されている。 using System; { // C, C++ と同じ // Era という名前で、Meiji、Taisho、Showa、Heiseiという4つの名前を含む列挙型を定義する。 //  宣言された列挙型は、文字列というよりも数値のように振る舞うと考えておけば間違いない。 enum Era { Meiji, Taisho, Showa, Heisei, // 最後に [,] をいれても良い。 } class Class1 { static void WriteEra( Era t ) { switch( t ) { case Era.Meiji: Console.WriteLine("明治"); break; case Era.Taisho: Console.WriteLine("大正"); break; case Era.Showa: Console.WriteLine("昭和"); break; case Era.Heisei: Console.WriteLine("平成"); break; } } static void Main(string[] args) { Era t = Era.Taisho; WriteEra( t ); } } 列挙型の値“Era.Taisho”に対応した年号を文字列で表示している。 列挙型を Console.WriteLine する using System; namespace ConsoleApplication2 { enum Era { Meiji, Taisho, Showa, Heisei } class Class1 { static void Main(string[] args) { Era t = Era.Taisho; Console.WriteLine( t ); } } } 列挙型の値の名前そのものが出力された。 列挙型をConsole.WriteLineメソッドに直接渡すと、その名前そのものが出力される。 C++プログラマは、この点で挙動が異なることに注意していただきたい。 C++では、名前ではなく数値が出力される。 C++の列挙型は数値の別名にすぎないが、 C#の列挙型はもう少し多くの管理情報を実行時に持っていて、機能的に同じではない。 列挙型で漢字を使う using System; { //  C#はキーワードに漢字を使用できる。 enum Era { 明治, 大正, 昭和, 平成 } class Class1 { static void Main(string[] args) { Era t = Era.大正; Console.WriteLine( t ); } } } { enum Era { Meiji, Taisho, Showa, Heisei, } class Class1 { static void Main(string[] args) { Era t = Era.Taisho; Console.WriteLine( t ); } } }


数値として使う列挙型


POINT   列挙型の中身は数値である。 個々の要素に対応する数値を確かめるには以下のように記述する。
{ enum Era { Meiji, Taisho, Showa, Heisei } class Class1 { static void Main(string[] args) { Console.WriteLine( (int)Era.Meiji ); Console.WriteLine( (int)Era.Taisho ); Console.WriteLine( (int)Era.Showa ); Console.WriteLine( (int)Era.Heisei ); } } }
列挙型の値を数値にキャストして表示する。 最初の項目が0に割り当てられ、それ以降1ずつ値が増えているのが分かる。 列挙型の値は数値にキャストできる。 何も指定しない場合 列挙型はint型の数値で内部処理するので int型へ問題なくキャストできる。 最初の項目が0に割り当てられ、それ以降1ずつ値が増えていく。


内部処理用の数値型の変更


 何も指定しなければ、列挙型はint型で内容を処理する。 しかし、内容の処理に使われるデータ型は、必要なら指定して変更することもできる。 メモリの節約のためにより少ないビット数の数値型にすることもできるし、 表現力を高めるためにより多くのビット数からなる数値型にもできる 符号付きと符号なしも選べる。 { enum Era : byte { Meiji, Taisho, Showa, Heisei } class Class1 { static void Main(string[] args) { Console.WriteLine( (byte)Era.Meiji ); Console.WriteLine( (byte)Era.Taisho ); Console.WriteLine( (byte)Era.Showa ); Console.WriteLine( (byte)Era.Heisei ); } } } 列挙型の名前を書いた直後に使用したい数値型を記述する。 内部処理で使用される型をbyte型に指定している。 列挙型の名前を書いた直後に、コロン記号を置いて その後に使用したい数値型を書く。 これにより、Era型はbyte型の整数で内部処理される。


privateな列挙型


 列挙型の宣言には クラスと同じように private などのキーワードを付けて利用範囲を制限できる。
{ class Class2 { // Access 権をつけることも可能. private enum Era { Meiji, Taisho } public static void WriteMeiji() { Console.WriteLine( Class2.Era.Meiji ); } } class Class1 { enum Era { Showa, Heisei } static void Main(string[] args) { //Console.WriteLine( Class2.Era.Meiji ); //エラー 'ConsoleApplication7.Class2.Era' はアクセスできない保護レベルになっています。 Class2.WriteMeiji(); Console.WriteLine( Class1.Era.Showa ); } }
privateを付けた列挙型は、それを宣言したクラス内からのみ使用できる。  ここでポイントになるのは、2つのEra型が定義されていることだ。 Era型はClass2クラスの中で宣言され、 privateキーワードが付いているので、クラス外からは利用できない。 しかし同じクラス内からは問題なく利用することができる。 「エラー 'ConsoleApplication7.Class2.Era' はアクセスできない保護レベルになっています。」というエラーになり、コンパイルできない。


名前に任意の数値を与える


列挙型の個々の要素には 何も指定しなければ、0から順番に1ずつ大きくなる値が割り当てられる。 しかし必要なら、値を明示的に指定できる。
{ // 任意の値を割り当てることも可能. enum Sample { A, // 0 B, // 1 C = 10, D, // 11 E = B // 代入も可能. } class Class1 { static void Main(string[] args) { Console.WriteLine( (int)Sample.A ); Console.WriteLine( (int)Sample.B ); Console.WriteLine( (int)Sample.C ); Console.WriteLine( (int)Sample.D ); Console.WriteLine( (int)Sample.E ); } }
列挙型である Sample 型の要素CおよびEに値を割り当てている。 サンプル・プログラム8の実行結果 “10”を割り当てた“C”の次の要素“D”は“11”となっている。 何も指定していない7〜8行目のAとBは0と1になる。 しかし、Cは、明示的に“C = 10”としているので、 10という値を持つ Dは、 Cの次なので11になる。 E は、“E = B”として、Bと同じとしているため、Bの値、つまり1になる。  列挙型の値を変更するのは、数値に何か特別な意味があって、それと一致させたい場合である。 逆に数値に特別な意味がないのに、数値を明示的に指定すると予期せぬトラブルが起こる場合があるので、 必要性を見極めてから利用すること。


数値指定時のトラブル例


POINT Enmu はクラスであり, static string GetName() により名前を取得可能 static GetNames() によりすべての 文字を取得できる.  以下は、数値指定時に起こるトラブルの一例である。 内容としては、ただ単に列挙型の値をコンソールに出力しているだけで何も問題がないように見える。
{ // クラスの外に指定した場合は, public 属性らしい. enum Sample { A, B, C = 10, D, E = B } class Class1 { static void Main(string[] args) { Console.WriteLine( Sample.A ); Console.WriteLine( Sample.B ); Console.WriteLine( Sample.C ); Console.WriteLine( Sample.D ); Console.WriteLine( Sample.E ); } }
“B”が出力されると予想される場所に“E”が表示されている。 POINT Enum は内部では数値として格納される. 数値の割り当ては初心者は避けた方がいい. [1] <-> [B|E]  見て分かるとおり、本来 A〜E が順に出力されるはずが、Bの表示がEに化けている。 その原因は、“E = B”にある。列挙型が内部的に数値で処理されるため、 同じ数値に割り当てられた名前は区別できない。 つまり、Sample.Bを使ったのか、Sample.Eを使ったのかは 列挙型の値からは区別できない。 そこで、Sample.Bを指定しているのに、 同じ値を持つ E がコンソールに表示されてしまうという現象が起きるのである。 これを回避するには、[ 同じ値 <-> を別の名前 ]に割り当てなければよい。 数値を明示的に指定しなければ、自動的に同じ値の割り当ては回避されるので、 初心者は数値指定を避けた方がトラブルに巻き込まれる危険が減る。 POINT Enum は内部的に数値として処理される. そのため, 限定的に数値演算が可能.


列挙型の数値計算


 列挙型は数値ではないが、内部的に数値として処理される。 そのためすべてではないが、限定的な数値計算ができる。
{ // Enum enum Sample { A, B, C } class Class1 { static void Main(string[] args) { Sample a = Sample.A; // 列挙型に 1 を足すのはあり, ( 意味としては次にいく. ^ ^/ ) Sample r = a + 1; Sample b = Sample.B; // Enum 同市の 演算はできない. //Sample s = a + b; //演算子 '+' を 'Console10.Sample' と 'Console10.Sample' 型のオペランドに適用することはできません。 Console.WriteLine( r ); } } }
“A”に1を足したため、次の要素を示す“B”が表示されている。 Aが代入されている変数aに1を足すコードが記述されている。 このコードは正常に実行でき、結果はBとなる これは、5〜10行目の列挙型の定義で、Aの次がBであることによる結果である。 一方、 列挙型の値と列挙型の値を足し算するような計算はエラー扱いとなり コンパイルできない。 Aに1を足すと次の要素を示す、という計算に意味はあっても、 AとBを足す、という計算に意味はないからだ。


値の名前を得る


POINT C# の列挙型は System.Enum を継承したものとして, 自動で定義される. -> ie. Enum の Method を利用できる. ^ ^/  C#の列挙型は、すべて System.Enum クラスを継承したものとして自動的に定義される。 そのため、System.Enum クラスを処理するためのさまざまなメソッドがすべて利用できる。 例えば、列挙型のある値に対応する名前を得るには、以下のように記述できる。
{ // Enum もクラスらしい. enum Era { Meiji, Taisho, Showa, Heisei } class Class1 { static void Main(string[] args) { Era t = Era.Taisho; // 列挙型に対応する名前を取得する. ( Enum 型と値を指定する. ) string name = Era.GetName( Type.GetType("ConsoleApplication11.Era"), t ); Console.WriteLine( name );a } }
System.Enum クラスにある GetName メソッドを使用している。 GetName メソッドに列挙型の値を渡すと、それに対応している名前が文字列で返される。 GetName メソッドは、 System.Enum クラスの static なメソッドである。 第1引数に列挙型の型を、第2引数に判定したい値を渡すと、列挙型の名前を返してくれる。 つまり、Era.Taisho という値を GetName メソッドの第2引数に渡すと、 対応する文字列“Taisho”が返される。 列挙型のすべての名前を得る
{ enum Era { Meiji, Taisho, Showa, Heisei } class Class1 { static void Main(string[] args) { // すべてのの名前を取得することも可能. string [] names = Era.GetNames( Type.GetType("ConsoleApplication12.Era") ); foreach( string s in names ) { Console.WriteLine( s ); } } } }
System.Enum クラスにある GetNames メソッドを使用している。 列挙型である Era 型に含まれるすべての要素の名前が列挙される。 GetNames メソッドは、引数として与えられた列挙型に含まれるすべての名前を、 文字列の配列として返す。 これで列挙型に含まれるすべての名前がわかる。


列挙型のすべての数値を得る


名前ではなく、数値の一覧を取得する方法
{ enum Sample { A=1, B=20, C=300, D=4000 } class Class1 { static void Main(string[] args) { // GetValues() Method を利用する System.Array values = Sample.GetValues( Type.GetType("ConsoleApplication14.Sample") ); foreach( int n in values ) { Console.WriteLine( n ); } } }
System.Enumクラスにある GetValues メソッドを使用している。 列挙型に含まれる数値の一覧が表示される。  GetValues メソッドは、ある列挙型に含まれる数値の一覧表を配列として返す機能を持つ。 このメソッドに与える引数は GetNames メソッドのそれと同じ。 戻り値は System.Array 型だが、これはすべての配列のスーパー・クラスに当たるもので、 どのような配列を渡すのにも使用できるデータ型である。 列挙型はどんな数値型で表現するかをプログラマが指定できるので、 汎用的なデータ型で戻ってくる。 文字列から列挙型の値へ  外部との入出力に列挙型の名前を使う場合、 入力された文字列を列挙型の値に変換しなければならない。 そのために System.Enum クラスには、Parse メソッドが存在する。 このメソッドは、“Taisho”などの文字列を入力して、その文字列を解析し、 それが指定列挙型の Taisho であることを突き止めて、その値を返す。
{ enum Era { Meiji, Taisho, Showa, Heisei } class Class1 { static void Main(string[] args) { string target = "Taisho"; // 文字列から 数値へ Era x = (Era)Enum.Parse( Type.GetType("ConsoleApplication15.Era"), target ); Console.WriteLine( x ); Console.WriteLine( x.GetType().FullName ); } }
System.EnumクラスのParseメソッドを使用している。 得られた列挙型の値と、その列挙型のフルネームを表示する。 このメソッドの最初の引数は、列挙型の型である。 2番目の引数には、変換する文字列を指定する。 このメソッドから戻ってくる値は、汎用的なobject型なので “(Era)”のようにキャストする必要がある。 文字列への変換  列挙型の値から対応する名前を得る方法はすでに説明したが これとは少し違う方法もある。
{ enum Sample { Alpha, Bravo, Charlie=10, Delta=11 } class Class1 { static void Main(string[] args) { Sample sample = Sample.Delta; string name1 = sample.ToString(); Console.WriteLine( name1 ); string name2 = sample.ToString("G"); Console.WriteLine( name2 ); string name3 = sample.ToString("X"); Console.WriteLine( name3 ); string name4 = sample.ToString("D"); Console.WriteLine( name4 ); } }
ToStringメソッドでは、フォーマット文字列を指定することによって 値を16進数などの文字列でも表示できる。  列挙型の値にはToStringメソッドを利用できる。 ToStringメソッドはすべてのクラスが持つメソッドだが、 列挙型では、値に対応する名前を返すように実装されている。 さらに引数にフォーマット文字列を指定して、 複数のフォーマットで文字列に変換できる。 “G”を指定した場合は名前を返す。 “X”の場合は16進数の文字列として返す。 “D”の場合は10進数の文字列として返す。  このほか、ToStringメソッドにはフォーマット機能を持つインターフェイスを指定して 変換する機能などもある。 まとめ 列挙型 は、決まりきったいくつかの名前を扱うときに便利な機能である。 C++などに見られる単なる数値の別名にすぎない列挙型よりも強力であり、 これらの言語で文字列として処理していたものが、C#では列挙型で処理すべきものになるかもしれない。