第20章 実行時に参照可能な属性:連載 改訂版 C#入門(2/4 ページ)
C#では、コードに属性を埋め込むことにより、実行時に参照可能な情報を持たせることができる。Win32 API呼び出しやWebサービスの実装も属性を使う。
20-3 クラスに付く属性
これまでの例は、すべてメソッドに付く属性であったが、もちろん、クラスや引数や戻り値などに付けることもできる。List 20-3はクラスに付けてみた例である。メソッドではなく、クラスごとにプログラマーの名前を記述するように変更してある。
1: using System;
2: using System.Reflection;
3:
4: namespace Sample003
5: {
6: [AttributeUsage(AttributeTargets.Class)]
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: [Author("Ichiro")]
20: class Sample1
21: {
22: }
23: [Author("Jiro")]
24: class Sample2
25: {
26: }
27: [Author("Saburo")]
28: class Class1
29: {
30: public static void dumpAuthor( string className )
31: {
32: Type type = Type.GetType("Sample003." + className);
33: object [] list = type.GetCustomAttributes(typeof(AuthorAttribute),false);
34: foreach( AuthorAttribute item in list )
35: {
36: Console.WriteLine( "class {0} is written by {1}", className, item.name );
37: }
38: }
39: [STAThread]
40: static void Main(string[] args)
41: {
42: dumpAuthor( "Sample1" );
43: dumpAuthor( "Sample2" );
44: dumpAuthor( "Class1" );
45: }
46: }
47: }
これを実行するとFig.20-3のようになる。
List 20-3のポイントは、6行目のAttributeUsage属性の引数をAttributeTargets.MethodからAttributeTargets.Classへ変更したことである。これにより、メソッドではなく、クラスに付く属性に機能が変化したのである。その結果、19、23、27行目の属性は、それぞれ、メソッドではなく、クラスの先頭に付くようになっている。
さて、もう1つのポイントは、リフレクションの機能によりクラスに属する属性を取得する方法である。32〜33行目を見ていただくと分かると思うが、それほど複雑なものではない。System.TypeクラスのstaticなメソッドであるGetTypeメソッドにクラス名を渡せば、そのクラスに関する情報を持つSystem.Type型のインスタンスが得られる。それにGetCustomAttributesメソッドが含まれるので、そのメソッドを呼び出せば属性の情報が得られる。後の処理はメソッドの場合と同じである。ただし、注意が必要なのは、GetTypeメソッドを呼び出す場合は、名前空間を含むクラスのフルネームを指定しなければならないことである。ほかの例ではコンパイル時にクラスが確定されているが、GetTypeメソッドは実行時にクラスを検索して処理するという都合によるものだ。
20-4 複数の属性を持たせる
List 20-3の例では、クラスを記述したプログラマーの名前をソース・コードに埋め込めるようにしているが、実用には1つの難点がある。それは、クラスは常に1人で記述されるわけではないのに名前を1つしか書けない点だ。これに対処するには、同じ属性を複数指定可能にすればよい。実際に、それを記述してみた例がList 20-4である。
1: using System;
2: using System.Reflection;
3:
4: namespace Sample004
5: {
6: [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
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: [Author("Ichiro")]
20: class Sample1
21: {
22: }
23: [Author("Jiro"), Author("Saburo")]
24: class Sample2
25: {
26: }
27: [Author("Saburo")]
28: class Class1
29: {
30: public static void dumpAuthor( string className )
31: {
32: Type type = Type.GetType("Sample004." + className);
33: object [] list = type.GetCustomAttributes(typeof(AuthorAttribute),false);
34: Console.WriteLine( "class {0} is written by {1} member(s)", className, list.Length );
35: foreach( AuthorAttribute item in list )
36: {
37: Console.WriteLine( " class {0} is written by {1}", className, item.name );
38: }
39: }
40: [STAThread]
41: static void Main(string[] args)
42: {
43: dumpAuthor( "Sample1" );
44: dumpAuthor( "Sample2" );
45: dumpAuthor( "Class1" );
46: }
47: }
48: }
これを実行するとFig.20-4のようになる。
ここで注目すべきは、23行目である。カンマで区切って、Author属性が2回連続して記述されている。これにより、クラスSample2には、2つのAuthor属性が付加される。
これを許すために、6行目のAttributeUsage属性の引数に、「AllowMultiple = true」という指定が追加されている。AllowMultipleは同じ属性を複数書いてよいかどうかを指定するプロパティで、trueなら複数を許すことを示す。指定しなければfalseが指定されたものと見なされる。
ところで、重要なことではないが、このサンプル・ソースになって、35〜38行目のforeachループの繰り返しの意義がはじめて生まれたことになる。これまでの例では、同じ名前の属性は1個しか記述できないので、繰り返し処理する意味はなかった。しかし、複数指定を許すと、複数のインスタンスを処理する場合があるため、繰り返す必要が出てくる。
20-5 名前付きパラメータ
List 20-4では「AllowMultiple = true」という一風変わった引数を使用した。このような引数はコンストラクタの引数としては用意できない形式である。自作の属性でこのような引数を実現するには、コンストラクタの引数とは違う方法を使わなければならない。コンストラクタの引数は位置指定パラメータ(Positional Parameter)、「AllowMultiple = true」のような方法は名前付きパラメータ(Named Parameter)という。List 20-5は名前付きパラメータを使用したサンプル・ソースである。
1: using System;
2: using System.Reflection;
3:
4: namespace Sample005
5: {
6: [AttributeUsage(AttributeTargets.Class)]
7: class AuthorAttribute : Attribute
8: {
9: private string _name;
10: private string _organization;
11: public AuthorAttribute( string name )
12: {
13: _name = name;
14: _organization = "(no organization)";
15: }
16: public string name
17: {
18: get { return _name; }
19: }
20: public string organization
21: {
22: get { return _organization; }
23: set { _organization = value; }
24: }
25: }
26: [Author("Ichiro", organization="The Program Company")]
27: class Sample1
28: {
29: }
30: [Author("Jiro")]
31: class Sample2
32: {
33: }
34: [Author("Saburo", organization="The Software Group")]
35: class Class1
36: {
37: public static void dumpAuthor( string className )
38: {
39: Type type = Type.GetType("Sample005." + className);
40: object [] list = type.GetCustomAttributes(typeof(AuthorAttribute),false);
41: foreach( AuthorAttribute item in list )
42: {
43: Console.WriteLine( "class {0} is written by {1} in {2}.", className, item.name, item.organization );
44: }
45: }
46: [STAThread]
47: static void Main(string[] args)
48: {
49: dumpAuthor( "Sample1" );
50: dumpAuthor( "Sample2" );
51: dumpAuthor( "Class1" );
52: }
53: }
54: }
これを実行するとFig.20-5のようになる。
ここでは、Author属性に、新しく所属組織(organization)という項目を付け加えてみた。26、34行目では「organization="組織名"」という書式で所属組織の名前が指定されている。しかし、30行目では指定されていない。名前付きパラメータは、省略も自由にできることを意味している。さて、一見、このソースには、名前付きパラメータを処理するコードがどこにもないように見えるかもしれない。実は、organizationという名前の名前付きパラメータをサポートするには、organizationという名前のpublicなプロパティを用意するだけでよいのである。つまり、20〜24行目のプロパティの存在が、organizationという名前付きパラメータの利用を可能としたわけである。そこからすぐに分かるとおり、名前付きパラメータが指定されなかった場合のデフォルト値は、そのクラス内で自由に用意することができる。ここでは14行目が、それを用意している。
Copyright© Digital Advantage Corp. All Rights Reserved.