CodeGym /コース /C# SELF /ラムダ式の型としてのデリゲート

ラムダ式の型としてのデリゲート

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

1. はじめに

コーヒーマシンを想像してみて。普通はコーヒーを淹れて終わりなんだけど、たまに上司が「淹れ終わったら『できた!』って叫んでくれない?」って言うことがあるよね。マシン本体を書き換えたりはしない。代わりに、最後に実行する関数を渡すだけでいいんだ。

C#ではこの役割をデリゲートが担うよ — メソッドにコードの断片(メソッド、ラムダ、匿名メソッド)を渡して、適切なタイミングで呼び出せるようにする。ざっくり言えば、デリゲートは特定のシグネチャを持つメソッドへの参照を保持できる型だよ。

デリゲートの定義

C#ではデリゲートはキーワード delegate で定義する。例:

// int を受け取り bool を返すメソッドを参照するデリゲート
public delegate bool PredicateInt(int x);

これで PredicateInt 型の変数は、1つの int を受け取って bool を返す任意のメソッド(あるいはラムダ)を参照できるようになる。

デリゲートは何に使う?

  • ロジックを引数として渡す(例えば、ソート、フィルタ、イベント処理など)
  • イベントの購読(これは後で詳しくやる)
  • コールバックの実装
  • 呼び出し側が振る舞いの一部を決める柔軟なAPI設計

簡単なビジュアル図

デリゲートの種類 シグネチャ 呼び出し例
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. ラムダがデリゲートに変わる仕組み

構文

例えばラムダ式 x => x > 5 と書くと、実際にはデリゲートのオブジェクトを作っているんだ。ラムダは文脈(パラメータの型や戻り値の型)を知らないと独立して存在できない。だからC#のラムダ式は常に(暗黙的にも明示的にも)デリゲートに変換されるんだよ。

例1:デリゲートをメソッドに結びつける

// 明示的にデリゲートを定義する
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // MyPredicate 型の変数にラムダを代入
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

例2:標準デリゲートの利用

C# は汎用的な標準デリゲート群を持っている:ActionFunc<>Predicate<>。ラムダを書くほとんどの場所でこれらを使うことになるよ。

// Func
  
    を使う Func
   
     isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false 
   
  

3. 標準デリゲート: Func, Action, Predicate

Func<...>

何かを受け取って何かを返すメソッド用に使うよ。

シグネチャ:
— 最後の型が戻り値で、それ以前がパラメータの型。例えば:
Func<int, string>int を受け取り string を返す

Func
  
    intToString = number => "数: " + number; Console.WriteLine(intToString(7)); // "数: 7" 
  

Action<...>

何かを実行するけど、結果を返す必要がない(void)ときに使う。

Action
  
    printHello = name => Console.WriteLine("こんにちは、" + name + "!"); printHello("ヴァシリー"); // "こんにちは、ヴァシリー!" 
  

Predicate<T>

本質的に Func<T, bool> の省略形。オブジェクトに対する論理チェック(true/false)のときに使う。

Predicate
  
    isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true 
  

ビジュアル早見表

デリゲート シグネチャ 用途
Func<T1, TResult>
Result(T1)
変換、プロジェクション
Action<T1>
void(T1)
副作用、出力
Predicate<T>
bool(T)
フィルタリング、検索

ラムダにどのデリゲート型を選ぶ?

  • 戻り値があるなら Func<...>
  • 何も返さない(void)なら Action<...>
  • 条件チェックが必要なら Predicate<T>

例:リストのフィルタリング

List
  
    numbers = new List
   
     { 1, 2, 3, 4, 5, 6 }; // Predicate
    
      を期待する List
     
       evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6 
     
    
   
  

4. 便利なポイント

ラムダ式とコレクションメソッド:内部で何が起きている?

例えばこんな呼び出しをすると:

var adults = users.Where(u => u.Age >= 18);

メソッド Where は引数として Func<T, bool> 型を期待している。つまり、ラムダ u => u.Age >= 18 はコンパイラによってその型のデリゲートオブジェクトに変換される、ということだよ。

ブロック図:仕組み


あなたのラムダ         -->      C#コンパイラ        -->     デリゲートのオブジェクト (Func
  
   ) (u => u.Age >= 18) [型は文脈から判明] (Where() 内で呼び出せる状態) 
  

型について詳しく: 型推論

通常、デリゲートの型はコンパイラが文脈から自動で推論してくれる。例えば List<T>.FindPredicate<T> を期待しているので、コンパイラはパラメータの型をメソッドのシグネチャから知っている。

List
  
    words = new List
   
     { "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find は Predicate
    
      を期待する Console.WriteLine(result); // "three" 
    
   
  

もし文脈が不明瞭なら、コンパイラを手伝ってやる必要があるよ:

// 明示的に型を指定する
Func
  
    check = x => x > 10; 
  

戻り値がデリゲート:関数ファクトリ

メソッドがデリゲートを返すこともできる — いわゆる「関数の工場」だね。動的な振る舞いを生成するのに便利だよ。

// デリゲート(ラムダ)を返す関数
Func
  
    GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15 
  

これは、ラムダ (x => x * factor) が外側の変数 factor を捕捉する(クロージャ)ため、Func<int, int> 型のオブジェクトとして返されても動くんだ。

5. デリゲートとラムダのエラーと誤解

シグネチャの不一致

パラメータや戻り値が合わないラムダをデリゲートに代入しようとすると、コンパイラは許してくれないよ。

Func
  
    f = x => "文字列を返せません!"; // コンパイルエラー 
  

デリゲートなしでラムダを使おうとして起きるエラー

型が分からないままラムダだけ書いて呼び出そうとしてもダメだよ:

// これは動かない - コンパイラは型を推論できない
// var myFunc = x => x * 2; // CS0815 エラー 
// myFunc(10);

動かすには型を明示するか、文脈を与える必要がある:

Func
  
    myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20 
  

Action、Func、Predicate の混同

たまに間違ったデリゲート型を選んでシグネチャ不一致になることがある。覚えておくシンプルなルール:結果があるなら Func、結果がないなら Actionvoid)、論理チェックなら Predicate<T>

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION