デリゲート(delegate)とは?
関数ポインタや関数オブジェクトをオブジェクト指向に適するように拡張したもの。
用途は、関数ポインタとほぼ同じ。ただし、インスタンスメソッドを参照したり、 複数のメソッドを同時に参照する事が出来る。
<ポイント>
- C# では、メソッドも他の型と同じように扱えます(変数に代入して使ったり、他のメソッドの引数や戻り値にしたりできる)。
- デリゲート: メソッドを代入するための変数の型。
- 例: delegate int DelegateName(int x, int y);
なぜアドレスで呼び出すのか?
●アドレスを使うと、目的のインスタンスのメソッドを確実に渡すことが出来るから。
●流れは・・・。
メインスレッドは、別スレッドからのコールバックが欲しい。
そのためには、別スレッドへここへ通知しなさいという情報を渡す必要がある。
このために使われるのが、通知すべきメソッドのアドレスとなる。
●メソッドの名前を渡す方法も考えられるが、以下の問題がある。
- 別スレッドを作成したメインスレッドも複数のインスタンスを持っている可能性が有る。
- 名前を渡す場合は何らかの方法でインスタンスを区別しなければならない。
- アドレスは複数のインスタンスが有ってもインスタンス毎に異なる。
デリゲートを使用するためには?
①デリゲート型を定義します。
delegate 戻り値の型 デリゲート型名(引数リスト);
ユーザ定義のクラスや構造体と同じ1つの“型”として扱われる。
デリゲート型は自動的に System.Delegate クラスの派生クラスになる。
C#1.1までは、newが必要だった。
例 SomeDelegate a = new SomeDelegate(A);
static void A(int n)
{
}
C#2.0からは、メソッドからデリゲートへの暗黙の変換が出来るようになった。
例 SomeDelegate a = A;
static void A(int n)
{
}
デリゲートの機能
①メソッドを参照し、 間接的なメソッド呼び出しを行うことができる。
②インスタンスメソッドの代入
→デリゲートにはクラス(static)メソッドとインスタンス(非static)メソッドのどちらでも代入する事が出来る。
③複数のメソッドを代入
using System;
/// <summary>
/// メッセージを表示するだけのデリゲート
/// </summary>
delegate void ShowMessage();
class Person
{
string name;
public Person(string name){this.name = name;}
public void ShowName(){Console.Write("名前: {0}\n", this.name);}
};
class DelegateTest
{
static void Main()
{
Person p = new Person("鬼丸美輝");
// インスタンスメソッドを代入。
ShowMessage show = new ShowMessage(p.ShowName);
show();
}
}
結果:名前: 鬼丸美輝
④複数のメソッドを代入(マルチキャストデリゲート)
デリゲートには += 演算子を用いることで、複数のメソッドを代入する事が出来る。
複数のメソッドを代入した状態で、デリゲート呼び出しを行うと、代入した全てのメソッドが呼び出される。
using System;
/// <summary>
/// メッセージを表示するだけのデリゲート
/// </summary>
delegate void ShowMessage();
class DelegateTest
{
static void Main()
{
ShowMessage a = new ShowMessage(A);
a += new ShowMessage(B);
a += new ShowMessage(C);
a();
}
static void A(){Console.Write("A が呼ばれました。\n");}
static void B(){Console.Write("B が呼ばれました。\n");}
static void C(){Console.Write("C が呼ばれました。\n");}
}
結果:
A が呼ばれました。
B が呼ばれました。
C が呼ばれました。
⑤クラスメソッドとインスタンスメソッドを混ぜて、複数のメソッドを代入することも出来る。
⑥デリゲート呼び出しは非同期にできる。
デリゲート型を定義すると、 C# コンパイラによって自動的に BeginInvoke と EndInvoke というメソッドが生成される。
BeginInvoke を用いることにより非同期呼び出しを開始し、EndInvoke を用いることにより非同期処理の終了を待つ事が出来る。
<BeginInvoke>
デリゲート型の定義時に引数リストで指定した引数と、System.AsyncCallback デリゲート型の引数および object 型の引数をとり、System.IAsyncResult インターフェース型の値を返します。
→IAsyncResult BeginInvoke(int n, ref int p, out int q, AsyncCallback callback, object state);
<EndInvoke>
デリゲート型の定義時に ref または out キーワードを付けた引数および System.IAsyncResult インターフェース型の引数を持ち、デリゲートの戻り値と同じ型の戻り値を持ちます。
例えば、delegate int ShowMessage(int n, ref int p, out int q); というデリゲート型を定義した場合、以下のようなメソッド定義になります。
→int EndInvoke(ref int p, out int q, IAsyncResult ar);
using System;
using System.Threading;
namespace A
{
/// <summary>
/// メッセージを表示するだけのデリゲート
/// </summary>
public delegate void ShowMessage(int n);
public class DelegateTest
{
static void Main()
{
const int N = 6;
ShowMessage asyncCall = new ShowMessage(AsynchronousMethod);
// asyncCall を非同期で呼び出す。
IAsyncResult ar = asyncCall.BeginInvoke(N, null, null);
// ↓この部分は asyncCall によって呼び出されるメソッドと同時に実行されます。
for(int i=0; i<N; ++i)
{
Thread.Sleep(600);
Console.Write("Main ({0})\n", i);
}
// asyncCall の処理が終わるのを待つ。
asyncCall.EndInvoke(ar);
Console.Write(" 処理完了\n");
}
static void AsynchronousMethod(int n)
{
for(int i=0; i<n; ++i)
{
Thread.Sleep(1000);
Console.Write("AsynchronousMethod ({0})\n", i);
}
}
static void B(IAsyncResult ar){}
}
}
●結果:
Main (0)
AsynchronousMethod (0)
Main (1)
Main (2)
AsynchronousMethod (1)
Main (3)
Main (4)
AsynchronousMethod (2)
Main (5)
AsynchronousMethod (3)
AsynchronousMethod (4)
AsynchronousMethod (5)
処理完了
<匿名メソッド> ※C#2.0から導入
●C# 1.1
デリゲートを使う際には、まず最初にどこかでメソッドを定義し、 その定義したメソッドを参照する必要があった。
そのメソッドを1度きりしか使わない場合でも、必ずどこかで定義する必要がある。
●C#2.0
デリゲートを渡すものと期待される任意の箇所に、 直接、名前のないメソッドを記述できる仕組みが搭載された。
この機能を匿名メソッドと呼びます。
new Predicate(IsOver10)と書いていた部分
↓
delegate(int n){ return n > 10; } と、IsOver10 の中身そのものが書ける。
匿名メソッドとは、このような、delegate キーワードから始めて、メソッドの中身を任意の箇所に埋め込んだ部分のことを指します。
書式:delegate (引数リスト){ メソッド定義 }
●C#3.0
匿名メソッドをさらに簡単に書けるようになった(ラムダ式)
C#2.0
delegate(int n){ return n > 10; }
↓
C#3.0
(int n) => { return n > 10; }
デリゲートのプロトタイプ宣言
デリゲートは単にクラスのインスタンスのメソドのアドレスを保持するだけではなく、引数と返値の受け渡しも行なう。
●処理としては・・・
①デリゲートのプロトタイプ宣言を行う。
目的のメソッドのアドレスを保持することが出来るデリゲートクラスは、
必ずそのメソッドと同じ戻り値や引数を設定できる方法で宣言する。
②コンパイラはプロトタイプを見る。
デリゲートが目的のメソドの引数や戻り値にマッチしているか否かを確認し安全性を保障する。
デリゲートの実際例
delegate void dlgWriteString(string msg);
※文字列を引数に取り返値を取らないデリゲートのクラスの宣言である(プロトタイプ)
このデリゲートクラスは同じ引数を持ち同じ返値を持つメソッドであればどの様なメソッドで有ってもその参照(アドレス)を保持できる。
Action<T>
FrameWorkには既定の宣言(既に行われている)が有る。
=Action<T>
デリゲートが宣言されている。
※Action<T>=FrameWork2.0以上で使用できる。
例:Action<string> wm = new Action<string>(ws.WriteMessage);
これにより、
delegate void dlgWriteString(string msg);
は必要なくなる。
デリゲートと匿名メソッド
using System;
using System.Collections.Generic;
using System.Text;
namespace DlegateTest
{
delegate void dlgWriteString(string msg);
delegate void dlgAwrite();
//デリゲートで呼ばれるメソドを持つクラス
class WriteToScreen
{
//デリゲートで呼ばれるメソド
public void WriteMessage(string msg)
{
Console.WriteLine(msg);
}
}
class Program
{
static void Main(string[] args)
{
string msg = "Hello World!";
//これはデリゲートとは関係無い普通の呼び出し
WriteToScreen ws = new WriteToScreen();
ws.WriteMessage(msg);
//デリゲートのインスタンスを作成して
//WriteToScreenの WriteMessageの
//インスタンスのアドレスを登録する
dlgWriteString dws = new dlgWriteString(ws.WriteMessage);
//デリゲートの呼び出し
dws(msg);
//FrameWorkで宣言済みのActionデリゲートを使う
Action<string> wm = new Action<string>(ws.WriteMessage);
wm(msg);
//匿名メソドを使用したデリゲート
dlgAwrite da = delegate() {Console.WriteLine(msg);};
da();
}
}
}
最終更新:2009年08月18日 14:51