csharp lambda完全ガイド:書き方・使いどころ・LINQでの実践例を初心者向けに解説

はじめに

C#を学び始めると、x => x * 2user => user.Age >= 20 のような書き方を目にすることがあります。これがC#のラムダ式です。

「csharp lambda」と検索する人の多くは、ラムダ式の書き方、=> の意味、FuncAction との関係、LINQでの使い方がよく分からずに調べているのではないでしょうか。

ラムダ式は、最初は記号が多く見えて難しく感じるかもしれません。しかし本質はとてもシンプルです。C#のラムダ式は、処理を短く書いて、変数に入れたり、メソッドの引数として渡したりするための記法です。

特にLINQではラムダ式が頻繁に使われます。たとえば、配列から偶数だけを取り出す処理は次のように書けます。

C#
int[] numbers = { 1, 2, 3, 4, 5, 6 };

var evenNumbers = numbers.Where(n => n % 2 == 0);

foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}

n => n % 2 == 0 の部分がラムダ式です。意味は「nを受け取り、nが偶数かどうかを判定する処理」です。

この記事では、csharp lambdaの基本構文から、デリゲート、Func、Action、Predicate、LINQでの実践例、よくあるエラー、読みやすく書くコツ、Expressionやstaticラムダといった発展的な内容まで、初心者にも分かるように順番に解説します。

1. csharp lambdaとは?初心者が最初に押さえるべき全体像

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

C#のラムダ式は、ひと言でいうと「名前のない関数」を短く書くための記法です。Microsoft Learnでも、ラムダ式は匿名関数を作成するために使う構文として説明されています。

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

C#
static int Double(int x)
{
return x * 2;
}

このメソッドは、Double という名前を持っています。

一方、ラムダ式では同じような処理を次のように書けます。

C#
Func<int, int> doubleValue = x => x * 2;

この x => x * 2 がラムダ式です。Double というメソッド名を付けなくても、「xを受け取って、xの2倍を返す処理」を表現できます。

ラムダ式は、単独で存在するというよりも、次のような形で使われることが多いです。

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

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

この例では、square という変数にラムダ式を代入しています。square(5) と呼び出すと、5 * 5 が計算されて 25 が返ります。

1-2. ラムダ式を使うとコードが読みやすくなる理由

ラムダ式を使うと、短い処理をその場で書けるようになります。

たとえば、数値リストから偶数だけを取り出す処理を、ラムダ式なしで書くと次のようになります。

C#
static bool IsEven(int n)
{
return n % 2 == 0;
}

int[] numbers = { 1, 2, 3, 4, 5, 6 };

var evenNumbers = numbers.Where(IsEven);

もちろん、この書き方も正しいです。ただし、IsEven がその場でしか使われない短い処理であれば、わざわざメソッドとして定義するのは少し大げさです。

ラムダ式を使うと、次のように書けます。

C#
int[] numbers = { 1, 2, 3, 4, 5, 6 };

var evenNumbers = numbers.Where(n => n % 2 == 0);

「numbersの中から、nが偶数であるものを取り出す」という意図が1行で分かります。

ラムダ式が読みやすくなる主な理由は、処理の内容を使う場所の近くに書けるからです。特にLINQでは、「どの条件で絞るのか」「何を取り出すのか」「何で並び替えるのか」をラムダ式で直感的に表現できます。

1-3. 「csharp lambda」で検索する人が混同しやすいAWS Lambdaとの違い

「lambda」という言葉で混同しやすいものに、AWS Lambdaがあります。

C#のラムダ式は、C#言語の構文です。x => x * 2 のように、関数のような処理を短く書くために使います。

一方、AWS LambdaはAmazon Web Servicesが提供するクラウドサービスの一種です。AWS Lambdaは、サーバーを自分で管理せずにコードを実行するためのサーバーレス実行環境です。

つまり、両者はまったく別のものです。

C#のラムダ式:
C#のコード内で使う「名前のない関数」の書き方

AWS Lambda:
クラウド上でコードを実行するためのサービス

ただし、AWS Lambda上でC#のコードを動かすことはあります。そのため、AWS Lambdaの中でC#ラムダ式を書くことも可能です。しかし、「csharp lambda」と検索して => の書き方を知りたい場合は、基本的にはC#のラムダ式を学べば問題ありません。

1-4. ラムダ式・匿名メソッド・デリゲート・LINQの関係

ラムダ式を理解するには、周辺用語との関係を整理しておくと分かりやすくなります。

ラムダ式は、匿名関数を簡潔に書くための構文です。

匿名メソッドは、ラムダ式が登場する前からある「名前のないメソッド」を書く方法です。

C#
Func<int, int> square = delegate (int x)
{
return x * x;
};

これをラムダ式で書くと、次のようになります。

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

デリゲートは、メソッドやラムダ式を代入できる型です。Func<int, int>Action<string> もデリゲートの一種です。

LINQは、配列やリストなどのコレクションを検索、抽出、変換、並び替え、集計するための機能です。LINQのメソッド構文では、条件や変換処理をラムダ式で渡すことが多いです。Microsoft Learnでも、LINQの作成時にラムダ式を使用できることが示されています。

関係をまとめると、次のようになります。

デリゲート:
処理を入れられる型

ラムダ式:
デリゲートに代入できる短い関数の書き方

匿名メソッド:
ラムダ式より古い、名前のないメソッドの書き方

LINQ:
ラムダ式をよく使う代表的な機能

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

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

C#のラムダ式では、=> という記号を使います。これはラムダ演算子、またはラムダ宣言演算子と呼ばれます。

基本形は次のとおりです。

C#
引数 => 処理

たとえば、次のラムダ式を見てみましょう。

C#
x => x * 2

これは、次のように読みます。

xを受け取って、x * 2 を返す

もう少し具体的に書くと、次のような通常メソッドに近い意味です。

C#
static int Double(int x)
{
return x * 2;
}

=> の左側には入力、右側には処理を書きます。

C#
n => n > 10

これは「nを受け取って、nが10より大きいかどうかを返す」という意味です。

C#
name => name.ToUpper()

これは「nameを受け取って、大文字に変換して返す」という意味です。

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

引数が1つだけの場合、ラムダ式では引数のかっこを省略できます。

C#
Func<int, int> doubleValue = x => x * 2;

これは次のようにも書けます。

C#
Func<int, int> doubleValue = (x) => x * 2;

どちらも同じ意味です。

ただし、一般的には、引数が1つで型を明示しない場合は、かっこを省略して書くことが多いです。

C#
var names = new List<string> { "Alice", "Bob", "Charlie" };

var shortNames = names.Where(name => name.Length <= 3);

この例では、name => name.Length <= 3 がラムダ式です。「nameを受け取り、文字数が3以下かどうかを返す」という意味になります。

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

引数が2つ以上ある場合は、かっこが必要です。

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

Console.WriteLine(add(3, 5)); // 8

Func<int, int, int> は、「intを2つ受け取り、intを返す処理」を表します。

ラムダ式の部分は次のとおりです。

C#
(x, y) => x + y

これは「xとyを受け取り、x + y を返す」という意味です。

LINQでも、複数引数のラムダ式を使うことがあります。たとえば、Select では要素とインデックスを受け取れるオーバーロードがあります。

C#
var fruits = new List<string> { "Apple", "Banana", "Orange" };

var indexedFruits = fruits.Select((fruit, index) => $"{index}: {fruit}");

foreach (var item in indexedFruits)
{
Console.WriteLine(item);
}

出力は次のようになります。

0: Apple
1: Banana
2: Orange

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

引数がないラムダ式では、空のかっこ () を使います。

C#
Action sayHello = () => Console.WriteLine("Hello!");

sayHello();

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

現在時刻を返すラムダ式なら、次のように書けます。

C#
Func<DateTime> getNow = () => DateTime.Now;

Console.WriteLine(getNow());

() は「引数なし」を意味します。つまり、() => DateTime.Now は「何も受け取らず、現在時刻を返す処理」です。

2-5. 戻り値があるラムダ式の書き方

戻り値があるラムダ式は、右側に返したい値を表す式を書きます。

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

int result = square(4);

Console.WriteLine(result); // 16

このような1行のラムダ式では、return を書きません。x * x の結果がそのまま戻り値になります。

文字列を返す例も見てみましょう。

C#
Func<string, string> greet = name => $"こんにちは、{name}さん";

Console.WriteLine(greet("田中"));

出力は次のようになります。

こんにちは、田中さん

条件判定のように bool を返すラムダ式もよく使います。

C#
Func<int, bool> isAdult = age => age >= 20;

Console.WriteLine(isAdult(18)); // False
Console.WriteLine(isAdult(25)); // True

2-6. 処理が複数行になるステートメントラムダの書き方

ラムダ式の処理が複数行になる場合は、 { } を使ってステートメントラムダとして書きます。

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

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

複数行のラムダ式では、戻り値がある場合に return が必要です。

戻り値がない場合は、Action を使えます。

C#
Action<string> printMessage = message =>
{
Console.WriteLine("メッセージを表示します");
Console.WriteLine(message);
};

printMessage("ラムダ式の練習です");

ステートメントラムダは便利ですが、長くなりすぎると読みにくくなります。Microsoft Learnでも、ステートメントラムダの本体は任意の数のステートメントを含められるものの、実際には通常2つか3つ以下にすることが多いと説明されています。

3. ラムダ式を理解するための前提知識

3-1. デリゲートとは何か

デリゲートとは、メソッドを参照できる型です。もう少し初心者向けに言うと、「処理を変数のように扱うための型」です。

たとえば、次のようなメソッドがあるとします。

C#
static int Double(int x)
{
return x * 2;
}

このメソッドを Func<int, int> 型の変数に代入できます。

C#
Func<int, int> operation = Double;

Console.WriteLine(operation(5)); // 10

ラムダ式も同じように代入できます。

C#
Func<int, int> operation = x => x * 2;

Console.WriteLine(operation(5)); // 10

つまり、デリゲート型があるから、ラムダ式を変数に入れたり、メソッドの引数として渡したりできます。

自分でデリゲート型を定義することもできます。

C#
delegate int Calculator(int x, int y);

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

Console.WriteLine(add(3, 4)); // 7

ただし、実務では FuncActionPredicate を使うことが多いため、最初はこの3つを覚えるとよいでしょう。

3-2. Funcの使い方

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

C#
Func<入力型, 戻り値の型>

引数が1つで戻り値がある場合は、次のように書きます。

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

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

この場合、最初の int が引数の型、最後の int が戻り値の型です。

C#
Func<int, int>

これは「intを受け取り、intを返す処理」です。

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

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

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

Func<int, int, int> は、「intを2つ受け取り、intを返す処理」です。Func では、最後の型が戻り値の型になります。

文字列を受け取って文字数を返す例です。

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

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

3-3. Actionの使い方

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

たとえば、文字列を受け取って表示するだけの処理は、戻り値がありません。

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

print("Hello Lambda");

引数がない場合は、次のように書きます。

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

greet();

引数が複数ある場合も使えます。

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

showUser("佐藤", 30);

Action は、イベント処理、ログ出力、コールバックなどでよく使われます。

FuncAction の違いは、戻り値があるかどうかです。

Func:
戻り値がある

Action:
戻り値がない

3-4. Predicateの使い方

Predicate<T> は、ある値を受け取って bool を返すデリゲートです。

つまり、条件判定に使います。

C#
Predicate<int> isEven = n => n % 2 == 0;

Console.WriteLine(isEven(4)); // True
Console.WriteLine(isEven(5)); // False

Predicate<int> は、次の Func とほぼ同じような意味です。

C#
Func<int, bool> isEven = n => n % 2 == 0;

どちらも「intを受け取り、boolを返す処理」です。

Predicate<T> は、List<T>.FindList<T>.RemoveAll などで使われます。

C#
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

numbers.RemoveAll(n => n % 2 == 0);

Console.WriteLine(string.Join(", ", numbers)); // 1, 3, 5

この例では、偶数に該当する要素をリストから削除しています。

3-5. 型推論によりラムダ式が短く書ける仕組み

ラムダ式では、多くの場合、引数の型を書かなくてもコンパイラが推論してくれます。

たとえば、次のコードを見てください。

C#
Func<int, bool> isEven = n => n % 2 == 0;

n の型は書いていません。しかし、左側に Func<int, bool> と書かれているため、コンパイラは「nはintだ」と判断できます。

明示的に型を書くこともできます。

C#
Func<int, bool> isEven = (int n) => n % 2 == 0;

LINQでも型推論が働きます。

C#
var names = new List<string> { "Alice", "Bob", "Charlie" };

var result = names.Where(name => name.Length > 3);

namesList<string> なので、Where の中の namestring だと推論されます。そのため、name.Length と書けます。

型推論により、ラムダ式は短く読みやすく書けます。ただし、型が推論できない場面では、明示的に型を書く必要があります。

4. C#ラムダ式の具体的な書き方サンプル

4-1. 数値を計算するラムダ式

数値計算は、ラムダ式の基本を理解するのに向いています。

C#
Func<int, int> doubleValue = x => x * 2;

Console.WriteLine(doubleValue(10)); // 20

2つの数値を足す例です。

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

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

消費税込み価格を計算する例です。

C#
Func<decimal, decimal> addTax = price => price * 1.10m;

Console.WriteLine(addTax(1000m)); // 1100.00

decimal を使う場合、数値の後ろに m を付けます。金額計算では、double より decimal が使われることが多いです。

4-2. 文字列を加工するラムダ式

文字列を加工するラムダ式もよく使います。

C#
Func<string, string> toUpper = text => text.ToUpper();

Console.WriteLine(toUpper("csharp lambda")); // CSHARP LAMBDA

前後の空白を削除する例です。

C#
Func<string, string> trim = text => text.Trim();

Console.WriteLine(trim(" hello ")); // hello

名前に敬称を付ける例です。

C#
Func<string, string> addSan = name => $"{name}さん";

Console.WriteLine(addSan("山田")); // 山田さん

複数行で少し複雑にすることもできます。

C#
Func<string, string> normalizeName = name =>
{
string trimmed = name.Trim();
string upper = trimmed.ToUpper();
return upper;
};

Console.WriteLine(normalizeName(" taro ")); // TARO

4-3. 条件判定を行うラムダ式

条件判定では、bool を返すラムダ式を使います。

C#
Func<int, bool> isEven = n => n % 2 == 0;

Console.WriteLine(isEven(8)); // True
Console.WriteLine(isEven(9)); // False

20歳以上かどうかを判定する例です。

C#
Func<int, bool> isAdult = age => age >= 20;

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

文字列が空でないかを判定する例です。

C#
Func<string, bool> hasValue = text => !string.IsNullOrWhiteSpace(text);

Console.WriteLine(hasValue("hello")); // True
Console.WriteLine(hasValue(" ")); // False

LINQの Where では、このような条件判定のラムダ式を頻繁に使います。

4-4. オブジェクトのプロパティを使うラムダ式

実務では、数値や文字列だけでなく、オブジェクトのプロパティを使うラムダ式がよく登場します。

次のような User クラスを考えます。

C#
public class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
}

ユーザー一覧から20歳以上のユーザーだけを抽出します。

C#
var users = new List<User>
{
new User { Name = "田中", Age = 18 },
new User { Name = "佐藤", Age = 25 },
new User { Name = "鈴木", Age = 30 }
};

var adults = users.Where(user => user.Age >= 20);

foreach (var user in adults)
{
Console.WriteLine(user.Name);
}

user => user.Age >= 20 は、「userを受け取り、Ageが20以上かどうかを返す」という意味です。

名前だけを取り出す場合は、Select を使います。

C#
var names = users.Select(user => user.Name);

foreach (var name in names)
{
Console.WriteLine(name);
}

このように、ラムダ式ではオブジェクトのプロパティにアクセスして、条件判定や変換を行えます。

4-5. 変数をキャプチャするラムダ式

ラムダ式は、外側の変数を使うことができます。これをキャプチャと呼びます。

C#
int threshold = 20;

Func<int, bool> isGreaterThanThreshold = x => x > threshold;

Console.WriteLine(isGreaterThanThreshold(25)); // True
Console.WriteLine(isGreaterThanThreshold(10)); // False

この例では、ラムダ式の外側で定義された threshold を、ラムダ式の中で使っています。

LINQでもよく使います。

C#
int minAge = 20;

var adults = users.Where(user => user.Age >= minAge);

minAge の値を変えると、判定条件も変わります。

C#
int minAge = 20;

Func<User, bool> filter = user => user.Age >= minAge;

minAge = 30;

var result = users.Where(filter);

このように、ラムダ式は変数そのものを参照するため、後から変数の値が変わると結果に影響する場合があります。便利な一方で、意図しない動作の原因にもなるため注意が必要です。

4-6. ラムダ式をメソッドの引数として渡す例

ラムダ式は、メソッドの引数として渡せます。これは非常に重要な使い方です。

たとえば、次のようなメソッドを作ります。

C#
static void PrintNumbers(IEnumerable<int> numbers, Func<int, bool> condition)
{
foreach (var number in numbers)
{
if (condition(number))
{
Console.WriteLine(number);
}
}
}

このメソッドは、数値一覧と条件を受け取り、条件に合う数値だけを表示します。

使う側では、条件をラムダ式で渡せます。

C#
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

PrintNumbers(numbers, n => n % 2 == 0);

奇数だけ表示したい場合は、ラムダ式を変えるだけです。

C#
PrintNumbers(numbers, n => n % 2 == 1);

10より大きい数だけ表示する場合も同じです。

C#
PrintNumbers(numbers, n => n > 10);

このように、処理の一部を外から渡せるようにすると、柔軟なメソッドを作れます。

5. ラムダ式はどこで使う?代表的な使いどころ

5-1. LINQでコレクションを検索・抽出する

C#ラムダ式の最も代表的な使いどころはLINQです。

Where を使うと、条件に合う要素だけを抽出できます。

C#
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

var evenNumbers = numbers.Where(n => n % 2 == 0);

文字列リストから、A で始まるものだけを取り出す例です。

C#
var names = new List<string> { "Alice", "Bob", "Anna", "Tom" };

var aNames = names.Where(name => name.StartsWith("A"));

オブジェクト一覧から条件に合うものを探す例です。

C#
var activeUsers = users.Where(user => user.Age >= 20);

このように、LINQとラムダ式を組み合わせると、コレクション操作を短く分かりやすく書けます。

5-2. 並び替えやグループ化を簡潔に書く

ラムダ式は、並び替えにもよく使います。

C#
var orderedUsers = users.OrderBy(user => user.Age);

これは、Age の昇順でユーザーを並び替えます。

降順にする場合は、OrderByDescending を使います。

C#
var orderedUsers = users.OrderByDescending(user => user.Age);

グループ化では、GroupBy を使います。

C#
var groups = users.GroupBy(user => user.Age);

商品をカテゴリごとにグループ化する例です。

C#
var groupedProducts = products.GroupBy(product => product.Category);

「何を基準に並び替えるか」「何を基準にグループ化するか」をラムダ式で指定できるため、処理の意図が明確になります。

5-3. イベントハンドラを短く書く

ラムダ式は、イベントハンドラを書くときにも使えます。

たとえば、ボタンがクリックされたときの処理を短く書けます。

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

従来のように別メソッドを作る場合は、次のようになります。

C#
button.Click += Button_Click;

void Button_Click(object? sender, EventArgs e)
{
Console.WriteLine("ボタンがクリックされました");
}

イベント処理が短く、その場で完結する場合はラムダ式が便利です。

ただし、処理が長い場合や再利用したい場合は、別メソッドに切り出した方が読みやすくなります。

5-4. コールバック処理を渡す

コールバックとは、ある処理が終わった後に呼び出される処理のことです。

ラムダ式を使うと、コールバック処理を簡単に渡せます。

C#
static void Execute(Action callback)
{
Console.WriteLine("処理を開始します");

callback();

Console.WriteLine("処理を終了します");
}

呼び出し側では、次のように書けます。

C#
Execute(() =>
{
Console.WriteLine("コールバック処理です");
});

引数付きのコールバックも作れます。

C#
static void ProcessUser(string name, Action<string> callback)
{
callback(name);
}

ProcessUser("田中", name => Console.WriteLine($"{name}さんを処理しました"));

このように、ラムダ式を使うと「後で実行してほしい処理」を柔軟に渡せます。

5-5. 非同期処理やTaskと組み合わせる

ラムダ式は、Task と組み合わせて非同期処理を書くときにも使います。Microsoft Learnでも、Task.Run の引数としてラムダ式を使用する例が示されています。

C#
await Task.Run(() =>
{
Console.WriteLine("バックグラウンドで処理します");
});

async ラムダも書けます。

C#
Func<Task> loadAsync = async () =>
{
await Task.Delay(1000);
Console.WriteLine("読み込み完了");
};

await loadAsync();

イベントハンドラでも、非同期ラムダを使うことがあります。

C#
button.Click += async (sender, e) =>
{
await Task.Delay(1000);
Console.WriteLine("クリック後の非同期処理");
};

ただし、非同期ラムダでは例外処理や戻り値の型に注意が必要です。特に async void になる場面は、イベントハンドラ以外では避けるのが基本です。

5-6. メソッドを別に作るべきケースとの判断基準

ラムダ式は便利ですが、何でもラムダ式で書けばよいわけではありません。

次のような場合はラムダ式が向いています。

処理が短い
その場でしか使わない
条件や変換の意図がすぐ分かる
LINQの中で自然に読める

一方、次のような場合はメソッドに切り出した方がよいです。

処理が長い
複数箇所で再利用する
名前を付けた方が意味が伝わる
テストしやすくしたい
複雑な条件分岐がある

たとえば、次のようなラムダ式は少し読みにくくなります。

C#
var result = users.Where(user =>
user.Age >= 20 &&
user.Name.StartsWith("A") &&
user.LastLoginDate >= DateTime.Today.AddDays(-30) &&
!user.IsDeleted);

この場合は、メソッドに切り出すと意図が明確になります。

C#
var result = users.Where(IsActiveAdultUser);

static bool IsActiveAdultUser(User user)
{
return user.Age >= 20 &&
user.Name.StartsWith("A") &&
user.LastLoginDate >= DateTime.Today.AddDays(-30) &&
!user.IsDeleted;
}

ラムダ式は短い処理を読みやすくするための道具です。長く複雑になってきたら、メソッド化を検討しましょう。

6. LINQで使うC#ラムダ式の実践例

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

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

C#
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

var evenNumbers = numbers.Where(n => n % 2 == 0);

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

文字列の例です。

C#
var names = new List<string> { "Alice", "Bob", "Charlie", "Ann" };

var result = names.Where(name => name.Length >= 4);

Console.WriteLine(string.Join(", ", result)); // Alice, Charlie

オブジェクトの例です。

C#
var adults = users.Where(user => user.Age >= 20);

Where に渡すラムダ式は、要素を受け取って bool を返します。

C#
要素 => 条件

条件が true の要素だけが残ります。

6-2. Selectで必要な値に変換する

Select は、要素を別の形に変換するためのメソッドです。

C#
var numbers = new List<int> { 1, 2, 3 };

var doubled = numbers.Select(n => n * 2);

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

ユーザー一覧から名前だけを取り出す例です。

C#
var names = users.Select(user => user.Name);

匿名型に変換することもできます。

C#
var userSummaries = users.Select(user => new
{
DisplayName = user.Name,
IsAdult = user.Age >= 20
});

Select は、元のデータを加工して別のデータに変換したいときに使います。

C#
var messages = users.Select(user => $"{user.Name}さんは{user.Age}歳です");

6-3. OrderBy・OrderByDescendingで並び替える

OrderBy は昇順、OrderByDescending は降順で並び替えるメソッドです。

C#
var numbers = new List<int> { 5, 2, 8, 1 };

var ordered = numbers.OrderBy(n => n);

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

降順にする場合です。

C#
var orderedDesc = numbers.OrderByDescending(n => n);

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

ユーザーを年齢順に並び替えます。

C#
var orderedUsers = users.OrderBy(user => user.Age);

年齢が高い順にする場合です。

C#
var olderUsers = users.OrderByDescending(user => user.Age);

複数条件で並び替える場合は、ThenByThenByDescending を使います。

C#
var result = users
.OrderBy(user => user.Age)
.ThenBy(user => user.Name);

6-4. First・FirstOrDefaultで最初の要素を取得する

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

C#
var numbers = new List<int> { 1, 3, 5, 8, 10 };

var firstEven = numbers.First(n => n % 2 == 0);

Console.WriteLine(firstEven); // 8

ただし、条件に合う要素がない場合、First は例外を発生させます。

C#
var numbers = new List<int> { 1, 3, 5 };

var firstEven = numbers.First(n => n % 2 == 0); // 例外

条件に合う要素がない可能性がある場合は、FirstOrDefault を使います。

C#
var firstEven = numbers.FirstOrDefault(n => n % 2 == 0);

Console.WriteLine(firstEven); // intの既定値である0

参照型の場合、見つからなければ null が返ることがあります。

C#
var user = users.FirstOrDefault(user => user.Name == "田中");

if (user != null)
{
Console.WriteLine(user.Age);
}

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

Any は、条件に合う要素が1つでもあるかを判定します。

C#
var numbers = new List<int> { 1, 3, 5, 8 };

bool hasEven = numbers.Any(n => n % 2 == 0);

Console.WriteLine(hasEven); // True

ユーザー一覧に未成年がいるかを判定します。

C#
bool hasMinor = users.Any(user => user.Age < 20);

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

C#
bool allAdults = users.All(user => user.Age >= 20);

AnyAll は、if文と組み合わせてよく使われます。

C#
if (users.Any(user => user.Age >= 20))
{
Console.WriteLine("成人ユーザーがいます");
}

6-6. Count・Sum・Averageで集計する

Count は、条件に合う要素数を数えます。

C#
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

int evenCount = numbers.Count(n => n % 2 == 0);

Console.WriteLine(evenCount); // 3

Sum は合計を求めます。

C#
var totalAge = users.Sum(user => user.Age);

Average は平均を求めます。

C#
var averageAge = users.Average(user => user.Age);

商品一覧の価格合計を求める例です。

C#
decimal totalPrice = products.Sum(product => product.Price);

条件で絞ってから集計することもできます。

C#
decimal expensiveTotal = products
.Where(product => product.Price >= 1000)
.Sum(product => product.Price);

6-7. GroupByでグループ化する

GroupBy は、指定したキーで要素をグループ化します。

商品クラスを考えます。

C#
public class Product
{
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public decimal Price { get; set; }
}

カテゴリごとにグループ化します。

C#
var products = new List<Product>
{
new Product { Name = "りんご", Category = "食品", Price = 120 },
new Product { Name = "牛乳", Category = "食品", Price = 200 },
new Product { Name = "ノート", Category = "文房具", Price = 150 }
};

var groups = products.GroupBy(product => product.Category);

foreach (var group in groups)
{
Console.WriteLine($"カテゴリ: {group.Key}");

foreach (var product in group)
{
Console.WriteLine(product.Name);
}
}

グループごとに件数や合計を出すこともできます。

C#
var summary = products
.GroupBy(product => product.Category)
.Select(group => new
{
Category = group.Key,
Count = group.Count(),
TotalPrice = group.Sum(product => product.Price)
});

6-8. 複数条件を組み合わせるラムダ式

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

C#
var result = users.Where(user => user.Age >= 20 && user.Name.StartsWith("A"));

これは、「20歳以上、かつ名前がAで始まるユーザー」を抽出します。

|| を使うと「または」を表せます。

C#
var result = users.Where(user => user.Age < 20 || user.Age >= 65);

これは、「20歳未満、または65歳以上」のユーザーを抽出します。

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

C#
var result = users.Where(user =>
user.Age >= 20 &&
user.Name.StartsWith("A") &&
!string.IsNullOrWhiteSpace(user.Name));

さらに複雑になる場合は、メソッドに切り出すのがおすすめです。

C#
var result = users.Where(IsTargetUser);

static bool IsTargetUser(User user)
{
return user.Age >= 20 &&
user.Name.StartsWith("A") &&
!string.IsNullOrWhiteSpace(user.Name);
}

7. LINQのメソッド構文とクエリ構文の違い

7-1. メソッド構文でラムダ式を使う例

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

メソッド構文は、WhereSelect などのメソッドをつなげて書く方法です。

C#
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

var result = numbers
.Where(n => n % 2 == 0)
.Select(n => n * 10);

この例では、ラムダ式を2回使っています。

C#
n => n % 2 == 0
n => n * 10

メソッド構文では、ラムダ式を使って条件や変換処理を渡します。

オブジェクト一覧でも同じです。

C#
var result = users
.Where(user => user.Age >= 20)
.OrderBy(user => user.Name)
.Select(user => user.Name);

7-2. クエリ構文で同じ処理を書く例

クエリ構文は、SQLに似た書き方でLINQを書く方法です。Microsoft Learnでは、クエリ構文やメソッド構文を含むLINQクエリの基本が説明されています。

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

C#
var result =
from n in numbers
where n % 2 == 0
select n * 10;

ユーザー一覧の例です。

C#
var result =
from user in users
where user.Age >= 20
orderby user.Name
select user.Name;

クエリ構文では、ラムダ式の => は直接出てきません。しかし、内部的にはLINQのメソッド呼び出しに変換されます。

7-3. 初心者はどちらから学ぶべきか

初心者には、まずメソッド構文から学ぶことをおすすめします。

理由は、実務でラムダ式と一緒に使われる場面が多いからです。

C#
var result = users.Where(user => user.Age >= 20);

この書き方に慣れておくと、WhereSelectOrderByAnyFirstOrDefault などを自然に使えるようになります。

一方で、クエリ構文はSQLに慣れている人には読みやすい場合があります。

C#
var result =
from user in users
where user.Age >= 20
select user;

どちらも正しい書き方です。ただし、ラムダ式を理解するという目的では、メソッド構文を優先して学ぶとよいでしょう。

7-4. 実務でラムダ式を使う場面が多い理由

実務でラムダ式を使う場面が多い理由は、メソッド構文のLINQが多用されるからです。

たとえば、Webアプリケーションでは、一覧データを条件で絞り込んだり、表示用の形に変換したりする処理が頻繁にあります。

C#
var viewModels = users
.Where(user => user.IsActive)
.OrderBy(user => user.Name)
.Select(user => new UserViewModel
{
Name = user.Name,
Age = user.Age
})
.ToList();

このようなコードは、実務でよく見かけます。

また、Entity Frameworkを使ったデータベース検索でもラムダ式がよく使われます。

C#
var users = dbContext.Users
.Where(user => user.Age >= 20)
.ToList();

ラムダ式を読めるようになると、C#の実務コードを理解しやすくなります。

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

8-1. 引数の型が推論できないエラー

ラムダ式では、コンパイラが型を推論できないとエラーになります。

たとえば、次のようなコードは意図が不明確です。

C#
var func = x => x * 2; // エラー

xint なのか、double なのか、ほかの型なのか分からないためです。

解決するには、代入先の型を明示します。

C#
Func<int, int> func = x => x * 2;

または、ラムダ式の引数に型を書きます。

C#
Func<int, int> func = (int x) => x * 2;

LINQでは、元のコレクションの型から推論されるため、多くの場合は型を書かなくても問題ありません。

C#
var numbers = new List<int> { 1, 2, 3 };

var result = numbers.Select(x => x * 2);

8-2. 戻り値の型が合わないエラー

Func の戻り値の型と、ラムダ式が返す値の型が合わないとエラーになります。

C#
Func<int, int> func = x => x > 0; // エラー

x > 0bool を返します。しかし、Func<int, int>int を返す必要があります。

正しくは次のようにします。

C#
Func<int, bool> func = x => x > 0;

または、intを返すように処理を変えます。

C#
Func<int, int> func = x => x * 2;

複数行ラムダでも同じです。

C#
Func<int, string> func = x =>
{
return x.ToString();
};

戻り値の型が一致しているかを確認しましょう。

8-3. null参照で発生するエラー

ラムダ式の中でnullの可能性がある値にアクセスすると、NullReferenceException が発生することがあります。

C#
var users = new List<User?>
{
new User { Name = "田中", Age = 20 },
null
};

var names = users.Select(user => user.Name); // nullでエラー

user がnullの可能性があるため、user.Name にアクセスできません。

解決方法の一つは、nullを除外することです。

C#
var names = users
.Where(user => user != null)
.Select(user => user!.Name);

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

C#
var names = users.Select(user => user?.Name);

文字列プロパティがnullの可能性がある場合も注意が必要です。

C#
var result = users.Where(user => user.Name.StartsWith("A")); // Nameがnullならエラー

安全に書くなら、次のようにします。

C#
var result = users.Where(user => user.Name != null && user.Name.StartsWith("A"));

または、null条件演算子を使います。

C#
var result = users.Where(user => user.Name?.StartsWith("A") == true);

8-4. 複数行ラムダでreturnを書き忘れるケース

1行の式ラムダでは、return は不要です。

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

しかし、複数行のステートメントラムダで戻り値がある場合は、return が必要です。

C#
Func<int, int> square = x =>
{
int result = x * x;
return result;
};

次のように return を書き忘れるとエラーになります。

C#
Func<int, int> square = x =>
{
int result = x * x;
// returnがないためエラー
};

戻り値が不要な場合は、Action を使います。

C#
Action<int> printSquare = x =>
{
int result = x * x;
Console.WriteLine(result);
};

8-5. LINQでFirstを使って例外になるケース

First は便利ですが、条件に合う要素がないと例外になります。

C#
var numbers = new List<int> { 1, 3, 5 };

var firstEven = numbers.First(n => n % 2 == 0); // 例外

このような場合は、FirstOrDefault を使います。

C#
var firstEven = numbers.FirstOrDefault(n => n % 2 == 0);

ただし、FirstOrDefault は見つからない場合に既定値を返します。int の場合は 0、参照型の場合は null になることがあります。

参照型では、nullチェックを忘れないようにしましょう。

C#
var user = users.FirstOrDefault(user => user.Name == "田中");

if (user == null)
{
Console.WriteLine("ユーザーが見つかりませんでした");
}
else
{
Console.WriteLine(user.Age);
}

8-6. 変数キャプチャで意図しない値になるケース

ラムダ式は外側の変数をキャプチャできますが、これが原因で意図しない値になることがあります。

たとえば、次のコードを見てください。

C#
var actions = new List<Action>();

for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}

foreach (var action in actions)
{
action();
}

このようなループ内の変数キャプチャは、期待と違う結果になる可能性があります。対策として、ループ内で別の変数にコピーします。

C#
var actions = new List<Action>();

for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(() => Console.WriteLine(current));
}

foreach (var action in actions)
{
action();
}

ラムダ式が外側の変数を使うときは、「値をその時点で固定している」のではなく、「変数を参照している」と考えると理解しやすくなります。

9. ラムダ式を読みやすく書くコツ

9-1. 引数名は意味が伝わる名前にする

ラムダ式では、xn のような短い引数名をよく使います。

数値の処理なら、次のような名前でも十分です。

C#
numbers.Where(n => n % 2 == 0);

しかし、オブジェクトを扱う場合は、意味が分かる名前を付けた方が読みやすくなります。

C#
users.Where(user => user.Age >= 20);

次のように短すぎる名前にすると、意味が分かりにくくなることがあります。

C#
users.Where(x => x.Age >= 20);

小さな処理なら問題ありませんが、条件が複雑な場合は、userproductorder のように意味のある名前を使いましょう。

C#
orders.Where(order => order.TotalPrice >= 10000);

9-2. 複雑すぎるラムダ式はメソッドに切り出す

ラムダ式が複雑になると、1行に詰め込んでも読みやすくなりません。

C#
var result = users.Where(user => user.Age >= 20 && user.Name.StartsWith("A") && user.LastLoginDate >= DateTime.Today.AddDays(-30) && !user.IsDeleted);

このような場合は、改行するだけでも読みやすくなります。

C#
var result = users.Where(user =>
user.Age >= 20 &&
user.Name.StartsWith("A") &&
user.LastLoginDate >= DateTime.Today.AddDays(-30) &&
!user.IsDeleted);

さらに、条件に意味があるならメソッドに切り出しましょう。

C#
var result = users.Where(IsRecentlyActiveAdultUser);

static bool IsRecentlyActiveAdultUser(User user)
{
return user.Age >= 20 &&
user.Name.StartsWith("A") &&
user.LastLoginDate >= DateTime.Today.AddDays(-30) &&
!user.IsDeleted;
}

メソッド名が付くことで、条件の意味が伝わりやすくなります。

9-3. 1行で書ける処理と複数行にすべき処理の判断

単純な変換や条件判定は、1行のラムダ式が向いています。

C#
users.Where(user => user.Age >= 20);
products.Select(product => product.Name);
numbers.Select(n => n * 2);

一方、次のような処理は複数行にした方が読みやすいです。

途中計算がある
ログを出力する
条件分岐が複数ある
例外処理がある

例です。

C#
Func<Product, decimal> calculateDiscountedPrice = product =>
{
decimal discountRate = product.Price >= 10000 ? 0.2m : 0.1m;
decimal discount = product.Price * discountRate;
return product.Price - discount;
};

ただし、複数行になった時点で、メソッド化した方がよいケースもあります。目安として、ラムダ式の中身が3行以上になるなら、メソッドに切り出すことを検討しましょう。

9-4. null対策を意識して書く

ラムダ式では、短く書ける分、nullチェックを忘れがちです。

危険な例です。

C#
users.Where(user => user.Name.StartsWith("A"));

Name がnullの場合、例外が発生します。

安全に書くなら、次のようにします。

C#
users.Where(user => user.Name?.StartsWith("A") == true);

または、先にnullを除外します。

C#
users
.Where(user => user.Name != null)
.Where(user => user.Name.StartsWith("A"));

オブジェクト自体がnullの可能性がある場合も注意します。

C#
users
.Where(user => user != null)
.Select(user => user!.Name);

実務では、null許容参照型を有効にして、コンパイラの警告を活用するのも有効です。

9-5. LINQを連続して使うときの可読性を保つ

LINQを連続して使うときは、メソッドごとに改行すると読みやすくなります。

読みにくい例です。

C#
var result = users.Where(user => user.Age >= 20).OrderBy(user => user.Name).Select(user => user.Name).ToList();

読みやすい例です。

C#
var result = users
.Where(user => user.Age >= 20)
.OrderBy(user => user.Name)
.Select(user => user.Name)
.ToList();

さらに、処理の途中結果に名前を付けた方が分かりやすいこともあります。

C#
var adultUsers = users.Where(user => user.Age >= 20);

var result = adultUsers
.OrderBy(user => user.Name)
.Select(user => user.Name)
.ToList();

1本の長いLINQにしすぎず、必要に応じて変数やメソッドに分けることが大切です。

10. ラムダ式の少し発展的な知識

10-1. ExpressionとFuncの違い

FuncExpression<Func<...>> は似ていますが、意味が違います。

Func は、実行できる処理そのものです。

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

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

一方、Expression<Func<int, int>> は、処理の構造をデータとして表します。

C#
using System.Linq.Expressions;

Expression<Func<int, int>> squareExpression = x => x * x;

Console.WriteLine(squareExpression);

Microsoft Learnでも、式形式のラムダは式ツリー型に変換できると説明されています。

大まかに言うと、違いは次のとおりです。

Func:
すぐ実行できる処理

Expression<Func<...>>:
処理の構造を表すデータ

LINQ to Objectsでは、主に Func が使われます。

C#
var result = numbers.Where(n => n > 10);

Entity Frameworkなどでは、ラムダ式が式ツリーとして扱われ、SQLなどに変換されることがあります。

10-2. Entity Frameworkでラムダ式がSQLに変換される仕組み

Entity Frameworkでは、次のようなコードを書くことがあります。

C#
var users = dbContext.Users
.Where(user => user.Age >= 20)
.ToList();

一見すると、C#のラムダ式で絞り込んでいるだけに見えます。しかし、Entity Frameworkでは、このラムダ式が式ツリーとして解釈され、SQLに変換されます。

イメージとしては、次のようなSQLになります。

SQL
SELECT *
FROM Users
WHERE Age >= 20

ここで重要なのは、すべてのC#メソッドがSQLに変換できるわけではないという点です。Microsoft Learnでも、クエリプロバイダーが認識しないメソッド呼び出しは変換または実行できない場合があると説明されています。

たとえば、独自メソッドをラムダ式の中で使うと、SQLに変換できずにエラーになることがあります。

C#
var users = dbContext.Users
.Where(user => IsAdult(user.Age))
.ToList();

このような場合は、SQLに変換しやすい形で書く必要があります。

C#
var users = dbContext.Users
.Where(user => user.Age >= 20)
.ToList();

Entity Frameworkでラムダ式を使うときは、「このラムダ式はSQLに変換できるか」を意識しましょう。

10-3. ラムダ式と遅延実行の関係

LINQには、遅延実行という特徴があります。

たとえば、次のコードを見てください。

C#
var numbers = new List<int> { 1, 2, 3, 4, 5 };

var query = numbers.Where(n => n % 2 == 0);

この時点では、まだ実際の抽出処理は実行されていません。

実際に実行されるのは、foreach で列挙したときや、ToList を呼んだときです。

C#
foreach (var number in query)
{
Console.WriteLine(number);
}

または、次のように書いたときです。

C#
var list = query.ToList();

遅延実行により、必要になるまで処理を待てます。

ただし、元のコレクションを後から変更すると、結果が変わることがあります。

C#
var numbers = new List<int> { 1, 2, 3 };

var query = numbers.Where(n => n >= 2);

numbers.Add(4);

Console.WriteLine(string.Join(", ", query)); // 2, 3, 4

query を作った時点ではなく、実際に列挙した時点のデータが使われます。

結果を固定したい場合は、ToList を使います。

C#
var query = numbers.Where(n => n >= 2).ToList();

10-4. ラムダ式とパフォーマンスで注意する点

ラムダ式自体は便利ですが、使い方によってはパフォーマンスに影響することがあります。

まず、LINQを何度も列挙すると、同じ処理が繰り返される場合があります。

C#
var query = numbers.Where(n =>
{
Console.WriteLine($"判定: {n}");
return n % 2 == 0;
});

var count = query.Count();
var list = query.ToList();

この場合、CountToList でそれぞれ列挙されるため、条件判定が複数回実行されます。

必要であれば、先にリスト化します。

C#
var list = numbers
.Where(n => n % 2 == 0)
.ToList();

var count = list.Count;

また、大量データに対して複雑なラムダ式を何度も実行すると、処理時間が増えます。

C#
var result = users.Where(user => ExpensiveCheck(user));

重い処理をラムダ式の中に書く場合は、実行回数を意識しましょう。

Entity Frameworkでは、ToList の位置にも注意が必要です。

C#
var users = dbContext.Users
.ToList()
.Where(user => user.Age >= 20);

この書き方では、データベースから全件取得した後にメモリ上で絞り込む可能性があります。

通常は、先に Where を書きます。

C#
var users = dbContext.Users
.Where(user => user.Age >= 20)
.ToList();

10-5. staticラムダとは何か

C#には、static を付けたラムダ式があります。

C#
Func<int, int> square = static x => x * x;

staticラムダは、外側のローカル変数やインスタンス状態をキャプチャできません。

たとえば、次のコードはエラーになります。

C#
int factor = 2;

Func<int, int> multiply = static x => x * factor; // エラー

factor は外側の変数なので、staticラムダからは使えません。

一方、外側の変数を使わない処理なら問題ありません。

C#
Func<int, int> square = static x => x * x;

staticラムダを使うメリットは、「このラムダ式は外側の変数をキャプチャしない」と明示できることです。意図しないキャプチャを防ぎたい場合や、パフォーマンスを意識する場面で役立ちます。

初心者のうちは通常のラムダ式を理解すれば十分です。慣れてきたら、キャプチャを避けたい場面でstaticラムダを検討するとよいでしょう。

11. csharp lambdaの理解を深める練習問題

11-1. 配列から偶数だけを抽出する

問題です。次の配列から偶数だけを抽出してください。

C#
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

解答例です。

C#
var evenNumbers = numbers.Where(n => n % 2 == 0);

Console.WriteLine(string.Join(", ", evenNumbers));

出力です。

2, 4, 6, 8

ポイントは、Wherebool を返すラムダ式を渡すことです。

C#
n => n % 2 == 0

これは「nが2で割り切れるか」を判定しています。

11-2. 商品一覧から価格が高い順に並べる

商品一覧を価格が高い順に並べます。

C#
var products = new List<Product>
{
new Product { Name = "りんご", Category = "食品", Price = 120 },
new Product { Name = "ノート", Category = "文房具", Price = 150 },
new Product { Name = "バッグ", Category = "雑貨", Price = 3000 }
};

解答例です。

C#
var orderedProducts = products
.OrderByDescending(product => product.Price);

foreach (var product in orderedProducts)
{
Console.WriteLine($"{product.Name}: {product.Price}円");
}

OrderByDescending(product => product.Price) により、Price を基準に降順で並び替えています。

昇順にしたい場合は、OrderBy を使います。

C#
var orderedProducts = products.OrderBy(product => product.Price);

11-3. ユーザー一覧から条件に合うユーザーを検索する

ユーザー一覧から、20歳以上で名前が「A」から始まるユーザーを検索します。

C#
var users = new List<User>
{
new User { Name = "Alice", Age = 25 },
new User { Name = "Bob", Age = 30 },
new User { Name = "Ann", Age = 18 },
new User { Name = "Alex", Age = 22 }
};

解答例です。

C#
var result = users.Where(user =>
user.Age >= 20 &&
user.Name.StartsWith("A"));

foreach (var user in result)
{
Console.WriteLine($"{user.Name}: {user.Age}");
}

このラムダ式は、複数条件を && で組み合わせています。

C#
user => user.Age >= 20 && user.Name.StartsWith("A")

条件が長い場合は、改行して読みやすくしましょう。

11-4. 文字列リストを大文字に変換する

文字列リストをすべて大文字に変換します。

C#
var words = new List<string> { "csharp", "lambda", "linq" };

解答例です。

C#
var upperWords = words.Select(word => word.ToUpper());

Console.WriteLine(string.Join(", ", upperWords));

出力です。

CSHARP, LAMBDA, LINQ

Select は、要素を別の形に変換するメソッドです。

C#
word => word.ToUpper()

これは「wordを受け取り、大文字に変換した文字列を返す」という意味です。

11-5. GroupByでカテゴリ別に集計する

商品一覧をカテゴリ別にグループ化し、カテゴリごとの合計金額を求めます。

C#
var products = new List<Product>
{
new Product { Name = "りんご", Category = "食品", Price = 120 },
new Product { Name = "牛乳", Category = "食品", Price = 200 },
new Product { Name = "ノート", Category = "文房具", Price = 150 },
new Product { Name = "ペン", Category = "文房具", Price = 100 }
};

解答例です。

C#
var summaries = products
.GroupBy(product => product.Category)
.Select(group => new
{
Category = group.Key,
TotalPrice = group.Sum(product => product.Price),
Count = group.Count()
});

foreach (var summary in summaries)
{
Console.WriteLine($"{summary.Category}: {summary.Count}件, 合計{summary.TotalPrice}円");
}

ポイントは、GroupBy でカテゴリごとにまとめた後、Select で集計結果に変換していることです。

C#
.GroupBy(product => product.Category)
.Select(group => new
{
Category = group.Key,
TotalPrice = group.Sum(product => product.Price),
Count = group.Count()
})

このように、LINQとラムダ式を組み合わせると、集計処理も簡潔に書けます。

12. csharp lambdaに関するよくある質問

12-1. ラムダ式は必ず使わないといけない?

ラムダ式は必ず使わなければならないものではありません。

同じ処理は、通常のメソッドでも書けます。

C#
static bool IsEven(int n)
{
return n % 2 == 0;
}

var evenNumbers = numbers.Where(IsEven);

ラムダ式で書くと次のようになります。

C#
var evenNumbers = numbers.Where(n => n % 2 == 0);

短く、その場でしか使わない処理ならラムダ式が便利です。一方、処理に名前を付けた方が分かりやすい場合や、複数箇所で再利用する場合は、通常のメソッドにする方がよいです。

12-2. ラムダ式と普通のメソッドは何が違う?

普通のメソッドは、名前を持ち、クラスや構造体のメンバーとして定義されます。

C#
static int Square(int x)
{
return x * x;
}

ラムダ式は、名前のない処理をその場で書けます。

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

大きな違いは、ラムダ式は変数に代入したり、メソッドの引数として渡したりしやすいことです。

C#
var result = numbers.Select(n => n * n);

ただし、複雑な処理に名前を付けたい場合は、普通のメソッドの方が読みやすくなります。

12-3. ラムダ式が読みにくいときはどうすればいい?

ラムダ式が読みにくいときは、次の方法を試しましょう。

まず、引数名を分かりやすくします。

C#
users.Where(user => user.Age >= 20);

次に、条件が長い場合は改行します。

C#
var result = users.Where(user =>
user.Age >= 20 &&
user.Name.StartsWith("A") &&
!user.IsDeleted);

それでも読みにくい場合は、メソッドに切り出します。

C#
var result = users.Where(IsTargetUser);

static bool IsTargetUser(User user)
{
return user.Age >= 20 &&
user.Name.StartsWith("A") &&
!user.IsDeleted;
}

ラムダ式は短く書くためのものですが、短さよりも読みやすさを優先しましょう。

12-4. LINQを使わずにラムダ式だけ使うことはできる?

はい、LINQを使わなくてもラムダ式は使えます。

たとえば、Func に代入できます。

C#
Func<int, int> doubleValue = x => x * 2;

Console.WriteLine(doubleValue(5)); // 10

Action に代入することもできます。

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

print("Hello");

メソッドの引数として渡すこともできます。

C#
static void Execute(Action action)
{
action();
}

Execute(() => Console.WriteLine("実行しました"));

ラムダ式はLINQでよく使われますが、LINQ専用の機能ではありません。デリゲート型や式ツリーが必要な場面で使えます。

12-5. 初心者はラムダ式をどこまで覚えればよい?

初心者が最初に覚えるべきポイントは、次の5つです。

1. x => x * 2 は「xを受け取ってx * 2を返す」と読む
2. Whereでは bool を返すラムダ式を使う
3. Selectでは変換結果を返すラムダ式を使う
4. Funcは戻り値あり、Actionは戻り値なし
5. 複雑になったらメソッドに切り出す

最初からExpression、staticラムダ、Entity FrameworkでのSQL変換まで完璧に覚える必要はありません。

まずは、次のような基本的なLINQを読めるようになれば十分です。

C#
var result = users
.Where(user => user.Age >= 20)
.OrderBy(user => user.Name)
.Select(user => user.Name)
.ToList();

このコードが読めるようになると、C#の実務コードへの理解が大きく進みます。

まとめ

csharp lambda、つまりC#のラムダ式は、「名前のない関数」を短く書くための構文です。

基本形は次のとおりです。

C#
引数 => 処理

たとえば、次のラムダ式は「nを受け取り、nが偶数かどうかを返す」という意味です。

C#
n => n % 2 == 0

ラムダ式は、FuncActionPredicate などのデリゲート型に代入できます。

C#
Func<int, int> square = x => x * x;
Action<string> print = message => Console.WriteLine(message);
Predicate<int> isEven = n => n % 2 == 0;

特にLINQでは、ラムダ式が頻繁に使われます。

C#
var result = users
.Where(user => user.Age >= 20)
.OrderBy(user => user.Name)
.Select(user => user.Name)
.ToList();

Where では条件判定、Select では変換、OrderBy では並び替えの基準をラムダ式で指定します。

一方で、ラムダ式を使いすぎると読みにくくなることもあります。処理が短く、その場で意味が分かる場合はラムダ式を使い、複雑な処理はメソッドに切り出すのがよい判断です。

C#ラムダ式を理解すると、LINQ、イベント処理、コールバック、非同期処理、Entity Frameworkなど、多くのC#コードが読みやすくなります。まずは x => x * 2 のような小さな例から始めて、WhereSelectOrderBy などのLINQで実際に使いながら慣れていきましょう。