第20章 実行時に参照可能な属性:連載 改訂版 C#入門(1/4 ページ)
C#では、コードに属性を埋め込むことにより、実行時に参照可能な情報を持たせることができる。Win32 API呼び出しやWebサービスの実装も属性を使う。
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。
属性は、言語仕様に含まれていない付加情報をクラスやメソッドに付加するために使われる。これにより、ソース・コードにさまざまな情報を埋め込むことが可能になり、ソース・コードの表現力が高まる。本章は属性の使い方を解説する。
20-1 属性(アトリビュート)とは何か?
通常、プログラムのソース・コードは、プログラミング言語の言語仕様の範囲内で記述する。しかし、しばしば、言語仕様の範囲内だけでは不自由であるという状況が発生し、言語仕様を拡張しようという動きが出てくる。例えば、Visual C++において、COMサポートのために言語仕様を拡張した例などがある。しかし、さまざまな企業が個々の事情で勝手に拡張しては、プログラミング言語に非互換性が生じてしまう。これに対処する1つの方法が、C#の属性である。属性は、クラスやメソッドに対して付加情報を付け加える機能を提供する。これにより、具体的な属性を自由に定義することができ、プログラミング言語の仕様を拡張することなく、独自の情報をクラスやメソッドに与えることができる。例えば、Webサービスを記述する際には、通常のメソッドとWebサービスで外部公開するメソッドを区別する必要があるが、そのために言語仕様を拡張するのではなく、属性を用いて対処するわけである(Webサービスの具体的なソース・コード例は後で紹介する)。
では、具体的に属性が使われている例を見てみよう。List 20-1は、デバッグ・ビルドの場合のみメッセージを表示するというサンプル・ソースである。前章で解説したプリプロセッサを用いても実現可能であるが、ここでは属性を用いた方法で行ってみる。
1: using System;
2: using System.Diagnostics;
3:
4: namespace Sample001
5: {
6: class Class1
7: {
8: [Conditional("DEBUG")]
9: private void test()
10: {
11: Console.WriteLine("test called");
12: }
13: [STAThread]
14: static void Main(string[] args)
15: {
16: Class1 instance = new Class1();
17: Console.WriteLine("call start");
18: instance.test();
19: Console.WriteLine("call end");
20: }
21: }
22: }
これを実行するとFig.20-1のようになる。
実行結果を見てのとおり、デバッグ・ビルドとリリース・ビルドでは結果が異なっている。その秘密は、8行目に記述されたConditional属性にある。Conditional属性は、引数で指定されたシンボル名が定義されている場合のみ、その属性が付加されたメソッド、つまり、Conditional属性の直後に書かれたメソッドを実行するようにシステムに指示する。そのため、シンボルDEBUGが定義されている場合のみ9〜12行目のメソッドtestは実行されるのである。
なお、Conditinal属性は、System.Diagnostics名前空間に属するので、2行目でこれをusing指定している。属性にも名前空間があることに注意されたい。
ここで使用したConditional属性は、C#の言語仕様の一部として定義されているもので、これを使うにあたって、名前空間に配慮する以外、特に何かを定義する必要はない。いきなりメソッドの手前に書けば利用できる。
20-2 属性を自作する
Conditional属性を始めはじめ、言語仕様やクラス・ライブラリ側で用意される属性も多いが、自作もできる。List 20-2は自分で新しい属性を定義して利用した例である。メソッドを書いたプログラマーの名前を明示的にソース・コードに書き込めるようにしたサンプル・ソースである。
1: using System;
2: using System.Reflection;
3:
4: namespace Sample002
5: {
6: [AttributeUsage(AttributeTargets.Method)]
7: class AuthorAttribute : Attribute
8: {
9: private string _name;
10: public AuthorAttribute( string name )
11: {
12: _name = name;
13: }
14: public string name
15: {
16: get { return _name; }
17: }
18: }
19: class Class1
20: {
21: [Author("Ichiro")]
22: public void test1()
23: {
24: }
25: [Author("Jiro")]
26: public void test2()
27: {
28: }
29: [Author("Saburo")]
30: public static void dumpAuthor( string methodName )
31: {
32: MethodInfo info = typeof(Class1).GetMethod(methodName);
33: object [] list = info.GetCustomAttributes(typeof(AuthorAttribute) ,false);
34: foreach( AuthorAttribute item in list )
35: {
36: Console.WriteLine( "method {0} is written by {1}", methodName, item.name );
37: }
38: }
39: [STAThread]
40: static void Main(string[] args)
41: {
42: dumpAuthor( "test1" );
43: dumpAuthor( "test2" );
44: dumpAuthor( "dumpAuthor" );
45: }
46: }
47: }
これを実行するとFig.20-2のようになる。
このソース・コードの大まかな構成から説明しよう。6〜18行目では新しい属性を定義している。21〜38行目は定義した属性を使ってプログラマーの名前を書き込んでみた例である。29〜38行目は、書き込まれた情報を表示するメソッドでもある。そして、39〜45行目はメイン・メソッドである。
まず、属性の定義から見ていこう。属性とは一種のクラスとして定義される。クラスを属性にするには、7行目のようにSystem.Attributeクラスを継承する。それに加えて、やるべきことが2つある。1つは、クラスの名前をAttributeで終わるようにすることと、6行目のようにAttributeUsage属性を付加して、それが何に付く属性であるかを示すことである。ここでは、AttributeTargets.Methodが指定されているが、これはメソッドに付く属性であることを指定している。AttributeTargetsは列挙型で、ほかにもクラスなどのバリエーションが定義されている。必要なものを選んで指定すればよい。|で区切って、[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class)]というように複数を並べて指定することもできる。
属性に引数を付けるには、属性クラスに引数を持ったコンストラクタを用意すればよい。ここでは、10行目で文字列を1つ引数に取るコンストラクタを定義している。これにより、この属性は1つの文字列を引数に取るようになる。
さて、こうして定義した属性は、21、25、29行目のように記述する。6行目でメソッドをターゲットに指定しているので、メソッドの先頭以外に記述するとエラーになる。そして、1個の文字列引数を取るコンストラクタが用意されているので、それぞれの属性には引数として文字列が記述されている。
もう1つ注意してほしいことがある。属性クラスの名前はAuthorAttributeであったが、実際に記述されている属性名はAuthorであり、AuthorAttributeではない。通常、属性クラスを定義するときはAttributeを付けた名前で行うが、利用する場合は付けない名前で記述する。このルールは、頭に入れておく価値がある。なぜなら、ある属性の仕様を知りたくなったとき、その属性名にAttributeの文字を付け加えた名前のクラスをリファレンス・マニュアルから探せばよいからだ。
もう1つ超えるべき山場がある。それは、記述した属性の参照方法だ。属性で記述した情報は実行時に参照できる。といっても、属性はフィールドやメソッドではないので、普通の方法では参照することができない。これを参照するには、リフレクションという機能を使わなければならない。本連載は、言語仕様を中心に解説し、クラス・ライブラリには深く立ち入らない方針なので、リフレクションの詳細には踏み込まない。しかし、属性を活用する上うえで最低限必要な知識は解説したい。
リフレクションは、実行時に実行中のプログラムに関する情報にアクセスする機能である。例えば、自分自身のプログラムの中に、どんなクラスやメソッドがあるかを調べることができる。この機能の一部として、クラスやメソッドにどんな属性が付いているかを調べる機能も用意されている。
まず、32行目に注目していただきたい。ここでは、typeof演算子で、クラスClass1に関する情報を持ったSystem.Type型のインスタンスを取得する。このインスタンスはGetMethodメソッドを持つ。これは、指定されたクラスの持つpublicなメソッドについて情報を取得する機能を持つ。ここでは引数で指定した名前のメソッドについての情報を取得している。このメソッドが返す値のデータ型は32行目の先頭に書かれたMethodInfoクラスである。このクラスには、メソッドについてのさまざまな情報が含まれている。ここで知りたいのは、属性についての情報なのだが、本章で取り上げる属性を取得するには、33行目のようにGetCustomAttributesというメソッドを呼び出せばよい。このメソッドには2つの引数を指定する。1つは、取得した属性クラスのデータ型を示すSystem.Type型の値である。ここでは、typeof演算子でそれを得ている。もう1つは、属性クラスが継承されたクラスかどうかを指定するものだが、ここでは継承関係が絡み合うような使い方はしていないので、シンプルにfalse(継承されていない)を指定している。さて、このメソッドの戻り値は、object型の配列だが、実際の中身はAuthorAttribute型のインスタンスの配列である。つまり、配列の個々の要素をAuthorAttributeにキャストしてやれば、内容にアクセス可能である。36行目を見て分かるとおり、AuthorAttributeクラスのnameプロパティを参照している。その結果、コンソールに、個々のメソッドを記述したプログラマーの名前が表示されるわけである。Aboutボックスに、すべての開発者の名前をリストしたい場合などに利用できる機能かもしれない。
なお、最後になったが、リフレクションの機能はSystem.Reflection名前空間にあるので、2行目でこれをusingしている。
Copyright© Digital Advantage Corp. All Rights Reserved.