デリゲート
デリゲート(delegate)とは
デリゲート(delegate)とはメソッド(関数)を変数のように扱う機能です。
C言語などでは関数ポインタと呼ばれるものとほぼ同じです。
デリゲートはメソッドの処理を動的に入れ替えることができる機能です。
(delegate=委譲、委託)
あまりイメージがわかないと思うので、順に説明します。
delegateの定義
//デリゲートの定義
delegate int MyDelegate(int a, int b);
static void Main(string[] args)
{
}
デリゲートの定義はメソッドの定義に似ています。
記述する場所も通常のメソッドと同じ場所(Mainメソッドの横)です。
最初のdelegate
というキーワードがデリゲートの宣言を意味します。
続いて戻り値、デリゲート名、引数の定義というように、通常のメソッドの定義と同じように記述します。
最後にセミコロンを書いてデリゲートの定義は終了です。
デリゲートはメソッドの「引数とそのデータ型、戻り値のデータ型」だけを定義します。
サンプルコードでは「引数にint型をふたつ取り、int型を返すMyDelegate
という名前のデリゲート型」を定義したことになります。
(仮引数名も記述しますが、この名前は重要ではありません)
デリゲートは「デリゲート型」という新しいデータ型を定義したことになります。
通常のデータ型とは異なり、メソッドを格納できる特殊なデータ型です。
デリゲートにはメソッドの処理が存在しないので、そのままでは使えません。
デリゲートを使用するには以下のようにします。
//デリゲートの定義
delegate int MyDelegate(int a, int b);
//定義したデリゲートと同じ引数、戻り値のメソッド
static int TestMethodA(int x, int y)
{
return x + y;
}
static void Main(string[] args)
{
//MyDelegate型の変数の使用宣言
//同じ引数、戻り値を持つメソッドを格納
MyDelegate md = TestMethodA;
//古い宣言方法
//MyDelegate md = new MyDelegate(TestMethodA);
//TestMethodAメソッドを実行
int num = md(3, 6);
Console.WriteLine(num);
Console.ReadLine();
}
9
まずMyDelegate型と同じ引数、戻り値を持つメソッドとしてTestMethodA
を定義します。
次にMyDelegate型の変数md
を宣言し、TestMethodA
を代入します。
この時、関数呼び出し演算子()
は記述しません。
関数呼び出し演算子は名前の通り関数を実行するので、これを記述するとメソッドを実行した戻り値を代入するという意味になってしまいます。
デリゲート型変数には、メソッドの戻り値ではなくメソッドそのものを格納します。
new
キーワードを使用してMyDelegate型変数md
に代入する方法もあります。
どちらにしても代入できるのはMyDelegate型と同じ引数、戻り値のメソッドのみです。
これで変数md
にはTestMethodA
メソッドが格納されている状態になります。
変数md
は、通常のメソッドのように関数呼び出し演算子と、必要な実引数を記述することでTestMethodA
を実行することができます。
(20行目)
上記コードを少し変形して以下のようにしてみます。
delegate int MyDelegate(int a, int b);
static int TestMethodA(int x, int y)
{
return x + y;
}
static int TestMethodB(int x, int y)
{
return x * y;
}
static void Main(string[] args)
{
MyDelegate md;
if (true)
md = TestMethodA;
else
md = TestMethodB;
int num = md(3, 6);
}
同じ引数、戻り値を持つメソッドをもう一つ定義します。
MyDelegate型変数md
に代入するメソッドは条件によって変えることにします。
(今回はテストなので常にtrue
)
このようにすると、変数md
は「条件によって呼び出されるメソッドが変わる」ことになります。
これがデリゲートの特徴です。
デリゲートは、必要に応じてメソッドの処理を別のメソッドに差し替えることができる機能です。
デリゲートの用途
デリゲートの特徴は「呼び出し方はそのままで、呼び出す処理(メソッド)を変えられる」ことです。
例えばメソッドの引数にデリゲートを使用すれば、メソッド内の処理は変えずに別の処理をさせることができます。
//デリゲートの定義
delegate bool MyPredecate(int x);
//引数が10以上なら真を返すメソッド
static bool IsGTE10(int n)
{
return n >= 10;
}
//引数が偶数なら真を返すメソッド
static bool IsEven(int n)
{
return n % 2 == 0;
}
//配列から条件に合致する要素を抽出するメソッド
//「条件」は引数のデリゲートで指定できる
static int[] Select(int[] arr, MyPredecate predicate)
{
//条件に合う要素の個数を数える
int count = 0;
foreach (var x in arr)
if (predicate(x))
count++;
//戻り値となる配列を宣言
int[] ret = new int[count];
//戻り値となる配列に値を代入
count = 0;
foreach (var x in arr)
if (predicate(x))
ret[count++] = x;
return ret;
}
static void Main(string[] args)
{
int[] nums = new int[]
{ 2, 4, 7, 8, 11, 14, 18, 19 };
int[] arr1 = Select(nums, IsGTE10);
int[] arr2 = Select(nums, IsEven);
foreach (var n in arr1)
Console.Write("{0} ", n);
Console.WriteLine();
foreach (var n in arr2)
Console.Write("{0} ", n);
Console.ReadLine();
}
11 14 18 19 2 4 8 14 18
MyPredecate
は「引数にint型一つを取り、bool型を返す」デリゲートです。
IsGTE10
メソッドとIsEven
メソッドの引数と戻り値と同じなので、これらのメソッドはMyPredecate
デリゲートで利用できます。
Select
メソッドはint型配列とMyPredecate
型を引数に取ります。
メソッド内の処理は、foreach文で配列の要素を一つずつ取り出し、引数predicate
を実行して条件判定をしています。
引数predicate
の中身はメソッドなので、関数呼び出し演算子()
に引数を渡してメソッドを実行することができます。
引数predicate
に格納するメソッドはSelect
メソッドの呼び出し時に決定しています。
(43、44行目)
定義済みデリゲート
使用するデリゲート型は自分で定義しても良いですが、よく使用される形式のものは最初から用意されています。
これを定義済みデリゲートといいます。
(いずれもSystem
名前空間)
定義済みデリゲート | 主な用途 |
---|---|
bool Predicate<T>(T obj) | 引数が条件に合致するか |
TOutput Converter<TInput, TOutput>(TInput input) | TInput型をTOutput型に変換する |
int Comparison<T>(T x, T y) | xとyを比較して、xのほうが小さければ0未満を返す。 xのほうが大きければ0より大きい値を返す。 同じならば0を返す。 |
デリゲート名直後の<T>
などは型引数といい、型引数T
は実際のデータ型に置き換えて使用します。
例えば先ほどのSelect
メソッドを定義済みデリゲートで書き換えると以下のようになります。
//System名前空間で定義されている定義済みデリゲート
public delegate bool Predicate<T>(T obj);
//↑を使う場合
//もうこれは必要ない
//delegate bool MyPredecate(int x);
static int[] Select(int[] arr, Predicate<int> predicate)
{
//省略
}
型引数は、クラスやメソッドなどの内部で使用するデータ型を、その呼び出し元から指定することができる機能です。
ListクラスやDictionaryクラスなどを利用する場合に、管理するデータ型をプログラマが自由に指定できるのと同じです。
自作のデリゲートでも型引数に対応することができます。
(詳しくはジェネリック参照)
Actionデリゲート
Actionデリゲートはより柔軟なデータ型を取れるデリゲートです。
Actionデリゲートは「戻り値なし(void)」「引数は0~4個(それぞれのデータ型は任意)」というデリゲートです。
.NET Framework4.0以降では引数は16個までに拡張されています。
Funcデリゲート
Funcデリゲートは「戻り値あり(データ型は任意)」「引数は0~4個(それぞれのデータ型は任意)」というデリゲートです。
.NET Framework4.0以降では引数は16個までに拡張されています。
Actionデリゲートとの違いは戻り値があるかないかだけです。
どちらの場合でも引数が4個以上のメソッドはあまりなく、ましてや16個以上というメソッドはほぼないので、これらを使用すればデリゲートを自分で定義して使用する必要はほとんどありません。
static void MethodA1() { }
static void MethodA2(int x) { }
static void MethodA3(int x, float y) { }
static int MethodF1() { return 0; }
static bool MethodF2(int x) { return false; }
static string MethodF3(int x, float y) { return "abc"; }
static void Main(string[] args)
{
Action action1 = MethodA1;
Action<int> action2 = MethodA2;
Action<int, float> action3 = MethodA3;
Func<int> func1 = MethodF1;
Func<int, bool> func2 = MethodF2;
Func<int, float, string> func3 = MethodF3;
}
Actionデリゲートの型引数はそのまま引数のデータ型です。
引数のないメソッドの場合は型引数の指定はありません。
Funcデリゲートの型引数の指定は、最初に引数のデータ型を指定し、最後に戻り値のデータ型を指定します。
引数を取らないメソッドでも戻り値はあるので、必ず一つは型引数を指定します。
例えばFuncデリゲートを利用して、先ほどのサンプルコードのSelect
メソッドを書き直すと以下のようになります。
//もうこれは必要ない
//delegate bool MyPredecate(int x);
//配列から条件に合致する要素を抽出するメソッド
//「条件」は引数のFuncデリゲートで指定できる
static int[] Select(int[] arr, Func<int, bool> func)
{
//省略
}
匿名メソッド
delegate
キーワードは匿名メソッドというものを定義することもできます。
ただしこの構文は古いもので、ラムダ式が導入されてからはそちらが利用されています。
//配列から条件に合致する要素を抽出するメソッド
//「条件」は引数のFuncデリゲートで指定できる
static int[] Select(int[] arr, Func<int, bool> func)
{
//省略
}
static void Main(string[] args)
{
int[] nums = new int[]
{ 2, 4, 7, 8, 11, 14, 18, 19 };
int[] arr1 = Select(
nums,
//匿名メソッド
delegate(int n)
{
return n >= 10;
});
}
デリゲートを利用する場合、処理の本体はメソッドとして定義する必要がありますが、匿名メソッドを利用すれば必要な場所に処理を直接記述することができます。
このメソッド自体は名前を持たないので「匿名」なメソッドということです。
上の例では、Select
メソッドの実引数(呼び出し側)に匿名メソッドを指定しています。
ただし、名前を持たないので他からそのメソッドを利用することはできません。
再利用したい場合はActionやFuncデリゲートを利用する方法があります。
//匿名メソッドをFuncデリゲート型変数に格納する
Func<int, bool> func = delegate (int n) { return n >= 10; };
bool b = func(5);