LINQ クエリ式が輝く5つのシーン + α

に公開
6

はじめに

LINQ 便利ですよね 👍 、普段はメソッド式ばかり使用しているので、たまにはクエリ式のことも思い出してあげよう。
ということで、個人的にクエリ式が見やすいと思う使い方をいくつか挙げてみます。

環境

  • .NET8
  • C#12

クエリ式

var q = from n in numbers
        where n % 2 == 0
        select n * n;

メソッド式

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

1.複数配列の結合(内部結合)

複数配列をキー項目で結合する様な場合です。

データ:

var people = new[]
{
    new Person { Id = 1, Name = "太郎" },
    new Person { Id = 2, Name = "花子" },
    new Person { Id = 3, Name = "次郎" }
};

var profiles = new[]
{
    new Profile { PersonId = 1, Email = "taro@example.com" },
    new Profile { PersonId = 2, Email = "hanako@example.com" },
    new Profile { PersonId = 3, Email = "jiro@example.com" }
};

var addresses = new[]
{
    new Address { PersonId = 1, City = "東京" },
    new Address { PersonId = 2, City = "大阪" },
    new Address { PersonId = 3, City = "広島" }
};

クエリ式:

var query =
    from p in people
    join pr in profiles on p.Id equals pr.PersonId
    join a in addresses on p.Id equals a.PersonId
    select new { p.Id, p.Name, pr.Email, a.City };

メソッド式:

var query = people
    .Join(profiles,
            p => p.Id,
            pr => pr.PersonId,
            (p, pr) => new { p, pr })
    .Join(addresses,
            pp => pp.p.Id,
            a => a.PersonId,
            (pp, a) => new { pp.p.Id, pp.p.Name, pp.pr.Email, a.City });

結果:

Id=1, Name=太郎, Email=taro@example.com, City=東京
Id=2, Name=花子, Email=hanako@example.com, City=大阪
Id=3, Name=次郎, Email=jiro@example.com, City=広島

所感:

どうでしょう?個人的には圧倒的にクエリ式の可読性が高いように思います。
メソッド式はパラメータが多い上に、ラムダ式が複数必要なので
何個目に何を渡したら良いのかわからなくなりそうな気がします。

2.複数配列の結合(外部結合)

複数配列をキー項目で結合し、データが存在しなければ空で取得する様な場合です。

データ:

// peopleは1と同じ

var profiles = new[]
{
    new Profile { PersonId = 1, Email = "taro@example.com" },
    // 2がない
    new Profile { PersonId = 3, Email = "jiro@example.com" }
};

var addresses = new[]
{
    new Address { PersonId = 1, City = "東京" },
    new Address { PersonId = 2, City = "大阪" },
    // 3がない
};

クエリ式:

var query =
    from p in people
    join pr in profiles on p.Id equals pr.PersonId into prg
    from pr in prg.DefaultIfEmpty()
    join a in addresses on p.Id equals a.PersonId into ag
    from a in ag.DefaultIfEmpty()
    select new
    {
        p.Id,
        p.Name,
        Email = pr?.Email ?? "(なし)",
        City  = a?.City  ?? "(なし)"
    };

メソッド式:

var query = people
    .GroupJoin(profiles,
                p => p.Id,
                pr => pr.PersonId,
                (p, prg) => new { p, prg })
    .SelectMany(tmp => tmp.prg.DefaultIfEmpty(),
                (tmp, pr) => new { tmp.p, pr })
    .GroupJoin(addresses,
                x => x.p.Id,
                a => a.PersonId,
                (x, ag) => new { x.p, x.pr, ag })
    .SelectMany(tmp => tmp.ag.DefaultIfEmpty(),
                (tmp, a) => new
                {
                    tmp.p.Id,
                    tmp.p.Name,
                    Email = tmp.pr?.Email ?? "(なし)",
                    City = a?.City ?? "(なし)"
                });

結果:

Id=1, Name=太郎, Email=taro@example.com, City=東京
Id=2, Name=花子, Email=(なし), City=大阪
Id=3, Name=次郎, Email=jiro@example.com, City=(なし)

所感:

さらに複雑になった感じ。
私の技術力ではメソッド式は作ることができませんでした。
ChatGPT 先生にお願いして作ってもらいました。

3.中間結果を使用するような場合

途中に計算などが入り let を使う必要があるような場合

データ:

var students = new[]
{
    new Student { Name = "Aさん",  Math = 80, English = 70, Science = 60 },
    new Student { Name = "Bさん",  Math = 50, English = 55, Science = 40 },
    new Student { Name = "Cさん", Math = 90, English = 85, Science = 95 },
    new Student { Name = "Dさん",   Math = 60, English = 60, Science = 60 },
};

クエリ式:

var query =
    from s in students
    let total = s.Math + s.English + s.Science
    let average = total / 3.0
    where average >= 60.0
    select new { s.Name, average };

メソッド式:

var query = students
    .Select(s => new { s.Name, total = s.Math + s.English + s.Science })
    .Select(x => new { x.Name, average = x.total / 3.0 })
    .Where(x => x.average >= 60.0)
    .Select(x => new { x.Name, x.average });

結果:

Aさん: 70.0
Cさん: 90.0
Dさん: 60.0

所感:

メソッド式は中間結果を匿名型や Tuple などで保持することになります。
計算結果を let で保持しておく事ができるクエリ式が有利?

4.複数項目の並び替えがある場合

データ:

var people = new Person[]
{
    new Person { Name = "Aさん", Age = 30, Income = 500, Department = "Sales", YearsAtCompany = 5 },
    new Person { Name = "Bさん", Age = 25, Income = 450, Department = "Dev",   YearsAtCompany = 2 },
    new Person { Name = "Cさん", Age = 30, Income = 480, Department = "Dev",   YearsAtCompany = 4 },
    new Person { Name = "Dさん", Age = 25, Income = 520, Department = "Sales", YearsAtCompany = 3 },
    new Person { Name = "Eさん", Age = 40, Income = 600, Department = "HR",    YearsAtCompany = 10 },
    new Person { Name = "Fさん", Age = 30, Income = 500, Department = "Dev",   YearsAtCompany = 6 },
    new Person { Name = "Gさん", Age = 25, Income = 450, Department = "HR",    YearsAtCompany = 1 },
};

クエリ式:

var query =
    from person in people
    orderby
        person.Age,
        person.Income descending,
        person.Department,
        person.YearsAtCompany descending
    select person.Name;

メソッド式:

var query = people
    .OrderBy(person => person.Age)
    .ThenByDescending(person => person.Income)
    .ThenBy(person => person.Department)
    .ThenByDescending(person => person.YearsAtCompany)
    .Select(person => person.Name);

結果:

Dさん
Bさん
Gさん
Fさん
Aさん
Cさん
Eさん

所感:

文字量の問題で若干クエリ式の可読性が高い?

5.集約を行う場合

データ:

var products = new[]
{
    new Product { Name = "Apple",  Category = "Fruit" },
    new Product { Name = "Banana", Category = "Fruit" },
    new Product { Name = "Carrot", Category = "Vegetable" },
    new Product { Name = "Milk",   Category = "Dairy" },
    new Product { Name = "Cheese", Category = "Dairy" },
};

クエリ式:

var query =
    from p in products
    group p.Name by p.Category into g
    select new { Category = g.Key, Count = g.Count(), Names = g.ToArray() };

メソッド式:

var query = products
    .GroupBy(p => p.Category, p => p.Name)
    .Select(g => new { Category = g.Key, Count = g.Count(), Names = g.ToArray() });

結果:

Fruit: 2 -> [Apple, Banana]
Vegetable: 1 -> [Carrot]
Dairy: 2 -> [Milk, Cheese]

所感:

クエリ式の構文は「group xxx by yyy」で
「xxx を yyy で集約する」。というのが若干わかりやすい?

6.リスト内包表記

2025/09/18 追記 : @いぬいぬ さん ご意見ありがとうございます。

データ:

var nums = new int[] { 1, 2, 3, 4, 5 };

クエリ式:

List<int> list = [.. from num in nums where num % 2 == 0 select num];

メソッド式:

List<int> list = [.. nums.Where(num=> num % 2 == 0)];

さいごに

最後まで読んでいただき、ありがとうございました。

個人的には上記の記述達はクエリ式が見やすいと感じていますが
チーム内でスタイルを統一しつつ、適材適所で使い分けるのが重要だと思います。

他にも「ここはクエリ式が良い」「この書き換え方がある」などあれば教えていただけると嬉しいです。🤩

Discussion

いぬいぬいぬいぬ

「ここはクエリ式が良い」

「リスト内包表記」風にするのはクエリ式の方が見やすいと思います!

整数列 collection から偶数列をもとめる
List<int> list = [.. from item in collection where item % 2 == 0 select item];

https://zenn.dev/inuinu/articles/d4f27e4ceb0ceb

せみせみ

ご意見ありがとうございます。
記事に追加させていただきました🙂‍↕️

junerjuner

途中で変数使うならクエリ式の方がやりやすいみたいなのもありますね。

※ linq to sql などの IQueryable<T> 環境 とかだと ラムダ式が強要される都合 ブロックにすることができず変数の発生が難しい為

List<int> list = [.. 
  from num in nums
  let isEven = num % 2 == 0
  where isEven
  select num
];

https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQcAMACOSB0AMgJZQCOA3OlrkgCxVrqnADaAutlBALYDO2ALzZW2JABpsCSQGZJdSQFZs7RuhJ9gAHhYA+bABtimoSPz5s6bNgBmYAPY8uvbKWf8rhgKbBXfAKIAbl5QptxOAKRSQsKYngDuABZeYF5+QSGefF4GXgDGvuHoqujUSACcABR4mPgAUvaklQBEkq2GxsAAlF0UQA==

せみせみ

こんにちは、ご意見ありがとうございます。

ここで言われているのは、IQueryableの場合

以下のはSQLとして動くけどいまいち。

nums.Select(num => new { num, isEven = num % 2 == 0 })
    .Where(x => x.isEven)
    .Select(x => x.num);

んで、こんなのはSQLとして動かない。

nums.Where(num => { var isEven = num % 2 == 0; return isEven; });

以下の記述が最良のコード

from num in nums
let isEven = num % 2 == 0
where isEven
select num

クエリ式良い感じ✌️という認識であってますか?

junerjuner

そうです。だいたいそうです。


動かないというか 式しか許容されないので 文を書くとエラーになる ……でしょうか。

なので下記はコンパイルが通らなかったりします。(※ IQueryable<T> の場合

nums.Where(num => { var isEven = num % 2 == 0; return isEven; });

すみません 通常 WarningAsError enable 環境でやっているので、 通常は warning で実行エラーの可能性もあります がご容赦。