TL;DR
- LINQ のラムダ変数・クエリ変数を日本語にするとビジネスルールがそのまま読める
- クエリ構文(
from 社員 in 社員一覧 where ...)は仕様書の条件式に近い構造になる -
Joinなど複数コレクションが絡む処理で特に可読性向上の恩恵が大きい -
GroupJoinはjoin ... intoでクエリ構文でも書ける -
Aggregate・Skip・Take・Distinctはクエリ構文に対応キーワードがなく、メソッド構文のみ
環境
| 項目 | バージョン・詳細 |
|---|---|
| OS | Windows 11 |
| Visual Studio | 2022 (17.x) |
| .NET | .NET 8 |
| 対象 | 社内業務アプリ(WinForms) |
英語変数名 vs 日本語変数名の比較
メソッド構文
// 英語変数名
var result = employees
.Where(e => e.Department == "営業" && e.IsActive)
.OrderBy(e => e.HireDate)
.Select(e => new { e.Name, e.HireDate });
// 日本語変数名
var 結果 = 社員一覧
.Where(社員 => 社員.部署 == "営業" && 社員.在籍中)
.OrderBy(社員 => 社員.入社日)
.Select(社員 => new { 社員.氏名, 社員.入社日 });
e が何を指すか文脈を覚えておく必要がなくなる。チェーンを上から読み下すだけで何をしているかわかる。
クエリ構文
var 結果 = from 社員 in 社員一覧
where 社員.部署 == "営業" && 社員.在籍中
orderby 社員.入社日
select new { 社員.氏名, 社員.入社日 };
from 社員 in 社員一覧 の構造は仕様書の「社員一覧の中で…」に直接対応する。Excel仕様書と並べて確認する作業がスムーズになる。
集計・グループ化
// 部署ごとの残業時間合計(降順)
var 部署別残業集計 = 勤怠一覧
.GroupBy(勤怠 => 勤怠.部署)
.Select(グループ => new
{
部署名 = グループ.Key,
残業時間合計 = グループ.Sum(勤怠 => 勤怠.残業時間),
対象人数 = グループ.Count()
})
.OrderByDescending(集計 => 集計.残業時間合計);
ラムダ変数が 勤怠 → グループ → 集計 と変化するたびに「今何を操作しているか」が明示される。英語だと g・x のような短縮変数になりがちで、ネストが深くなると追いにくい。
Join
// 英語
var result = orders.Join(customers,
o => o.CustomerId,
c => c.Id,
(o, c) => new { o.OrderDate, c.Name, o.Amount });
// 日本語
var 注文明細 = 注文一覧.Join(顧客一覧,
注文 => 注文.顧客ID,
顧客 => 顧客.ID,
(注文, 顧客) => new { 注文.注文日, 顧客.氏名, 注文.金額 });
引数が4つある Join は英語1文字変数だと特に読みにくい。注文 と 顧客 という名前があるだけで、各引数が何のキーを結合しているか一目でわかる。
GroupJoin はクエリ構文でも書ける
// クエリ構文での GroupJoin(join ... into)
var 結果 = from 部署 in 部署一覧
join 社員 in 社員一覧
on 部署.ID equals 社員.部署ID
into 部署社員グループ
select new
{
部署名 = 部署.名称,
人数 = 部署社員グループ.Count()
};
join ... into 構文を使えば GroupJoin をクエリ構文で表現できる。左外部結合は into の後に追加の from 句と DefaultIfEmpty() を組み合わせて書く。
// 左外部結合(部署に社員がいない場合も部署を含める)
var 結果 = from 部署 in 部署一覧
join 社員 in 社員一覧
on 部署.ID equals 社員.部署ID
into 部署社員グループ
from 社員 in 部署社員グループ.DefaultIfEmpty() // この from 句が左外部結合のポイント
select new
{
部署名 = 部署.名称,
氏名 = 社員?.氏名 ?? "(未所属)"
};
クエリ構文で書けない操作
以下はクエリ構文に対応するキーワードがなく、メソッド構文でしか書けない。
| 操作 | メソッド構文 |
|---|---|
| 集計 | Aggregate |
| ページング |
Skip / Take
|
| 重複除去 | Distinct |
| 全件一致・任意一致 |
All / Any
|
| 先頭・末尾 |
First / Last / Single
|
実務では「フィルタリング・ソートはクエリ構文、ページングや集計はメソッド構文」という混在スタイルが多くなる。
// クエリ構文でフィルタ → メソッド構文でページング
var ページ結果 = (from 社員 in 社員一覧
where 社員.在籍中
orderby 社員.入社日
select 社員)
.Skip(ページ番号 * ページサイズ)
.Take(ページサイズ)
.ToList();
ハマりどころ
IME の干渉
=> を打った直後に IME が自動でオンになることがある。ラムダ変数の入力タイミングで IME を手動制御する習慣をつけると解消する。Visual Studio の「ツール > オプション > テキストエディター > C# > IntelliSense」で補完タイミングの調整も有効。
行長の増加
日本語変数名は英語1文字変数より横幅をとる。チェーンが長い場合は各メソッドを改行して縦展開する。
// 縦展開の例
var 対象 = 社員一覧
.Where(社員 => 社員.在籍中)
.Where(社員 => 社員.入社日 < 基準日)
.OrderBy(社員 => 社員.氏名)
.Select(社員 => 社員.氏名)
.ToList();
汎用ユーティリティは英語に戻す
特定ドメインに紐づくクエリは日本語変数が有効だが、複数箇所から呼ばれる汎用フィルタ・拡張メソッドを切り出す場合は英語に戻すほうが無難。日本語変数の効果はドメイン文脈が明確な場所ほど高い。
まとめ
- LINQ ラムダ変数を日本語にすると「何のコレクションの何を操作しているか」がコードから直接読める
- クエリ構文と日本語変数の組み合わせは仕様書の条件式に近い構造になり、照合コストが下がる
-
GroupJoinはクエリ構文のjoin ... intoで書ける -
Aggregate・Skip・Take・Distinct等はメソッド構文のみ。混在スタイルが実用的 - IME干渉と行長には慣れが必要。縦展開を意識すると解消しやすい
概要や実際の開発体験を含めた記事はこちら → C# の LINQ を日本語変数で書いたら、クエリが仕様書みたいになった話