はじめに
誤りについては優しく指摘してくださると嬉しいです。
デリゲートとは
メソッドを参照する型のこと。
デリゲートという名前の通り、他のメソッドに処理を移譲するためのオブジェクト。
delegate 戻り値型 デリゲートの名前(引数リスト);として定義し、
デリゲートの名前 変数;として宣言する。
この時指定した戻り値ないし引数リストと、同じ戻り値ないし引数リストを持つメソッドを代入して使うことができる。
そしてデリゲートに格納されたメソッドは、デリゲートを介して呼び出す。
宣言と代入は同時に行っても良い (コメント参照)。
namespace TMP
{
// デリゲートの宣言
// int型の戻り値、int型の引数を2つ
public delegate int Calc(int a, int b);
internal class Program
{
// デリゲートと同じ戻り値、引数リスト
public int Sum(int a, int b)
{
return a + b;
}
static void Main(string[] args)
{
Program p = new Program();
Calc c;
c = p.Sum; // Calc c = p.Sum;でまとめて宣言しても良い
// デリゲートの引数に値を渡して呼び出す
// 4と出力される
Console.WriteLine(c(3, 1));
}
}
}
デリゲートには、+= メソッド名で複数のメソッドを代入することができる。
代入したメソッドを削除するには-= メソッド名とすればよい。
デリゲートを1度呼び出すと紐づけた複数のメソッドを一気に呼ぶことができる。
戻り値のあるメソッドについては、
最後に代入したメソッドの戻り値しか返らないので注意。
namespace TMP
{
// デリゲートの宣言
// int型の戻り値、int型の引数を2つ
public delegate int Calc(int a, int b);
internal class Program
{
// デリゲートと同じ戻り値、引数リスト
public int Sum(int a, int b)
{
return a + b;
}
// デリゲートと同じ戻り値、引数リスト
public int Sub(int a, int b)
{
return a - b;
}
static void Main(string[] args)
{
Program p = new Program();
Calc c = p.Sum;
c += p.Sub;
// 2とだけ出力される
Console.WriteLine(c(3, 1));
}
}
}
デリゲートはパラメータとして渡したり、プロパティに割り当てることができる。
namespace TMP
{
public class Dele_Lam
{
private static int _a = 10;
private static int _b = 20;
// 引数としてCalcデリゲートを取る
public static int Add(Calc method)
{
return method(_a, _b);
}
}
}
// 下のコードで実行する
namespace TMP
{
// デリゲートの宣言
public delegate int Calc(int a, int b);
internal class Program
{
public int Sum(int a, int b)
{
return a + b;
}
public Program()
{
// デリゲート型「method」にメソッドを代入
Calc method = Sum;
// 「method」の処理を実行
// 5が出力される
Console.WriteLine(method(2, 3));
// 先に定義した引数に代入することもできる
int n = Dele_Lam.Add(method);
// 30が出力される
Console.WriteLine(n);
}
static void Main(string[] args)
{
// コンストラクタが動く
// 5と30が出力される
Program p = new Program();
}
}
}
使いどころ
今のところはイベント処理に使っている。
下に示す2つのコードは、入力された小文字を大文字して返すプログラムである。
空行の入力があった場合はプログラムを終了する。
namespace Sample
{
// デリゲートの宣言
public delegate void ShowCapital(string line);
// クラスの宣言
public class ConsoleLineReader
{
// イベントの宣言
public event ShowCapital ConsoleLineRead;
// メソッド
public void StartRead()
{
Console.WriteLine("文字列を入力してください。空行で終了します。");
while (true)
{
string input = Console.ReadLine(); // 入力された文字列を受け取る
if (input == null || input == string.Empty) // null,もしくは空文字で終了
{
Console.WriteLine("");
Environment.Exit(0);
}
else
{
// イベント発火
// input(入力された文字列)を登録されたものに渡す
ConsoleLineRead?.Invoke(input);
}
}
}
}
}
namespace Sample
{
internal class Program
{
// 引数に渡された文字列を大文字にして返す
public void ReturnCapital(string input)
{
Console.WriteLine(input.ToUpper());
}
// エントリポイント
static void Main(string[] args)
{
Program p = new Program();
// 1つ目のコードで示したクラスのインスタンスを生成
ConsoleLineReader clr = new ConsoleLineReader();
// イベントに登録
clr.ConsoleLineRead += p.ReturnCapital;
// 1つ目のコードのメソッドを呼ぶ
// elseでイベントが発火し、ReturnCapitalが実行される
clr.StartRead();
}
}
}
上の例ではpublic delegate 戻り値 デリゲート型名 (引数リスト)でデリゲートを宣言し、public event デリゲート型名 イベント名でイベントを宣言した。
しかし、イベントの宣言時にpublic event デリゲート型名 イベント名 = delegate{}と書くことでnullチェックが不要になる書き方もあり、おそらくこちらの方が都合が良い。
この書き方の場合、イベントの発火は?.Invokeではなくイベント名(引数)で行う。
namespace TMP
{
public delegate void ShowCapital(string line);
public class ConsoleLineReader
{
// delegate{} で初期化することでnullチェック不要になる
public event ShowCapital ConsoleLineRead = delegate { };
public void StartRead()
{
Console.WriteLine("文字列を入力してください。空行で終了します。");
while (true)
{
string input = Console.ReadLine();
if (input == null || input == string.Empty)
{
Console.WriteLine("");
Environment.Exit(0);
}
else
{
ConsoleLineRead(input); // ?.Invoke()不要で直接呼び出せる
}
}
}
}
}
他にも、参考に挙げたサイトによればLinqというライブラリを使う際に登場するらしい。
これについてはまだ機会がないので、出会った際に追記する。
参考