連載

C#入門

第17回 処理を委譲するdelegate

(株)ピーデー
川俣 晶
2001/12/07


delegateとは何か

 「delegate」は日本語では「委譲」と訳されることが多い。委譲という言葉が分かりにくいと思うなら、「代表者」と考えてもよい。何かの処理を実行させたいときに、直接処理機能を持つメソッドを呼び出すのではなく、代表者に処理を求めるのである。代表者は処理機能自体は持っていないが、それを処理できる適切なメソッドを知っており、そのメソッドに処理要求を渡す。

 最も単純な状況を考えてみよう。「クラスA」の「メソッドa」は自分では何も処理せず、常に「クラスB」の「メソッドb」に処理をゆだねるとしよう。このような場合、「メソッドa」の中に「メソッドb」を呼び出すコードを書けばよいので、特別な機能は何も必要がない。しかし、このように常に処理をゆだねる相手が決まっているとは限らないし、相手が常に1つとも限らない。このような状況に対処するために、C#のdelegate機能が存在するわけである。

 最初に注意点を2つ述べる。delegateの機能はeventの機能によく似ている(event機能については「第13回 言語に内蔵されたイベント機能」を参照)。実際に、event機能はdelegate機能を利用して作られている。しかし、delegateとeventは想定される使われ方が違っており、機能面でも相違がある。delegateは処理を他にゆだねるものだが、eventはクラス内の出来事を外部に伝えるものである。どちらか一方だけ覚えて、他方は使わないという方法はうまく行かないだろう。もう1つの注意点は、C/C++プログラマ向けのものである。「C# Language Specification」のdelegateの章を読むと、いきなりdelegateは「関数ポインタ」のようなもの、という説明から入るが、そこでdelegateは関数ポインタと同じだと理解すると危険である。関数ポインタと似ているのは役割であって、関数ポインタの機能とdelegateの機能が同じというわけではない。例えば、C言語で関数ポインタを使っていた場面で、C#ではdelegateを使うかもしれないが、具体的な機能はまったく違うということである。

 さて、以下はdelegate経由でメソッドを呼び出すサンプル・ソースである。

 1: using System;
 2:
 3: namespace ConsoleApplication20
 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:     static void Main(string[] args)
16:     {
17:       Class2 instance = new Class2();
18:       Sample sample = new Sample(instance.method);
19:       int result = sample( 2, 3 );
20:       Console.WriteLine( result );
21:     }
22:   }
23: }
delegate経由でメソッドを呼び出すサンプル・プログラム1
5行目でdelegateとなるデータ型を宣言し、18行目でそのインスタンスを生成している。

 これを実行すると以下のようになる。

サンプル・プログラム1の実行結果
delegateを介して、実際にはメソッド“method”が呼び出される。

 このソースコードのポイントは、19行目に記述したsampleというdelegate呼び出しによって、実際には8〜11行目のmethodというメソッドが呼び出されているということである。つまり、sampleが代表者で、実際の処理をメソッド“method”に委譲しているということだ。

 では具体的なコーディングについて説明しよう。まず、5行目のdelegateキーワードが目に付くと思う。delegateキーワードから書き始めた宣言は、delegateのデータ型を規定する。この後で述べるように、delegateはさまざまなメソッドを呼び出すために使用できるが、呼び出せるのは、あくまで同じ戻り値、同じ引数のメソッドに限られる。そこで、どのような戻り値、どのような引数のメソッドを対象とするかを、ここで明確にしておくわけである。ここでは、戻り値としてdelegateキーワードの次のintが指定されている。引数はカッコ内に記述されたとおり、整数値が2つ必要となる。最後に、Sampleというキーワードが残ったが、これがデータ型の名前となる。これ以後、Sampleというデータ型を記述することで、この戻り値と引数の条件を満たすdelegateを示すことができる。逆にいえば、名前さえ変えれば、戻り値や引数の異なるデータ型をいくらでも定義して利用することも可能である。なお、このdelegateの宣言には、publicやprivateなどのキーワードも付けることもできる。

 次に見ていただきたいのは、18行目である。ここでは、delegateのインスタンスを作成している。通常のクラスのインスタンスを生成するのと手順はまったく同じである。コンストラクタの引数に記述されたinstance.methodとは、instanceというインスタンスのmethodというメソッドに処理を委譲したい、ということを意味している。ここを変えれば、他のインスタンスの他のメソッドに移譲先を変更できる。

 最後に注目していただきたいのが、19行目である。パッと見ると、普通のメソッド呼び出しと何も変わらないように見える。しかし、実際には処理は委譲されている。一見、何の変哲もないメソッド呼び出しのように見えるところが、C#のdelegateのポイントである。

名前の異なるメソッドに委譲する

 上記のサンプルを見ても、delegateの利用価値はいまひとつ分からないかもしれない。そこで、delegateを使って異なる処理を一元化する例を以下に示そう。

 1: using System;
 2:
 3: namespace ConsoleApplication21
 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:     static void Main(string[] args)
25:     {
26:       Class2 instance = new Class2();
27:       calc( 2, 3, new Sample( instance.methodMult ) );
28:       calc( 2, 3, new Sample( instance.methodPlus ) );
29:     }
30:   }
31: }
異なる処理を一元化しているサンプル・プログラム2
掛け算と足し算を行う2つのメソッドの呼び出しをcalcメソッドで一元化して扱っている。calcメソッドでは引数で渡されたdelegateを呼び出すだけである。

 これを実行すると以下のようになる。

サンプル・プログラム2の実行結果
calcメソッドでは、delegateであるcalcMethodを呼び出しているだけだが、移譲先が異なるため実行結果も異なる。

 このサンプル・ソースで意図しているのは、19〜23行目のcalcメソッドで、さまざまな計算を実行させたい、ということである。ここでは掛け算と足し算をメソッドとして用意している。計算方法の変更は、呼び出すメソッドを変えるだけで実現できるのは明らかだ。8〜11行目のmethodMultメソッドを呼び出せばかけ算になり、12〜15行目のmethodPlusメソッドを呼び出せば足し算になる。そこで、calcメソッドは第3引数として、delegateのインスタンスを受け取るようになっている。このdelegateのインスタンスの移譲先として指定するメソッドを入れ替えれば、計算内容も変わるというわけだ。

 実際に27行目では、methodMultメソッドに委譲するdelegateインスタンスを作成して渡している。また28行目では、同様にmethodPlusメソッドを使っている。これにより、21行目で行われる計算内容は(より正確には21行目から呼び出されるメソッドは)、27行目から呼ばれたときと、28行目から呼ばれたときでは異なることになる。

 

 INDEX
  第17回 処理を委譲するdelegate
  1.delegateとは何か
    2.staticなメソッドへの委譲
    3.戻り値と引数が合えば委譲できる
    4.delegateをオブジェクトとして使う
    5.移譲先リストの変更

「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間