C#では、メソッドの引数として「処理そのもの(ラムダ式やメソッド)」を受け取ることができます。これにより、処理の一部を呼び出し元で決定できる、柔軟で再利用性の高いメソッドを作成可能です。
以前は独自のdelegate型を定義する必要がありましたが、現在のC#開発では、標準ライブラリ(System名前空間)に用意されている汎用デリゲート(Action, Func, Predicate)を使用するのが一般的です。
ここでは、それぞれのデリゲートの役割と、それらを引数に取るメソッドの実装方法について解説します。
標準デリゲート(Action, Func, Predicate)の概要
よく利用される標準デリゲートの定義と用途は以下の通りです。これらを使い分けることで、戻り値の有無や意味を明確に表現できます。
| デリゲート名 | 戻り値 | 意味・用途 |
Action | void | 引数なしの処理を実行する。 |
Action<T> | void | 引数Tを受け取り、値を返さずに処理を実行する(副作用のみ)。 |
Func<TResult> | TResult | 引数なしで、結果TResultを返す処理を実行する。 |
Func<T, TResult> | TResult | 引数Tを受け取り、結果TResultを返す(変換や生成)。 |
Predicate<T> | bool | 引数Tを受け取り、条件に合致するか判定する(真偽値を返す)。 |
※ FuncやActionは引数を最大16個まで取ることができます(例: Action<T1, T2, ...>)。
1. Actionデリゲートを受け取るメソッド(処理の実行)
Action系デリゲートは、戻り値を持たない処理(コンソール出力、ログ記録、状態変更など)を渡す際に使用します。
以下のコードは、指定された回数だけループを行い、その都度、呼び出し元から渡された処理(action)を実行するメソッドの例です。
using System;
namespace DelegateExamples
{
class Program
{
static void Main()
{
// 使用例: 0から2までのインデックスを受け取り、コンソールに出力する
// 引数 n は現在のループインデックス
Repeat(3, n => Console.WriteLine($"{n + 1}回目の実行です。"));
}
/// <summary>
/// 指定回数ループし、その都度アクションを実行します。
/// </summary>
/// <param name="count">繰り返し回数</param>
/// <param name="action">実行したい処理(引数は現在のインデックス)</param>
static void Repeat(int count, Action<int> action)
{
for (var i = 0; i < count; i++)
{
// 渡されたデリゲート(ラムダ式)を実行
action(i);
}
}
}
}
実行結果
1回目の実行です。
2回目の実行です。
3回目の実行です。
2. Funcデリゲートを受け取るメソッド(値の生成・変換)
Func系デリゲートは、何らかの値を返す処理を渡す際に使用します。計算式を与えて結果を受け取る場合や、工場の製造ラインのように入力を出力へ変換する場合に適しています。
以下のコードは、インデックスを受け取り、計算結果をシーケンスとして生成するメソッドの例です。
using System;
using System.Collections.Generic;
namespace DelegateExamples
{
class Program
{
static void Main()
{
// 使用例: 0〜4のインデックスを受け取り、2のn乗を計算して返す
// Func<int, double> として扱われます
var seq = Generate(5, n => Math.Pow(2, n));
foreach (var x in seq)
{
Console.WriteLine(x);
}
}
/// <summary>
/// 指定されたジェネレーター関数を使用してシーケンスを生成します。
/// </summary>
/// <param name="count">生成する個数</param>
/// <param name="generator">インデックスから値を生成する関数</param>
/// <returns>生成されたシーケンス</returns>
static IEnumerable<T> Generate<T>(int count, Func<int, T> generator)
{
for (int i = 0; i < count; i++)
{
// ジェネレーター関数を呼び出し、結果を呼び出し元へ返す
yield return generator(i);
}
}
}
}
実行結果
1
2
4
8
16
3. Predicateデリゲートを受け取るメソッド(条件判定)
Predicate<T>デリゲートは、オブジェクトが特定の条件を満たしているかを判定する(true または false を返す)処理を渡す際に使用します。フィルタリングや検索ロジックの実装によく使われます。
※ Func<T, bool> と機能的には同じですが、意味的に「条件判定」であることを強調する場合に使われます。
以下のコードは、数値のリストから、条件に合致する要素だけを抽出して新しいリストを作成するメソッドの例です。
using System;
using System.Collections.Generic;
namespace DelegateExamples
{
class Program
{
static void Main()
{
var numbers = new[] { 1, 5, 8, 10, 13, 20 };
// 使用例: 10以上の数値のみを抽出する条件(Predicate)を渡す
List<int> largeNumbers = Filter(numbers, n => n >= 10);
Console.WriteLine("--- 10以上の数値 ---");
largeNumbers.ForEach(Console.WriteLine);
// 使用例: 偶数のみを抽出する
List<int> evenNumbers = Filter(numbers, n => n % 2 == 0);
Console.WriteLine("--- 偶数 ---");
evenNumbers.ForEach(Console.WriteLine);
}
/// <summary>
/// 条件に一致する要素のみを抽出します。
/// </summary>
/// <param name="items">入力コレクション</param>
/// <param name="predicate">判定条件(trueなら採用)</param>
/// <returns>抽出されたリスト</returns>
static List<T> Filter<T>(IEnumerable<T> items, Predicate<T> predicate)
{
var result = new List<T>();
foreach (var item in items)
{
// Predicateを実行し、trueが返ってきた場合のみリストに追加
if (predicate(item))
{
result.Add(item);
}
}
return result;
}
}
}
実行結果
--- 10以上の数値 ---
10
13
20
--- 偶数 ---
8
10
20
まとめ
メソッドの引数にデリゲートを採用することで、処理の「枠組み(ループやリスト操作)」と「中身(具体的な処理や条件)」を分離できます。
- 副作用のある処理なら
Action - 値を返す計算や変換なら
Func - 条件判定なら
Predicate
これら標準デリゲートを適切に使い分けることで、汎用的で読みやすいコードを設計してください。
