CodeGym /コース /C# SELF /ラムダ式と Func,

ラムダ式と Func, Action, Predicate

C# SELF
レベル 50, レッスン 1
使用可能

1. はじめに

面白いことに気づいた? ラムダ式自体は鍋のないレシピみたいなものだ。何をするかは書けるけど、コードの中で動かすには「コンテナ」が必要になる。C# にはそのための汎用デリゲートが用意されていて、それが Func、Action、Predicate だ。

必要な型をテンプレートとして使って、そこに自分のロジックを流し込めばOK。わざわざ自前のデリゲートを作って自転車を再発明する必要はほとんどない。

ちょっと歴史

関数をパラメータとして渡す必要が出てきた初期のころは、毎回デリゲート型を宣言しないといけなかった。面倒で長かった、こんな感じ:

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

C# に Func、Action、Predicate が入ってからは、手動でデリゲートを宣言する必要は 90% のケースでなくなった。今はずっとシンプル:

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — 戻り値のある関数

構文と用途

Func はジェネリックなデリゲートで、パラメータを0個から16個まで受け取り、値を返す。

Func<int, int, int> sum = (x, y) => x + y;

Func<型1, 型2, ..., 型N, TResult> — 最後の型が戻り値で、それ以前が引数になる。

例

1. 2つの数の合計

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. 数値を二乗する

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. パラメータ無し

Func<string> greet = () => "やあ、ラムダ!";
Console.WriteLine(greet());

視覚的な図

シグネチャ 例 説明
Func<int, int>
x => x * 2
int を受け取り、int を返す
Func<int, int, int>
(a, b) => a + b
2つの int を受け取り、int を返す
Func<string>
() => "hi"
何も受け取らず、文字列を返す

アプリでの見た目

例えば小さなアプリ(仮の「ユーザー辞書」)で、数値のリストに色んな処理を適用したいとする。二乗したり、固定値と合計したり:

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — 戻り値のない処理

Action は何かを実行するメソッド用の汎用デリゲート(画面に出力するなど)で、値を返さない。

パラメータを0個から16個まで取れるが、常にvoid を返す。

例

1. 画面に出力

Action<string> print = text => Console.WriteLine("データ: " + text);
print("やあ、世界!");

2. パラメータ無しのアクション

Action greet = () => Console.WriteLine("ようこそ!");
greet();

3. 複数パラメータのアクション

Action<int, int> showSum = (a, b) => Console.WriteLine($"合計: {a + b}");
showSum(2, 3); // 合計: 5

視覚的な図

シグネチャ 例 説明
Action
() => ...
パラメータ無し、戻り値無し
Action<int>
x => ...
1つのパラメータ
Action<int, string>
(x, s) => ...
複数パラメータ

アプリでの例

ユーザー辞書に全員の名前を出力するメソッドを追加してみる:

List<string> names = new() { "アンナ", "ボリス", "ヴィカ" };
Action<string> printName = name => Console.WriteLine("ユーザー: " + name);

names.ForEach(printName);
// あるいは: names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — yes / no?

1つのパラメータに対して true か false を返す関数が必要なときは Predicate<T> を使おう。これは1つのパラメータを受け取り bool を返すデリゲートだ。

Predicate は要するに「ブール判定が欲しい」用のラッパーだ。

例

1. 5 より大きいかをチェック

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. List<T>.Find と一緒に使う

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // Predicate<int> が使われる
Console.WriteLine(found); // 7

3. 全員が成人か?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll は Predicate<int> を受け取る

Func<T, bool> と何が違う?

実際にはほとんど互換性がある。Microsoft のドキュメントでも言われている通り、"Predicate<T> は特定の API のための Func<T, bool> に過ぎない"。ただし標準ライブラリのメソッドは時々あえて Predicate を要求することがある。

5. ラムダがどうやって Func、Action、Predicate に「はまる」か

ラムダを書くと、C# は「この形は要求されているデリゲートに合っているな」と解析して代入できるようにする。

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

どれもラムダだけど、内部ではシグネチャの違う3つの別々のデリゲートが動いている。

実際のコード例での適用

List<User> users = new() {
    new User("アンナ", 24),
    new User("ボリス", 17),
    new User("ヴィカ", 31),
};

// 成人ユーザーだけを返す関数 (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("成人一覧: " + string.Join(", ", adults.Select(u => u.Name)));

全ユーザーの名前を Action<User> で出力したいなら:

users.ForEach(user => Console.WriteLine(user.Name));

名前を取得する (Func<User, string>):

IEnumerable<string> names = users.Select(user => user.Name);

わかりやすい表

デリゲート シグネチャ ラムダの例 使われる場所
Func<T, U>
T → U
user => user.Name
Select、任意の変換
Action<T>
T → void
user => Console.WriteLine(...)
ForEach、処理メソッド
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, フィルタ

6. 内蔵アプリの例:ステップ・バイ・ステップ

小さな「ユーザー辞書」アプリを拡張してみよう。まずクラス User を用意する:

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — ユーザーが成人かチェック

Func<User, bool> isAdult = user => user.Age >= 18;

LINQ で使う例:

var adults = users.Where(isAdult);

2. Predicate<User> — アクティブなユーザーを探す

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — ユーザーを非アクティブ化する

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — 短い説明を取得

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

これらのラムダは「データとしてのコード」で、メソッドに渡したり、変数に保持したり、組み合わせたりできる。

7. 気をつけるポイントとよくあるミス

1. ラムダはデリゲートのシグネチャに合っている必要がある。 シグネチャが合わなければコンパイルエラーになる。

Func<int, string> wrong = x => x * 2; // エラー: string が期待されましたが int が得られました
// 正しい例:
Func<int, string> right = x => (x * 2).ToString();

2. void と return を混同しないこと。 Action は値を返さない。例えば Action<int> a = x => x * x; は動かない。戻り値を返しているからだ。

3. Predicate<T> と Func<T, bool> はよく互換だが、常に同じではない。 コレクションのメソッドが特に Predicate<T> を要求することがあり、その場合は直接代入できない。

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // エラー
// ただし:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // コンストラクタ経由で変換できる
2
タスク
C# SELF, レベル 50, レッスン 1
ロック未解除
Actionを使って文字列リストを処理しよう
Actionを使って文字列リストを処理しよう
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION