イベントやコールバック処理の記述をシンプルにしてくれるC#のデリゲート。クラス・ライブラリを活用するには必須の機能なので、ぜひマスターしておこう。
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。
何かの処理を行う際に、メソッドなどを直接呼び出すのではなく、間接的な「デリゲート」と呼ばれる機構を経由して呼び出すことができる。デリゲートはイベント処理や、スレッドとして実行すべきメソッドを指定するために使用される。目新しい奇異な機能と感じるプログラマーも少なくないと思われるので、本章ではデリゲートを詳しく説明したい。
デリゲートは日本語では「委譲」と訳されることが多い。委譲という言葉が分かりにくいと思うなら、代表者と考えてもよい。何かの処理を実行させたいときに、処理機能を持つメソッドを直接呼び出すのではなく、代表者に処理を求めるのである。代表者は処理機能は持っていないが、それを処理できる適切なメソッドを知っており、そのメソッドに処理要求を渡す。
最も単純な状況を考えてみよう。クラスAのメソッドaは常にクラスBのメソッドbに処理を委ねるとしよう。このような場合、メソッドaの中にメソッドbを呼び出すコードを書けばよいので、特別な機能は何も必要がない。しかし、常に委ねる相手が決まっているとは限らないし、委ねる相手が常に1つとも限らない。このような状況に対処するために、C#のデリゲート機能が存在するわけである。
最初に注意点を2つ述べる。デリゲートの機能は後の章で説明するイベントの機能によく似ている。実際に、イベント機能はデリゲート機能を利用して作られている。しかし、デリゲートとイベントは想定される使われ方が違っており、機能面でも相違がある。どちらか一方だけ覚えて、他方は使わないという方法はうまくいかないだろう。もう1つの注意点は、C/C++プログラマー向けのものである。C#言語の仕様のデリゲートの章を読むと、いきなり、「デリゲートを使用すると、C++、Pascal、Modulaなどのほかの言語が関数ポインタで扱うシナリオを処理できるようになります。」という説明から入るが、そこで、デリゲートは関数ポインタの一種だと理解すると危険である。関数ポインタと似ているのは、役割であって、機能ではない。例えば、C言語では関数ポインタを使っていた場面で、C#の場合はデリゲートを使うかもしれないが、具体的な機能はまったく違うということである。
さて、List 13-1はデリゲート経由でメソッドを呼び出すサンプル・ソースである。
1: using System;
2:
3: namespace Sample001
4: {
5: delegate int Sample( int x, int y );
6: class Class2
7: {
8: public int method( int x, int y )
9: {
10: return x*y;
11: }
12: }
13: class Class1
14: {
15: [STAThread]
16: static void Main(string[] args)
17: {
18: Class2 instance = new Class2();
19: Sample sample = new Sample(instance.method);
20: int result = sample( 2, 3 );
21: Console.WriteLine( result );
22: }
23: }
24: }
これを実行するとFig.13-1のようになる。
このソース・コードのポイントは、20行目に記述したsampleというデリゲート呼び出しによって、実際には8〜10行目のmethodというメソッドが呼び出されているということである。つまり、sampleが代表者で、実際の処理をmethodメソッドに委譲しているということである。
では、具体的なコーディングについて説明しよう。まず、5行目のdelegateキーワードが目に付くと思う。delegateキーワードから書き始めた宣言は、デリゲートのデータ型を規定する。この後述べるように、デリゲートはさまざまなメソッドを呼び出すために使用できるが、あくまで同じ戻り値、同じ引数のメソッドに限られる。そこで、どのような戻り値、どのような引数のメソッドを対象とするかを、ここで明確にしておくわけである。ここでは、戻り値としてdelegateキーワードの次のintが指定されている。引数は括弧内に記述されたとおり、整数値が2つ必要となる。最後に、Sampleというキーワードが残ったが、これがデータ型の名前となる。これ以後、Sampleというデータ型を記述することで、この戻り値と引数の条件を満たすデリゲートを示すことができる。逆にいえば、名前さえ変えれば、戻り値や引数の異なるデータ型をいくらでも定義して利用することも可能である。なお、このデリゲートの宣言には、publicやprivateなどのキーワードを付けることもできる。
次に見ていただきたいのは、19行目である。ここでは、デリゲートのインスタンスを作成している。通常のクラスのインスタンスを生成するのと、手順はまったく同じである。コンストラクタの引数に記述された「instance.method」とは、instanceというインスタンスのmethodというメソッドに処理を委譲したい、ということを意味している。ここを変えれば、ほかのインスタンスのほかのメソッドに委譲先を変更できる。
最後に見ていただきたいのが、20行目である。パッと見ると、普通のメソッド呼び出しと何も変わらないように見える。しかし、実際には処理は委譲されているわけである。一見、何の変哲もないメソッドのように見えるところが、C#のデリゲートのポイントである。
List 13-1を見ても、デリゲートの利用価値はいま1つ分からないかもしれない。そこで、デリゲートを使って異なる処理を一元化する例をList 13-2に示そう。
1: using System;
2:
3: namespace Sample002
4: {
5: delegate int Sample( int x, int y );
6: class Class2
7: {
8: public int methodMult( int x, int y )
9: {
10: return x*y;
11: }
12: public int methodPlus( int x, int y )
13: {
14: return x+y;
15: }
16: }
17: class Class1
18: {
19: public static void calc( int x, int y, Sample calcMethod )
20: {
21: int result = calcMethod( x, y );
22: Console.WriteLine( result );
23: }
24:
25: [STAThread]
26: static void Main(string[] args)
27: {
28: Class2 instance = new Class2();
29: calc( 2, 3, new Sample( instance.methodMult ) );
30: calc( 2, 3, new Sample( instance.methodPlus ) );
31: }
32: }
33: }
これを実行するとFig.13-2のようになる。
List 13-2では、19〜23行目のcalcメソッドで、さまざまな計算を実行させることを意図している。ここでは掛け算と足し算をメソッドとして用意している。計算方法の変更は、呼び出すメソッドを変えるだけで実現できるのは明らかだ。8〜11行目のmethodMultメソッドを呼び出せば掛け算になり、12〜15行目のmethodPlusメソッドを呼び出せば足し算になる。そこで、calcメソッドは第3引数として、デリゲートのインスタンスを受け取るようになっている。このデリゲートのインスタンスの委譲先として指定するメソッドを入れ替えれば、計算内容も変わるというわけだ。
実際に29行目では、methodMultメソッドに委譲するデリゲート・インスタンスを作成して渡している。また30行目では、同様にmethodPlusメソッドを使っている。これにより、21行目で行われる計算内容は(より正確には21行目から呼び出されるメソッドは)、29行目から呼ばれたときと30行目から呼ばれたときでは異なることになる。
Copyright© Digital Advantage Corp. All Rights Reserved.