C#: ラムダ式

1. ラムダ式の概要

ラムダ式とは、簡潔な匿名関数の構文であり、より少ないコードでインライン関数を定義することができます。これはC#の関数型プログラミングの中核となる機能であり、LINQクエリの基盤となっています。


2. ラムダ構文

ラムダ式の基本的な構文は次のとおりです:parameters => expression or statement block=>は「~へ」と読み、左辺のパラメータを右辺の演算に割り当てます。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Main()
    {
        Func`<int, int>` square = x => x * x;
        Console.WriteLine(square(5));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
25

3. 1行および複数行のラムダ式

1行のラムダ式は、returnキーワードや中括弧を指定せずに、式の結果を直接返します。複数行のラムダ式では、ステートメントブロックを囲むために中括弧が必要であり、明示的なreturnステートメントも指定する必要があります。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Main()
    {
        Func`<int, int>` singleLine = x => x * x;
        Func`<int, int>` multiLine = x =>
        {
            int result = x * x;
            return result;
        };
        Console.WriteLine(singleLine(4));
        Console.WriteLine(multiLine(4));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
16
16

4. 型推論

通常、コンパイラは文脈からラムダ式のパラメータの型を推論できるため、明示的な宣言は必要ありません。文脈から型が判別できない場合は、手動で型を指定することができます。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Main()
    {
        Func`<int, int>` inferred = x => x + 1;
        Func`<int, int>` explicitType = (int x) => x + 1;
        Console.WriteLine(inferred(10));
        Console.WriteLine(explicitType(10));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
11
11

5. ラムダ式とデリゲート

ラムダ式はデリゲート型に代入できるため、デリゲートインスタンスを作成する上で最も簡潔な方法となります。従来の匿名メソッドや明示的なデリゲートのインスタンス化と比較して、ラムダ式の方が記述が簡潔です。

▶ サンプル

CSHARP
using System;

delegate int Transform(int x);

class Program
{
    static void Main()
    {
        Transform doubleIt = x => x * 2;
        Transform addTen = x => x + 10;
        Console.WriteLine(doubleIt(3));
        Console.WriteLine(addTen(3));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
6
13

6. func および action を使用したラムダ式

Func<T, TResult>は戻り値を持つジェネリックデリゲートを表し、Action<T>は戻り値を持たないジェネリックデリゲートを表します。これらとラムダ式を組み合わせて使用することは非常に一般的です。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Main()
    {
        Func`<int, int, int>` add = (a, b) => a + b;
        Action```<string>``` greet = msg => Console.WriteLine(msg);
        Console.WriteLine(add(3, 7));
        greet("Hello");
    }
}
▶ 試してみよう
TEXT 📖 参照専用
10
Hello

7. ラムダ式と述語

Predicate<T>は、boolを返すデリゲートであり、フィルタリングや条件チェックによく使用されます。ラムダ式を使えば、条件を非常に簡潔に定義できます。

▶ サンプル

CSHARP
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var numbers = new List```<int>``` { 1, 2, 3, 4, 5, 6 };
        Predicate```<int>``` isEven = x => x % 2 == 0;
        var evens = numbers.FindAll(isEven);
        Console.WriteLine(string.Join(", ", evens));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
2, 4, 6

8. クロージャ

ラムダ式は外側の変数をキャプチャすることができ、これを「クロージャ」と呼びます。キャプチャされた変数の存続期間はラムダ式とともに延長され、元のスコープが終了した後も、それらの変数は存続し続けます。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Main()
    {
        int factor = 3;
        Func`<int, int>` multiply = x => x * factor;
        Console.WriteLine(multiply(5));
        factor = 10;
        Console.WriteLine(multiply(5));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
15
50
💡 クロージャは変数の値ではなく、変数そのものをキャプチャするため、外側の変数を変更すると、ラムダ式の実行結果に影響が及びます。

▶ サンプル

CSHARP
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var actions = new List`<Action>`();
        for (int i = 0; i < 3; i++)
        {
            int copy = i;
            actions.Add(() => Console.WriteLine(copy));
        }
        foreach (var action in actions)
        {
            action();
        }
    }
}
▶ 試してみよう
TEXT 📖 参照専用
0
1
2
⚠️ コピーを作成せずにループ変数iを直接キャプチャすると、すべてのラムダ式が同じiを共有することになり、最終的な出力はすべて3になってしまいます。ローカルコピーを作成することで、この問題を回避できます。


9. LINQにおけるラムダ式 — 入門編

ラムダ式は、LINQ クエリの中心的な要素です。多くの LINQ メソッドは Func または Expression パラメータを受け付けますが、ラムダ式を使用することで、条件、プロジェクション、およびソートロジックをインラインで簡潔に記述することができます。

▶ サンプル

CSHARP
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = new List```<int>``` { 3, 7, 1, 9, 4, 6 };
        var big = numbers.Where(x => x > 5);
        var doubled = numbers.Select(x => x * 2);
        var sorted = numbers.OrderBy(x => x);
        Console.WriteLine(string.Join(", ", big));
        Console.WriteLine(string.Join(", ", doubled));
        Console.WriteLine(string.Join(", ", sorted));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
7, 9, 6
6, 14, 2, 18, 8, 12
1, 3, 4, 6, 7, 9

10. 式ツリーの概要

Expression<TDelegate>にラムダ式が割り当てられると、コンパイラは実行可能コードではなく、データ構造(式木)を生成します。式木は解析や変更が可能であり、SQLクエリなどの他の形式に変換することもできます。これが、LINQ to SQLなどの技術の根底にある仕組みです。

▶ サンプル

CSHARP
using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func`<int, int>`> expr = x => x * x + 1;
        Console.WriteLine(expr.Body);
        Console.WriteLine(expr.Parameters[0]);
        var compiled = expr.Compile();
        Console.WriteLine(compiled(4));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
(x * x) + 1
x
17

ℹ️ Expression<Func``<int,int>``> および Func<int,int> は異なるタイプです。前者はデータ構造であり、後者は実行可能なデリゲートです。

❓ よくある質問

Q ラムダ式と匿名メソッドの違いは何ですか?
A ラムダ式の構文はより簡潔で、型推論や式ツリーをサポートしています。一方、匿名メソッドの構文は冗長で、サポートされている機能も少ないです。
Q Lambdaのパラメータ型は、どのような場合に明示的に指定する必要がありますか?
A コンパイラが文脈から型を推論できない場合です。たとえば、パラメータ型の曖昧性やオーバーロードの解決における競合などが挙げられます。
Q クロージャによってキャプチャされた変数はいつ解放されますか?
A キャプチャされた変数は、それらを参照しているすべてのラムダ式がガベージコレクションの対象となった後にのみ解放されます。
Q Expression と Func は互いに変換できますか?
A Expression は Compile() を使って Func に変換できますが、Func を直接 Expression に戻すことはできません。

📖 まとめ

📝 練習問題

  1. 2つの数値の最大値を計算するFunc<double, double, double>ラムダ関数を作成してください
  2. Action<string>ラムダ関数を使用して、タイムスタンプ付きのログメッセージを出力する
  3. クロージャの例を作成してください:呼び出されるたびに1ずつ増える整数を返すカウンター関数を作成してください
  4. Lambda関数でWhereを使用し、文字列のリストから5文字を超える文字列をフィルタリングする
  5. Func<int, bool>のラムダ式をFunc<int, bool>Expression<Func``<int, bool>``>の両方に割り当て、それぞれのGetType()の結果を出力する
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%