delegateについて
C#においてマルチスレッドプログラミングを行うときなどに、”delegate”について理解しておく必要があります。 英単語の”delegate”は”移譲”という意味で、その名の通りある処理の実行を別のオブジェクトに移譲するときに使用します。
delegateというのは「関数、メソッドを格納する箱」のようなデータ型と考えればよい。 CやC++でいうところの関数ポインタをひとまとめにする型 ともいえるのではないかと思います。
使い方のイメージ
まずは使い方の簡単なイメージを整理してみる。
まずはdelegateの定義(宣言)、つまり関数を格納する箱の形を定義する。この状態では、箱の形を定義しただけなので実体が存在していない。
次に、箱の実体を作成(インスタンス化)する。これで、delegateを使えるようになる。
次に、作った箱の中に関数を格納していく。関数は1つだけでなく複数格納することができる。上図では、func1, func2, func3の3つの関数を格納している。
func1, func2, func3を別オブジェクトで実行させたい場合は、それらを格納した箱ごと渡す。
コード例
次に、ソースコードの例を示す。下記の例では、別オブジェクトにdelegateを渡すということはせずに同じオブジェクト内でdelegateを実行する、最も単純な例を示す。
そもそもなぜdelegateのようなものが必要なのかという話ですが、例えばC言語では関数ポインタを使ったりして別のオブジェクトに処理を移譲することができる。しかし、C#はそもそもポインタを使うような言語(unsafeなコード)ではないため、安全に処理の受け渡しをできるような仕組みとしてdelegateが存在していると理解している。
Actionについて
先に説明したように、delegateの基本的な使い方は 宣言⇒インスタンス化⇒メソッド代入 ですが、コード量としてはちょっと面倒くさい。
そこでAction(Action Delegate)が登場する。Actionを使うことで通常のdelegateような宣言が必要なくなり、コードを単純にすることができる。つまりActionは通常のdelegateを使いやすくしたような型ってことですね。
Actionの定義は下記の通り。
1 |
public delegate void Action<in T>(T obj); |
Parameter T : Action
に格納したメソッドに渡す引数のデータ型。インスタンス化するときに引数の型を指定してやる。 Action
を実行するときは、データ型Tの引数objを渡して実行する。 上記の定義では引数は1つだけですが、16個まで拡張できます。
先ほどのdelegateのコード例をActionを使って実現してみる。
ただし、Action
を使用できるのはAction Delegateに格納した関数/メソッドが返り値を持たない場合のみです。
Microsoftの公式ドキュメントには、Action
について下記のように説明されている。
Encapsulates a method that has no parameters and does not return a value.
Funcについて
Action
に対して、戻り値を持てるようにしたのがFunc
です。
1 |
public delegate TResult Func<in T,out TResult>(T arg); |
Parameter
T : Func
に格納したメソッドに渡す引数のデータ型。インスタンス化するときに引数の型を指定します。
Func
を実行するときは、データ型Tの引数objを渡して実行する。
Action
と同様に16個まで拡張できます。
TResult : Func
に格納したメソッドの戻り値のデータ型。インスタンス化するときにデータ型を指定します。
Actionのときに加えて、関数の戻り値のデータ型をパラメータとしてインスタンス生成時に記述する。 Actionとの差はこのくらい。
匿名メソッドとラムダ式
ここまで、ActionやFuncを使って通常のdelegateをシンプルにする方法を示してきましたが、別の方法やさらにシンプルにする方法がある。 ラムダ式は匿名メソッドの改良版みたいな位置づけなので、ラムダ式のみを理解しておけばOK。
匿名メソッド
簡単な処理、かつ何回も使いまわさないその場限りのメソッドであれば、わざわざ定義を行わなくてもOKといったもの。 匿名メソッドはdelegate(Action, Func含む)にのみ使用できる。
例として、値aに1を足してその結果をプリントする匿名メソッドのdelegateの例を示します。
1 2 3 4 5 6 7 8 9 10 11 12 |
static void Main(string[] args) { int value = 0; Action<int> del_func_inst = delegate (int a) { Console.WriteLine("anonymous method is executed. a+1={0}.\n", a + 1); }; del_func_inst(value); return; } |
匿名メソッドのテンプレートは下記の通り。
1 |
delegate (引数 a) {引数 aを使った処理}; |
通常のメソッドの宣言から名前を無くしたような感じ。まさに匿名メソッド。
ラムダ式
ラムダ式は匿名メソッドをさらに簡単に書けるようにしたもの。要は匿名メソッドのシンタックスシュガーです。
現在はこちらの方が主流で、多くの場合ラムダ式を使う。
1 2 3 4 5 6 7 8 9 10 11 12 |
static void Main(string[] args) { int value = 0; Action<int> del_func_inst = (int a) =>{ Console.WriteLine("anonymous method is executed. a+1={0}.\n", a + 1); }; del_func_inst(value); return; } |
匿名メソッドに対して、delegate
オペレータが不要になっている。
ラムダ式のテンプレートは下記の通り。
1 |
(引数 a)=>{引数 aを使った処理}; |
引数が必要ない場合は、()=>{処理}
という形でカッコ内を空にしておけばよい。
delegateの使いどころ
delegateは主にtaskクラスに渡して使用します。taskクラスは並列処理を行うために必要なもので、別スレッドで走らせたい処理をtaskクラスに渡すためにdelegateを使用します。
taskクラスについては下記の記事を参照ください。