記事タイトルの項目について理解が甘かったため、勉強してまとめました。
動作確認環境
C# 7.3
.Net Framework 4.7.2
delegate
関数への参照を表す型のこと。
デリゲート型の変数を使用することで、参照している関数を呼び出すことができる。
// 参照したい関数と同じ戻り値・引数のデリゲートを定義
private delegate void DelegateType(int i);
static void Main()
{
// デリゲートのインスタンスを作成
DelegateType func = new DelegateType(DisplayNum);
// DisplayNum(1)を呼び出したことになる
func(1);
}
// 引数の数値をコンソールに表示
private static void DisplayNum(int i)
{
Console.WriteLine($"引数の値は {i} です");
}
インスタンス作成の行は、以下のような書き方も可能です。
こちらの方が直感的に理解できるのではないでしょうか。
// 参照したい関数と同じ戻り値・引数のデリゲートを定義
private delegate void DelegateType(int i);
static void Main()
{
// デリゲートのインスタンスを作成
DelegateType func = DisplayNum;
// DisplayNum(1)を呼び出したことになる
func(1);
}
// 引数の数値をコンソールに表示
private static void DisplayNum(int i)
{
Console.WriteLine($"引数の値は {i} です");
}
「int num = 1;」とした場合にnumを1として扱えるのと同様、「DelegateType func = DisplayNum;」とすればfuncをDisplayNum関数として扱えます。
以下は戻り値を返す場合の例。
private delegate int DelegateType2(string str);
static void Main()
{
DelegateType2 func = GetStrLen;
// GetStrLen("abc")を呼び出したことになる
int len = func("abc");
Console.WriteLine($"文字数は {len} です");
}
// 引数の文字列の文字数を返却する
private static int GetStrLen(string str)
{
return str.Length;
}
一つのデリゲート型の変数で複数の関数を参照することもできますが、この記事では割愛します。
Action
簡略化したdelegate。
引数は指定できるが、戻り値は指定できない。
以下はdelegateの項のソースと同じ結果になります。
static void Main()
{
// 「Action<参照する関数の引数1, 引数2, ...>」のように定義する
Action<int> func = DisplayNum;
// DisplayNum(1)を呼び出したことになる
func(1);
}
// 引数の数値をコンソールに表示
private static void DisplayNum(int i)
{
Console.WriteLine($"引数の値は {i} です");
}
ちなみにActionは以下のように定義されています。
public delegate void Action<in T>(T obj);
戻り値無しのデリゲートを、.Net Framework側が「Action」という名前で事前に用意してくれているということですね。
そのためdelegateの項の「DelegateType」のように、わざわざ自前で実装する必要がありません。
なお、Actionの引数は以下の通り0~16個の範囲で指定可能です。
public delegate void Action();
public delegate void Action<in T>(T obj);
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);
:
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
Func
簡略化したdelegate。
Actionと違い、戻り値も指定可能。
static void Main()
{
// 「Func<参照する関数の引数1, 引数2, ... , 参照する関数の戻り値>」のように定義する
Func<string, int> func = GetStrLen;
// GetStrLen("abc")を呼び出したことになる
int len = func("abc");
Console.WriteLine($"文字数は {len} です");
}
// 引数の文字列の文字数を返却する
private static int GetStrLen(string str)
{
return str.Length;
}
こちらも定義を見てみましょう。
Actionと違い、戻り値としてTResultが追加されていますね。
public delegate TResult Func<in T, out TResult>(T arg);
FuncもActionと同様、引数を0~16個の範囲で指定可能です。
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
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);
:
public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
匿名メソッド
参照するタイミングで定義する関数。
一箇所でしか参照しないような関数や、簡単な関数の場合、わざわざ事前に定義せずこの方法を使用したりする。
以下はAction、Funcの項のソースと同じ結果になります。
参照する関数の定義を、インスタンス作成行の右辺に移動しています。
static void Main()
{
Action<int> func = delegate (int i) { Console.WriteLine($"引数の値は {i} です"); };
func(1);
}
static void Main()
{
Func<string, int> func = delegate (string str) { return str.Length; };
int len = func("abc");
Console.WriteLine($"文字数は {len} です");
}
匿名メソッドは「delegate (関数の引数) {関数の処理}」のような記述をします。
関数名が無い以外は通常の関数と同様、つまり「名前のない関数」=「匿名メソッド」です。
ラムダ式
簡略化した匿名メソッド。
こちらもAction、Funcの項のソースと同じ結果になります。匿名メソッドよりも更に関数定義が簡単になっています。
static void Main()
{
Action<int> func = (int i) => { Console.WriteLine($"引数の値は {i} です"); };
func(1);
}
static void Main()
{
Func<string, int> func = (string str) => { return str.Length; };
int len = func("abc");
Console.WriteLine($"文字数は {len} です");
}
更に関数定義を省略することができます。
ネット上ではこちらの書き方をよく見かけます。
static void Main()
{
Action<int> func = i => Console.WriteLine($"引数の値は {i} です");
func(1);
}
static void Main()
{
Func<string, int> func = str => str.Length;
int len = func("abc");
Console.WriteLine($"文字数は {len} です");
}
ラムダ式の使用例。
static void Main()
{
// 非同期処理
Task.Run(() => { Console.WriteLine("非同期処理です"); });
// LINQなど
var lst = new List<int> { 9, 99, 999 };
// 10以上の値の要素のみ取り出して、新しいリストを作成
// 要素ごとにWhereの引数の関数を実施し、trueを返すならばその要素を取り出す
var newLst = lst.Where((int num) => { return num >= 10; }).ToList();
//var newLst = lst.Where(num => num >= 10).ToList(); // 省略形
// 新しいリストの各要素の値をコンソールに表示
// 要素ごとにForEach引数の関数を実施する
newLst.ForEach((int num) => { Console.WriteLine($"要素の値は {num} です"); });
//newLst.ForEach(num => Console.WriteLine($"要素の値は {num} です")); // 省略形
}
まとめ
参考URL
【C#】delegate, Action, Funcについて 多分一番易しい解説
Action<T> Delegate (System) | Microsoft Learn
Func<T,TResult> Delegate (System) | Microsoft Learn
ラムダ式 - ラムダ式と匿名関数 - C# reference | Microsoft Learn