C#ラムダ式とは?書き方・使い方・LINQでの実例を初心者向けに解説

1. C#のラムダ式とは?

1-1. ラムダ式は「名前のない関数」を短く書くための記法

C#のラムダ式とは、名前を付けずに処理を定義するための記法です。「名前のない関数」や「無名関数」と説明されることもあります。

通常のメソッドは、次のように名前を付けて定義します。

static int Double(int number){return number * 2;}

同じ処理をラムダ式で表すと、次のように書けます。

number => number * 2

左側のnumberが引数、右側のnumber * 2が実行する処理です。

ラムダ式にはメソッド名がありません。そのため、短時間だけ使用する処理や、別のメソッドへ引数として渡す処理を簡潔に記述できます。

1-2. C#でラムダ式を使う場面

C#では、主に次のような場面でラムダ式を使用します。

  • LINQでコレクションを検索・変換・並び替えする

  • FuncAction型の変数に処理を代入する

  • メソッドへ処理そのものを引数として渡す

  • ボタンのクリックなどのイベント処理を記述する

  • Task.Runで別スレッドに実行させる処理を定義する

なかでも代表的なのがLINQとの組み合わせです。

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

List<int> evenNumbers = numbers.Where(number => number % 2 == 0).ToList();

number => number % 2 == 0は、「受け取った数値が偶数かどうかを判定する処理」です。

1-3. 初心者がラムダ式でつまずきやすいポイント

初心者がラムダ式でつまずきやすい理由の一つは、引数の型や戻り値の型が省略されていることです。

x => x * 2

このコードだけを見ると、xが整数なのか小数なのか判断できません。実際の型は、ラムダ式を代入する変数や、ラムダ式を渡すメソッドの定義から推論されます。

Func<int, int> doubleNumber = x => x * 2;

Func<int, int>に代入しているため、C#コンパイラは次のように判断します。

  • 引数xの型はint

  • 戻り値の型もint

ラムダ式だけを見るのではなく、「どの型に代入されているか」「どのメソッドへ渡されているか」まで確認することが重要です。

2. ラムダ式の基本構文と読み方

2-1. 基本形は「引数 => 処理」

ラムダ式の基本形は次のとおりです。

引数 => 処理

たとえば、受け取った数値を2倍にするラムダ式は次のように書きます。

x => x * 2

このコードは、「xを受け取り、x * 2の結果を返す」と読みます。

文字列が空かどうかを判定する場合は、次のように記述できます。

text => string.IsNullOrEmpty(text)

ラムダ式は、左側でデータを受け取り、右側でそのデータを使った処理を行う構造です。

2-2. 「=>」ラムダ演算子の意味

=>はラムダ演算子と呼ばれます。

ラムダ式では、=>の左側に引数、右側に処理を記述します。

number => number + 1

日本語では、次のように読むと理解しやすいでしょう。

numberを受け取って、numberに1を加えた値を返す

=>は比較演算子の>=<=とは異なります。また、「以上」を意味する記号でもありません。

2-3. 引数が1つの場合の書き方

引数が1つで、型を省略する場合は、丸括弧を省略できます。

x => x * x

次のように丸括弧を付けても同じ意味です。

(x) => x * x

引数の型を明示するときは、丸括弧が必要です。

(int x) => x * x

一般的には、型推論が可能であれば、次の簡潔な書き方が使用されます。

x => x * x

2-4. 引数が複数ある場合の書き方

引数が複数ある場合は、引数を丸括弧で囲みます。

(x, y) => x + y

Func<int, int, int>へ代入すると、2つのint値を受け取り、int値を返す処理になります。

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

int result = add(10, 20);Console.WriteLine(result); // 30

引数の型を明示する場合は、すべての引数に型を記述します。

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

2-5. 引数がない場合の書き方

引数がないラムダ式では、空の丸括弧を記述します。

() => Console.WriteLine("実行されました")

Action型に代入する例は次のとおりです。

Action showMessage = () => Console.WriteLine("こんにちは");

showMessage();

現在日時を返す処理も、引数なしのラムダ式で記述できます。

Func<DateTime> getCurrentTime = () => DateTime.Now;

DateTime currentTime = getCurrentTime();

3. ラムダ式の書き方をサンプルコードで解説

3-1. 1行で値を返す式ラムダ

右側に1つの式を記述する形式を「式形式のラムダ式」または「式ラムダ」と呼びます。

Func<int, int> square = x => x * x;

Console.WriteLine(square(5)); // 25

式ラムダでは、式の評価結果が自動的に戻り値になります。そのため、returnは記述しません。

次の例では、文字列の長さを返しています。

Func<string, int> getLength = text => text.Length;

Console.WriteLine(getLength("CSharp")); // 6

条件判定を行う式ラムダもよく使用されます。

Func<int, bool> isAdult = age => age >= 18;

Console.WriteLine(isAdult(20)); // True

3-2. 複数行の処理を書くステートメントラムダ

複数の処理を記述する場合は、{}で処理を囲みます。この形式は「ステートメント形式のラムダ式」または「ステートメントラムダ」と呼ばれます。

Func<int, int> calculate = number =>{int doubled = number * 2;int result = doubled + 10;return result;};

Console.WriteLine(calculate(5)); // 20

ステートメントラムダでは、変数の宣言、条件分岐、繰り返し処理なども記述できます。

Func<int, string> checkNumber = number =>{if (number > 0){return "正の数";}

if (number &lt; 0){return "負の数";}return "ゼロ";

};

3-3. 戻り値があるラムダ式

戻り値があるラムダ式は、主にFunc型へ代入します。

Func<int, int> doubleNumber = number => number * 2;

Func<int, int>の最後の型引数が戻り値の型です。この例では、最初のintが引数の型、最後のintが戻り値の型です。

引数が2つの場合は、次のようになります。

Func<int, int, int> multiply = (x, y) => x * y;

最初の2つのintが引数の型、最後のintが戻り値の型です。

3-4. 戻り値がないラムダ式

戻り値がない処理は、主にAction型へ代入します。

Action<string> printMessage =message => Console.WriteLine(message);

printMessage("ラムダ式を実行しました");

複数行の処理も記述できます。

Action<string> showDetails = name =>{Console.WriteLine("ユーザー情報");Console.WriteLine($"名前: {name}");};

showDetails("山田");

Actionを使うラムダ式では値を返さないため、基本的にreturn 値;は記述できません。

3-5. 型を明示する場合と省略する場合

多くの場合、ラムダ式の引数型はコンパイラが推論できるため、省略できます。

Func<int, int> square = x => x * x;

型を明示すると、次のようになります。

Func<int, int> square = (int x) => x * x;

型推論ができない場合や、オーバーロードの選択を明確にしたい場合は、型を明示すると解決できることがあります。

Func<double, double> half = (double value) => value / 2;

引数が複数ある場合、一部の引数だけ型を明示するのではなく、すべての引数で書き方を統一します。

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

4. ラムダ式とdelegate・Func・Actionの関係

4-1. ラムダ式はデリゲートに代入して使える

デリゲートとは、メソッドや処理を変数のように扱うための型です。ラムダ式は、互換性のあるデリゲート型に変換して使用できます。

独自のデリゲート型を定義する例を見てみましょう。

delegate int Calculator(int x, int y);

Calculator add = (x, y) => x + y;Calculator subtract = (x, y) => x - y;

Console.WriteLine(add(10, 3)); // 13Console.WriteLine(subtract(10, 3)); // 7

ラムダ式そのものに具体的なデリゲート型が決まっているわけではありません。代入先や引数として渡す先によって、対応する型が決まります。

4-2. Funcで戻り値のある処理を書く

Funcは、戻り値がある処理を表す汎用デリゲートです。

引数が1つの場合は、次の形式になります。

Func<引数の型, 戻り値の型>

使用例は次のとおりです。

Func<int, string> convertToText =number => $"数値は{number}です";

string result = convertToText(100);Console.WriteLine(result);

引数が2つの場合は、次の形式です。

Func<第1引数の型, 第2引数の型, 戻り値の型>
Func<int, int, bool> isGreater =(left, right) => left > right;

Console.WriteLine(isGreater(10, 5)); // True

引数がない場合でも、戻り値があればFuncを使用できます。

Func<string> getMessage = () => "こんにちは";

4-3. Actionで戻り値のない処理を書く

Actionは、戻り値がない処理を表す汎用デリゲートです。

引数が1つの場合は、次のように記述します。

Action<string> print =message => Console.WriteLine(message);

引数が複数ある場合も使用できます。

Action<string, int> showUser =(name, age) => Console.WriteLine($"{name}さんは{age}歳です");

showUser("佐藤", 25);

引数も戻り値もない処理では、型引数を持たないActionを使用します。

Action greet = () => Console.WriteLine("こんにちは");

greet();

4-4. Predicateとの違い

Predicate<T>は、T型の値を1つ受け取り、bool値を返すデリゲートです。

Predicate<int> isEven = number => number % 2 == 0;

処理の形だけを見ると、次のFunc<T, bool>とほぼ同じです。

Func<int, bool> isEven = number => number % 2 == 0;

主な違いは、使用されるAPIと意味です。

Predicate<T>は「条件を満たすか判定する処理」であることを明確に表します。たとえば、List<T>FindRemoveAllなどで使用されます。

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

int found = numbers.Find(number => number > 3);Console.WriteLine(found); // 4

一方、LINQのWhereなどでは、通常Func<T, bool>が使用されます。

4-5. 匿名メソッドとの違い

ラムダ式が導入される前から、C#には匿名メソッドという書き方があります。

Func<int, int> square = delegate(int number){return number * number;};

同じ処理をラムダ式で書くと、より簡潔になります。

Func<int, int> square = number => number * number;

匿名メソッドではdelegateキーワードを使用しますが、ラムダ式では=>を使用します。

現在は、短く読みやすいラムダ式が使われる場面が一般的です。ただし、既存のコードを読む際に匿名メソッドが登場することもあるため、基本的な形は覚えておくとよいでしょう。

5. LINQで使うラムダ式の実例

5-1. Whereで条件に合う要素を抽出する

Whereは、条件に合う要素だけを抽出するLINQメソッドです。

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

List<int> evenNumbers = numbers.Where(number => number % 2 == 0).ToList();

Console.WriteLine(string.Join(", ", evenNumbers));// 2, 4, 6

ラムダ式の部分は、各要素が条件を満たすか判定しています。

number => number % 2 == 0

文字列のリストから、3文字以上の要素を抽出する例は次のとおりです。

List<string> names = new() { "田中", "佐々木", "森", "山本" };

List<string> results = names.Where(name => name.Length >= 3).ToList();

5-2. Selectで要素を変換する

Selectは、コレクションの各要素を別の値へ変換するLINQメソッドです。

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

List<int> doubledNumbers = numbers.Select(number => number * 2).ToList();

Console.WriteLine(string.Join(", ", doubledNumbers));// 2, 4, 6

数値を文字列へ変換することもできます。

List<string> texts = numbers.Select(number => $"番号:{number}").ToList();

Selectでは、入力要素と出力要素の型が異なっていても問題ありません。

5-3. OrderByで並び替える

OrderByは、指定した値を基準に要素を昇順で並び替えます。

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

List<int> sortedNumbers = numbers.OrderBy(number => number).ToList();

Console.WriteLine(string.Join(", ", sortedNumbers));// 1, 2, 4, 5, 8

降順で並び替える場合は、OrderByDescendingを使用します。

List<int> descendingNumbers = numbers.OrderByDescending(number => number).ToList();

オブジェクトを特定のプロパティで並び替えることもできます。

List<User> users = new(){new User { Name = "田中", Age = 30 },new User { Name = "佐藤", Age = 22 },new User { Name = "鈴木", Age = 27 }};

List<User> sortedUsers = users.OrderBy(user => user.Age).ToList();

5-4. FirstOrDefaultで最初の要素を取得する

FirstOrDefaultは、条件に合う最初の要素を取得します。

List<int> numbers = new() { 10, 20, 30, 40 };

int result = numbers.FirstOrDefault(number => number >= 25);

Console.WriteLine(result); // 30

条件に合う要素がない場合は、その型の既定値が返ります。

int result = numbers.FirstOrDefault(number => number >= 100);

Console.WriteLine(result); // 0

参照型の場合は、条件に合う要素がなければ通常nullが返るため、nullチェックが必要です。

User? user = users.FirstOrDefault(user => user.Name == "高橋");

if (user is not null){Console.WriteLine(user.Name);}

5-5. Any・Allで条件判定する

Anyは、条件を満たす要素が1つでも存在するかを判定します。

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

bool hasEvenNumber =numbers.Any(number => number % 2 == 0);

Console.WriteLine(hasEvenNumber); // True

Allは、すべての要素が条件を満たしているかを判定します。

bool allPositive =numbers.All(number => number > 0);

Console.WriteLine(allPositive); // True

要素の存在確認だけを行う場合は、Where(...).Count() > 0よりもAny(...)のほうが意図を表しやすくなります。

5-6. LINQのメソッド構文とクエリ構文の違い

LINQには、メソッド構文とクエリ構文があります。

メソッド構文では、ラムダ式を使用します。

List<int> results = numbers.Where(number => number >= 3).OrderBy(number => number).Select(number => number * 10).ToList();

同じ処理をクエリ構文で書くと、次のようになります。

List<int> results =(from number in numberswhere number >= 3orderby numberselect number * 10).ToList();

クエリ構文はSQLに似た形で記述できます。一方、すべてのLINQ操作をクエリ構文で表現できるわけではありません。

実際の開発では、メソッド構文だけを使用する場合や、クエリ構文とメソッド構文を組み合わせる場合があります。初心者は、まずWhereSelectOrderByなどのメソッド構文に慣れるとよいでしょう。

6. ラムダ式を使うメリット

6-1. コードを短くシンプルに書ける

ラムダ式を使うと、一時的な処理のために個別のメソッドを作成する必要がありません。

通常のメソッドを使う例は次のとおりです。

static bool IsEven(int number){return number % 2 == 0;}

List<int> results = numbers.Where(IsEven).ToList();

ラムダ式を使うと、次のようにその場で処理を書けます。

List<int> results = numbers.Where(number => number % 2 == 0).ToList();

短い条件や変換処理であれば、ラムダ式のほうがコード量を減らせます。

6-2. 処理の意図が読みやすくなる

ラムダ式は、処理を使用する場所の近くに記述できます。

List<User> adults = users.Where(user => user.Age >= 18).ToList();

このコードを見るだけで、「18歳以上のユーザーを抽出している」と理解できます。

別の場所に定義されたメソッドを確認しなくても処理内容が分かるため、単純な処理では可読性が高くなります。

6-3. LINQと組み合わせるとコレクション操作が簡単になる

ラムダ式とLINQを組み合わせると、検索、変換、並び替えなどの処理を連続して記述できます。

List<string> names = users.Where(user => user.Age >= 20).OrderBy(user => user.Name).Select(user => user.Name).ToList();

このコードでは、次の処理を順番に行っています。

  1. 20歳以上のユーザーを抽出する

  2. 名前順に並び替える

  3. 名前だけを取り出す

  4. リストへ変換する

for文やif文を組み合わせる場合と比べて、処理の流れを宣言的に表現できます。

6-4. 一時的な処理をその場で定義できる

一度しか使わない短い処理に名前付きメソッドを作ると、コードの確認箇所が増える場合があります。

ラムダ式なら、その処理が必要な場所で直接定義できます。

button.Click += (sender, eventArgs) =>{Console.WriteLine("ボタンがクリックされました");};

処理内容と使用場所が近くなるため、小規模で単純な処理を管理しやすくなります。

7. ラムダ式を使うときの注意点

7-1. 複雑な処理を書きすぎると読みにくくなる

ラムダ式は短い処理に適していますが、何十行もの処理を記述すると読みにくくなります。

users.Where(user =>{// 多数の条件分岐// データベースへのアクセス// ログの出力// 複雑な計算return true;});

処理が複雑な場合は、名前付きメソッドへ分離したほうが意図を伝えやすくなります。

List<User> results = users.Where(IsTargetUser).ToList();

static bool IsTargetUser(User user){// 複雑な判定処理return user.Age >= 20 && user.IsActive;}

ラムダ式は、短ければ必ずよいというものではありません。可読性を基準に使い分けましょう。

7-2. 変数のスコープとキャプチャに注意する

ラムダ式の外側で定義された変数を、ラムダ式の内側から参照できます。この仕組みを変数のキャプチャと呼びます。

int minimumAge = 20;

List<User> results = users.Where(user => user.Age >= minimumAge).ToList();

ラムダ式は、minimumAgeの値そのものではなく、外側の変数を参照します。そのため、変数の値を変更すると実行結果が変わることがあります。

int threshold = 10;Func<int, bool> isGreater = number => number > threshold;

Console.WriteLine(isGreater(15)); // True

threshold = 20;

Console.WriteLine(isGreater(15)); // False

意図しない値を参照しないように、変数がいつ変更されるかを確認する必要があります。また、キャプチャによって内部的なオブジェクトが生成される場合があるため、性能が重要な箇所では注意しましょう。

7-3. null参照や例外処理に注意する

ラムダ式の内部でも、通常のメソッドと同じようにnull参照や例外が発生します。

List<string?> names = new() { "田中", null, "佐藤" };

List<int> lengths = names.Select(name => name.Length).ToList();

このコードでは、namenullのときに例外が発生します。

nullを除外してから処理する方法があります。

List<int> lengths = names.Where(name => name is not null).Select(name => name!.Length).ToList();

null条件演算子を使用する方法もあります。

List<int> lengths = names.Select(name => name?.Length ?? 0).ToList();

ラムダ式だから特別な例外処理が行われるわけではありません。入力値や処理内容に応じて、適切なnullチェックが必要です。

7-4. 初心者はまずLINQの基本パターンから覚える

ラムダ式には、デリゲート、式ツリー、変数のキャプチャなど、発展的な仕組みがあります。しかし、最初からすべてを理解する必要はありません。

まずは次の基本パターンを覚えるとよいでしょう。

// 条件に合う要素を抽出items.Where(item => 条件)

// 各要素を変換items.Select(item => 変換後の値)

// 昇順に並び替えitems.OrderBy(item => 並び替えの基準)

// 条件に合う要素があるか判定items.Any(item => 条件)

// 最初の要素を取得items.FirstOrDefault(item => 条件)

これらを実際に書きながら、引数 => 処理という基本構造に慣れることが重要です。

8. ラムダ式の実践例

8-1. Listから条件に合うデータを取り出す

整数のリストから、10以上の偶数を取り出してみましょう。

List<int> numbers = new(){3, 8, 10, 12, 15, 18, 21};

List<int> results = numbers.Where(number => number >= 10 && number % 2 == 0).ToList();

Console.WriteLine(string.Join(", ", results));// 10, 12, 18

ラムダ式では、&&を使って複数の条件を組み合わせられます。

条件が長くなる場合は、改行すると読みやすくなります。

List<int> results = numbers.Where(number =>number >= 10 &&number % 2 == 0).ToList();

8-2. オブジェクトの一覧を絞り込む

ユーザーを表すクラスを用意します。

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

ユーザーの一覧から、20歳以上かつ有効なユーザーを抽出します。

List<User> users = new(){new User { Name = "田中", Age = 25, IsActive = true },new User { Name = "佐藤", Age = 17, IsActive = true },new User { Name = "鈴木", Age = 32, IsActive = false },new User { Name = "高橋", Age = 28, IsActive = true }};

List<User> activeAdults = users.Where(user => user.Age >= 20 && user.IsActive).ToList();

foreach (User user in activeAdults){Console.WriteLine(user.Name);}

実行結果は次のとおりです。

田中高橋

8-3. 文字列リストを変換する

文字列のリストをすべて大文字へ変換する例です。

List<string> languages = new(){"csharp","java","python"};

List<string> upperLanguages = languages.Select(language => language.ToUpperInvariant()).ToList();

Console.WriteLine(string.Join(", ", upperLanguages));// CSHARP, JAVA, PYTHON

前後の空白を削除してから大文字にすることもできます。

List<string> normalizedLanguages = languages.Select(language => language.Trim().ToUpperInvariant()).ToList();

複数の処理が単純なメソッド呼び出しでつながる場合は、式ラムダのまま簡潔に記述できます。

8-4. イベント処理でラムダ式を使う

ラムダ式は、GUIアプリケーションなどのイベント処理でも使用できます。

button.Click += (sender, eventArgs) =>{messageLabel.Text = "ボタンがクリックされました";};

イベントハンドラーを短く書ける点がメリットです。

ただし、後からイベントを解除する必要がある場合は注意が必要です。次のように同じ内容のラムダ式を書いても、通常は登録したデリゲートと別のものとして扱われます。

button.Click -= (sender, eventArgs) =>{messageLabel.Text = "ボタンがクリックされました";};

解除する必要がある場合は、デリゲートを変数へ保存しておきます。

EventHandler handler = (sender, eventArgs) =>{messageLabel.Text = "ボタンがクリックされました";};

button.Click += handler;

// 必要になったときに解除button.Click -= handler;

8-5. Task.Runで非同期処理に使う

Task.Runへラムダ式を渡すと、指定した処理をスレッドプール上で実行できます。

await Task.Run(() =>{for (int i = 0; i < 5; i++){Console.WriteLine($"処理中: {i}");}});

戻り値がある処理も記述できます。

int result = await Task.Run(() =>{int total = 0;

for (int i = 1; i &lt;= 100; i++){total += i;}return total;

});

Console.WriteLine(result); // 5050

ラムダ式の中で非同期メソッドを呼び出す場合は、asyncを付けられます。

await Task.Run(async () =>{await SomeAsyncMethod();Console.WriteLine("処理が完了しました");});

ただし、すでに非同期APIが用意されているI/O処理を、単にTask.Runで包む必要は通常ありません。Task.Runは、主にCPU負荷の高い同期処理を別スレッドで実行したい場合に検討します。

9. ラムダ式でよくあるエラーと解決方法

9-1. 型推論できない場合の対処法

ラムダ式は、代入先や引数として渡す先の型を利用して引数型を推論します。そのため、型を判断する情報が不足しているとエラーになることがあります。

たとえば、ラムダ式を型が決まっていない変数へ直接代入することはできません。

// 型を判断できないvar square = x => x * x;

デリゲート型を明示すると解決できます。

Func<int, int> square = x => x * x;

引数の型を明示する方法もあります。

Func<int, int> square = (int x) => x * x;

メソッドのオーバーロードによって型推論が曖昧になる場合は、ラムダ式を一度明示的な型の変数へ代入すると分かりやすくなります。

9-2. 戻り値の型が合わない場合の対処法

Funcの戻り値型と、ラムダ式が返す値の型が一致しない場合はエラーになります。

// stringを返しているためエラーFunc<int, int> convert = number => number.ToString();

文字列を返したい場合は、戻り値型をstringに変更します。

Func<int, string> convert =number => number.ToString();

反対に、整数を返したいのであれば、ラムダ式の処理を変更します。

Func<int, int> doubleNumber =number => number * 2;

エラーが出たときは、Funcの最後の型引数と、returnまたは式の結果の型を確認しましょう。

9-3. 複数行ラムダでreturnを書き忘れた場合

戻り値のあるステートメントラムダでは、returnが必要です。

// 戻り値がないためエラーFunc<int, int> calculate = number =>{int result = number * 2;};

次のようにreturnを追加します。

Func<int, int> calculate = number =>{int result = number * 2;return result;};

条件分岐がある場合は、すべての実行経路で値が返るようにします。

Func<int, string> check = number =>{if (number >= 0){return "0以上";}

return "負の数";

};

1行で表せる処理なら、式ラムダにするとreturnの書き忘れを防げます。

Func<int, int> calculate = number => number * 2;

9-4. LINQで条件式の書き方を間違えた場合

WhereAnyへ渡すラムダ式は、最終的にbool値を返す必要があります。

次のコードでは、number * 2が整数を返すため、Whereの条件として使用できません。

// boolではなくintを返しているためエラーList<int> results = numbers.Where(number => number * 2).ToList();

条件式を記述して、bool値を返すようにします。

List<int> results = numbers.Where(number => number > 2).ToList();

代入演算子=と比較演算子==の混同にも注意が必要です。

List<User> results = users.Where(user => user.Name == "田中").ToList();

複数条件では、AND条件に&&、OR条件に||を使用します。

List<User> results = users.Where(user => user.Age >= 20 && user.IsActive).ToList();

10. C#ラムダ式に関するよくある質問

10-1. ラムダ式とメソッドの違いは?

通常のメソッドは名前を持ち、クラスや構造体などのメンバーとして定義できます。

static int Double(int number){return number * 2;}

ラムダ式は、名前を持たない処理として、その場で定義します。

Func<int, int> doubleNumber =number => number * 2;

何度も使用する処理、複雑な処理、単体テストしたい処理には、名前付きメソッドが適しています。一度だけ使う短い処理や、LINQへ渡す条件・変換処理にはラムダ式が適しています。

10-2. ラムダ式とLINQは同じもの?

ラムダ式とLINQは同じものではありません。

ラムダ式は、名前のない処理を記述するための構文です。LINQは、コレクションやデータソースを検索・変換するための仕組みです。

LINQのメソッド構文では、条件や変換方法を指定するためにラムダ式がよく使われます。

List<int> results = numbers.Where(number => number > 10).ToList();

このコードでは、WhereがLINQのメソッド、number => number > 10がラムダ式です。

ラムダ式は、LINQ以外にもイベント処理、デリゲート、コールバック、非同期処理などで使用できます。

10-3. ラムダ式はいつ使うべき?

ラムダ式は、次のような場面に適しています。

  • 処理が短く、内容をその場で理解できる

  • LINQの条件や変換処理を指定する

  • 一度しか使わない処理を定義する

  • メソッドへ処理を引数として渡す

  • イベントハンドラーを簡潔に記述する

一方、処理が長い場合、複数箇所で再利用する場合、処理に分かりやすい名前を付けたい場合は、通常のメソッドへ分離したほうが読みやすくなります。

10-4. 初心者はどこまで覚えればいい?

初心者は、まず次の内容を理解すれば十分です。

// 基本形引数 => 処理

// 引数が1つx => x * 2

// 引数が複数(x, y) => x + y

// 引数がない() => Console.WriteLine("実行")

// 複数行x =>{int result = x * 2;return result;}

あわせて、次のLINQメソッドを使えるようにしておくと、実際の開発で役立ちます。

  • Where

  • Select

  • OrderByOrderByDescending

  • FirstOrDefault

  • AnyAll

その後、必要に応じてFuncAction、変数のキャプチャ、式ツリーなどを学ぶと理解を深められます。

10-5. ラムダ式はパフォーマンスに影響する?

ラムダ式を使用すると、デリゲートの生成や呼び出しに伴うコストが発生する場合があります。また、外側の変数をキャプチャすると、追加のオブジェクトが内部的に生成されることがあります。

LINQでは、列挙処理や中間オブジェクトの生成などによって、単純なfor文より処理時間やメモリ使用量が増える場合もあります。

ただし、一般的なアプリケーションでは、ラムダ式によるわずかな差よりも、コードの読みやすさや保守性が重要になることが多いでしょう。

大量データを何度も処理する箇所や、短時間に繰り返し呼び出される処理など、性能が重要な部分では実際に計測して判断する必要があります。測定せずに、すべてのラムダ式やLINQを避ける必要はありません。

まとめ

C#のラムダ式は、名前のない処理を引数 => 処理という形で簡潔に記述するための構文です。

number => number * 2

引数が複数ある場合や、引数がない場合は次のように記述します。

(x, y) => x + y

() => Console.WriteLine("こんにちは")

戻り値のある処理にはFunc、戻り値のない処理にはActionを使用できます。

Func<int, int> square = x => x * x;

Action<string> print =message => Console.WriteLine(message);

また、ラムダ式はLINQと組み合わせることで、コレクションの抽出、変換、並び替え、条件判定を分かりやすく記述できます。

List<string> names = users.Where(user => user.Age >= 20).OrderBy(user => user.Name).Select(user => user.Name).ToList();

初心者は、まずWhereSelectOrderByFirstOrDefaultAnyなどで使われる基本的なラムダ式から覚えるのがおすすめです。

ラムダ式は便利ですが、複雑な処理を詰め込みすぎると、かえって読みにくくなります。短い処理にはラムダ式、長く複雑な処理には名前付きメソッドというように、可読性を基準に使い分けましょう。