異なるクラスのメソッドを1つのメソッド呼び出しで扱うというのは何もデリゲートだけが持つ機能ではない。クラスの継承やインターフェイスの機能を使っても実現できる。List 13-6は継承を使って実現してみた例である。これをデリゲートを使った場合と比較して考えてみよう。
1: using System;
2:
3: namespace Sample006
4: {
5: abstract class ClassBase
6: {
7: public abstract int method( int x, int y );
8: }
9: class ClassMult : ClassBase
10: {
11: public override int method( int x, int y )
12: {
13: return x*y;
14: }
15: }
16: class ClassPlus : ClassBase
17: {
18: public override int method( int x, int y )
19: {
20: return x+y;
21: }
22: }
23: class Class1
24: {
25: public static void calc( int x, int y, ClassBase instance )
26: {
27: int result = instance.method(x,y);
28: Console.WriteLine( result );
29: }
30: [STAThread]
31: static void Main(string[] args)
32: {
33: ClassMult instance1 = new ClassMult();
34: ClassPlus instance2 = new ClassPlus();
35: calc( 2, 3, instance1 );
36: calc( 2, 3, instance2 );
37: }
38: }
39: }
これを実行するとFig.13-6のようになる。
List 13-6は、27行目のメソッド呼び出しによって、掛け算をしたり足し算をしたりする。機能面では、これまで説明したサンプル・ソースと似ているが、コーディングにはいくつもの違いが見られる。まず、継承を使う方法ではメソッド名を統一しておく必要がある。また、あらかじめ、基底クラス(ClassBase)でabstractなメソッド宣言をしておき、機能をオーバーライドする場合はoverrideキーワードを付けてメソッドを記述する必要がある。戻り値と引数が合っていれば使用できるデリゲートと比較すれば、制約は大きいといえる。さらに、27行目を見て分かるとおり、具体的に呼び出すインスタンスとメソッドが直接記述されていても同様であり、代表者としての役割を果たしているとはいいがたい。
このようにデリゲートはデリゲートという1つの機能であって、継承やインターフェイスとは異なる特徴を持っている。これらは、適切に使い分けられるべきものなのである。
デリゲートのインスタンスも、当然ながら一種のインスタンスである。これらは、System.DelegateクラスやSystem.MulticastDelegateクラスを継承しており、これらのクラスのメソッドやプロパティを呼び出すことができる。List 13-7はその機能を用いて、デリゲート・インスタンスが指し示しているメソッドとインスタンスの属するクラス名を出力するようにした例である。
1: using System;
2:
3: namespace Sample007
4: {
5: delegate void Sample();
6: class Class1
7: {
8: public void method()
9: {
10: Console.WriteLine( "Hello!" );
11: }
12: [STAThread]
13: static void Main(string[] args)
14: {
15: Class1 instance = new Class1();
16: Sample sample = new Sample( instance.method );
17: Console.WriteLine( sample.Method.Name );
18: Console.WriteLine( sample.Target.GetType().FullName );
19: }
20: }
21: }
これを実行するとFig.13-7のようになる。
17行目のsample.Method.Nameは、デリゲートが持つメソッドに関する情報をMethodプロパティを通じて取り出し、さらにメソッドの名前を持つNameプロパティを読み出している。18行目のsample.Target.GetType().FullNameは、まず、デリゲート・インスタンスが対象とするインスタンスをTargetプロパティで取り出し、それに対してGetType().FullNameを適用することで、クラス名を取り出している。
デリゲートを用いてアクセス権限のないメソッドへ委譲することはできないが、アクセス権限のないメソッドを呼び出せないわけではない。デリゲート・インスタンスを作成する時点で、呼び出すべきメソッドへのアクセス権限があればよいのであって、メソッド呼び出しそのものを行うときにはアクセス権限がなくてもよい。実際に、アクセス権限のないメソッド呼び出しが、デリゲートを経由することで可能になっている例をList 13-8に示す。
1: using System;
2:
3: namespace Sample008
4: {
5: delegate void Sample( string message );
6: class Class2
7: {
8: private void outputMessage( string message )
9: {
10: Console.WriteLine( message );
11: }
12: public Sample getDelagate()
13: {
14: return new Sample( this.outputMessage );
15: }
16: }
17: class Class1
18: {
19: [STAThread]
20: static void Main(string[] args)
21: {
22: Class2 instance = new Class2();
23: Sample sample = instance.getDelagate();
24: sample( "Hello!" );
25: }
26: }
27: }
これを実行するとFig.13-8のようになる。
23行目に、8〜11行目のoutputMessageメソッドへの呼び出しを記述した場合、コンパイル・エラーになるのは当たり前である。しかし、このサンプル・ソースは正常に動作する。そのポイントは、デリゲート・インスタンスを作成するとき(14行目)には、outputMessageメソッドへのアクセス権がある、という点にある。もし、14行目をClass1クラス内に移動させたら、このソースはコンパイルできなくなる。
Copyright© Digital Advantage Corp. All Rights Reserved.