デリゲートを簡単に説明すると、関数を変数のように扱う為のものです。
C言語に慣れている人には関数ポインタのようなものと言った方が分かるかもしれません。
変数のように扱えれば、関数の引数に関数を渡す事ができます。
引数に関数が渡せれば、計算式の一部だけを簡単に差し替える事ができます。
デリゲートはどのように使われる?
デリゲートはイベントハンドラやコールバックで利用されます。
イベントハンドラは「クリックされた」とか「値が変わった」のようなイベントが起こった時に、それに対応する処理を行う仕組みです。Visual Studioなどの統合環境を使っているとコードが自動生成されるのであまり意識しないかもしれませんがデリゲートの仕組みが使われています。
コールバックは、ある関数が実行している途中で別の関数を実行させる仕組みです。
コールバックを理解する事でより汎用的なクラスやメソッドが作れるようになります。
デリゲート(delegate)を定義する
変数に int や double といった型がありますね。
関数を変数として扱う為には、関数の型を決める必要があります。
関数の型を定義するのが delegate キーワードです。
delegateの宣言は↓このようになります。
public delegate bool TestDelegate(object obj);
クラスのメソッドを作るのと似ていますが、先頭に delegate キーワードが付いていてメソッドの中身がありません。こんな形の関数を作りますよと宣言しているだけです。
上の例では「TestDelegateという名前の型は、 object 型の引数を1つ必要として bool の戻り値を返す関数です」と宣言しているわけです。
イベントハンドラ的な使い方
delegateを使ってイベントハンドラのような動きをするサンプルを作ってみました。
以下の例では、ループする処理の中で進捗の%が進む度にイベントを発生させています。
public delegate void ProgressHandler(int percentage); public class TestProcess { public event ProgressHandler Progress; public void Run() { int percentage = -1; int count = 100000; for (int i = 0; i < count; ++i) { int work = (int)((double)i / (double)count * 100.0); if (percentage != work) { percentage = work; Progress(percentage); } // // なにかしらの処理 // } Progress(100); } }
1行目で delegate が宣言されています。
5行目で、そのdelegateを使って TestProcessクラスの Progressプロパティを作ります。
15~20行目で処理の進捗率を計算し、パーセンテージが進んだら Progress プロパティにセットされたメソッドを呼び出しています。
※5行目のProgressプロパティには event キーワードが使われています。
※event キーワードは書かなくても同じように動作しますが、これを付ける事で delegate の一部の機能が制限されます。
※使い方をイベントハンドラとしてのものに制限する事でバグの発生を抑制しようという狙いのようです。
public partial class TestWindow : Window { public TestWindow() { InitializeComponent(); var test = new TestProcess(); test.Progress += TestProgressHandler1; test.Progress += TestProgressHandler2; test.Run(); } public void TestProgressHandler1(int percentage) { Console.Write(percentage); } public void TestProgressHandler2(int percentage) { Console.WriteLine("%.."); } }
デリゲートにメソッドをセットするには「+=」演算子を使います。
セットしたメソッドを外すのは「-=」演算子です。
※eventキーワードが使われた場合「=」演算子でセットする事は出来なくなります。
デリゲートには複数のメソッドをセットする事が出来ます。
コールバック的な使い方
delegateを使ってコールバックをするサンプルを作ってみました。
以下の例は、List<int>を継承したクラスを作り、条件に合う項目だけを抜き出すWhereメソッドを作っています。この「条件に合う」という部分にdelegateを使って外に出す事で、様々な条件に対応できる汎用的なメソッドになります。
public delegate bool TestDelegate(int obj); public class TestList : List<int> { public TestList Where(TestDelegate func) { var list = new TestList(); foreach (var obj in this) { if (func(obj)) { list.Add(obj); } } return list; } }
1行目で delegate が宣言されています。
6行目で、その delegate を引数にしたWhereメソッドを定義しています。
引数で渡された func メソッドが true を返すものだけを抽出しています。
public partial class TestWindow : Window { public TestWindow() { InitializeComponent(); var list = new TestList(); list.Add(0); list.Add(1); list.Add(2); var result = list.Where(TestFunc); } public bool TestFunc(int n) { if (0 < n) return true; else return false; } }
定義済みdelegate(FuncとAction)
ここまでの例では自分で delegate を定義して使っていました。
引数の数が変わる度にいちいち定義するのはめんどくさい気がします。
そんな訳で、Microsoftは予め Func と Action という delegate を定義してくれています。
戻り値を持つ delegate が Func、
戻り値を持たない delagete が Actionです。
定義を見てみるとジェネリックという機構をつかって様々な型に対応できるようになっています。
引数の数ごとにたくさん定義されているので、自分でdelegateを定義する必要はもはや無さそうです。
Func
public delegate TResult Func<out TResult>(); public delegate TResult Func<in T1, out TResult>(T1 arg1); public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3); :
Action
public delegate void Action(); public delegate void Action<in T1>(T1 arg1); public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3); :
関連記事
- C#の値型と参照型の違い
- C#のコンストラクタでオーバーロード
- C#のコンストラクタの継承
- C#のジェネリックを使おう
- C#のデリゲート (delegate) って何?
- C#のデリゲートお手軽にする匿名メソッド
- C#のラムダ式【=>】って何?
- C#で基底クラスのメソッドを置き換えるオーバーライド
- C#でキャストとas演算子を使いこなす
- C#で型を判別するtypeofとis演算子
- C#の値型でもnullを扱えるようにするNullable
- C#のリソース解放にはIDisposableとusingを使おう
- C#のStringとstring、Int32とint 違いは・・・ない!
- C#でasync/awaitを使った非同期処理
- C#で文字列を指定の区切り文字で分割
- C#のstring.Formatで桁数や書式を指定する
- C#の配列やListをソートする
- C#の配列やListを検索する (Find,FindAll,FindIndex)
- C#の配列やListを高速に検索する (BinarySearch)
- C#の配列の中に指定の要素が存在するかを調べる(LINQ Contains)
- C#の配列の中に条件を満たす要素が存在するかを調べる(LINQ Any)
- C#の配列から条件に合う要素を抽出する(LINQ Where)
- C#の配列で要素毎の処理結果を得る(LINQ Select)
- C#の配列を並び替える(LINQ OrderBy,ThenBy)
- C#の配列をグループ毎に処理する(LINQ GroupBy)
- C#の配列を内部結合(INNER JOIN)する(LINQ Join)
- C#の配列から最初の要素を取り出す(LINQ First,FirstOrDefault)
- C#の配列の重複要素を削除する(LINQ Distinct)
- C#でフォルダ内のファイル名一覧を取得する
- C#でテキストファイルを読み込む
- C#でテキストファイルに書き込む
- C#でバイナリファイルを読み込む
- C#でバイナリファイルに書き込む
コメントをお書きください