前回は、LINQ to Objects の基本構文と遅延実行を見ました。
今回は、LINQ で最もよく使うフィルター、射影、並べ替えを整理します。
LINQ の多くの処理は、次の 3 つを組み合わせて書けます。
- 条件に合う要素だけを残す
- 必要な形に変換する
- 指定した順序に並べる
サンプルデータ
この記事では、次の Product クラスを使います。
class Product
{
public string Name { get; }
public string Category { get; }
public int Price { get; }
public bool InStock { get; }
public Product(string name, string category, int price, bool inStock)
{
Name = name;
Category = category;
Price = price;
InStock = inStock;
}
}
商品一覧は次のように用意します。
List<Product> products = new List<Product>
{
new Product("ノート", "文房具", 200, true),
new Product("ペン", "文房具", 150, false),
new Product("キーボード", "PC", 4500, true),
new Product("マウス", "PC", 2500, true),
new Product("モニター", "PC", 24000, false)
};
Where で絞り込む
Where は、条件に合う要素だけを残すメソッドです。
IEnumerable<Product> inStockProducts =
products.Where(product => product.InStock);
完全な例です。
using System;
using System.Collections.Generic;
using System.Linq;
class Product
{
public string Name { get; }
public string Category { get; }
public int Price { get; }
public bool InStock { get; }
public Product(string name, string category, int price, bool inStock)
{
Name = name;
Category = category;
Price = price;
InStock = inStock;
}
}
class Program
{
static void Main()
{
List<Product> products = new List<Product>
{
new Product("ノート", "文房具", 200, true),
new Product("ペン", "文房具", 150, false),
new Product("キーボード", "PC", 4500, true),
new Product("マウス", "PC", 2500, true),
new Product("モニター", "PC", 24000, false)
};
var result = products.Where(product => product.InStock);
foreach (Product product in result)
{
Console.WriteLine(product.Name);
}
}
}
実行結果です。
ノート
キーボード
マウス
Where に渡すラムダ式は、要素を受け取り、bool を返します。
product => product.InStock
true を返した要素だけが結果に残ります。
複数条件で絞り込む
条件は自由に組み合わせられます。
var affordablePcItems = products
.Where(product => product.Category == "PC" && product.Price <= 5000);
foreach (Product product in affordablePcItems)
{
Console.WriteLine($"{product.Name}: {product.Price}円");
}
実行結果です。
キーボード: 4500円
マウス: 2500円
Where を複数回つなげることもできます。
var result = products
.Where(product => product.Category == "PC")
.Where(product => product.Price <= 5000);
条件が長くなる場合は、複数の Where に分けた方が読みやすいこともあります。
Select で別の形に変換する
Select は、要素を別の形に変換するメソッドです。
商品オブジェクトから商品名だけを取り出す例です。
IEnumerable<string> names = products.Select(product => product.Name);
foreach (string name in names)
{
Console.WriteLine(name);
}
実行結果です。
ノート
ペン
キーボード
マウス
モニター
Select は、元の要素を受け取り、新しい値を返します。
product => product.Name
この場合、Product から string へ変換しています。
匿名型に射影する
複数の値を組み合わせた結果を作りたい場合は、匿名型が便利です。
var summaries = products.Select(product => new
{
product.Name,
product.Category,
TaxIncludedPrice = (int)(product.Price * 1.1)
});
foreach (var item in summaries)
{
Console.WriteLine($"{item.Name} / {item.Category} / {item.TaxIncludedPrice}円");
}
実行結果です。
ノート / 文房具 / 220円
ペン / 文房具 / 165円
キーボード / PC / 4950円
マウス / PC / 2750円
モニター / PC / 26400円
匿名型は、LINQ の結果を一時的に整形したいときによく使います。
別のデータ型に射影する
画面表示用や API 応答用に、別のクラスへ変換することもあります。
class ProductListItem
{
public string DisplayName { get; }
public string PriceText { get; }
public ProductListItem(string displayName, string priceText)
{
DisplayName = displayName;
PriceText = priceText;
}
}
List<ProductListItem> items = products
.Where(product => product.InStock)
.Select(product => new ProductListItem(
product.Name,
$"{product.Price:N0}円"
))
.ToList();
LINQ の Select は、単なるプロパティ抽出だけではありません。
アプリケーションで使いやすい形にデータを作り替えるためにも使えます。
SelectMany で入れ子のコレクションを平らにする
コレクションの中にコレクションがある場合は、SelectMany が便利です。
class Order
{
public int Id { get; }
public List<string> Items { get; }
public Order(int id, List<string> items)
{
Id = id;
Items = items;
}
}
List<Order> orders = new List<Order>
{
new Order(1, new List<string> { "ノート", "ペン" }),
new Order(2, new List<string> { "マウス" }),
new Order(3, new List<string> { "キーボード", "モニター" })
};
IEnumerable<string> allItems = orders.SelectMany(order => order.Items);
foreach (string item in allItems)
{
Console.WriteLine(item);
}
実行結果です。
ノート
ペン
マウス
キーボード
モニター
Select を使うと、IEnumerable<List<string>> のように入れ子のままになります。
SelectMany を使うと、複数のコレクションを 1 つの列挙に平らにできます。
OrderBy で並べ替える
OrderBy は、昇順に並べ替えるメソッドです。
var result = products.OrderBy(product => product.Price);
foreach (Product product in result)
{
Console.WriteLine($"{product.Name}: {product.Price}円");
}
実行結果です。
ペン: 150円
ノート: 200円
マウス: 2500円
キーボード: 4500円
モニター: 24000円
降順にしたい場合は、OrderByDescending を使います。
var result = products.OrderByDescending(product => product.Price);
ThenBy で第 2 条件を指定する
複数の条件で並べ替えたい場合は、ThenBy を使います。
var result = products
.OrderBy(product => product.Category)
.ThenBy(product => product.Price);
この例では、まずカテゴリ順に並べ、同じカテゴリの中では価格順に並べます。
foreach (Product product in result)
{
Console.WriteLine($"{product.Category} / {product.Name}: {product.Price}円");
}
実行結果です。
PC / マウス: 2500円
PC / キーボード: 4500円
PC / モニター: 24000円
文房具 / ペン: 150円
文房具 / ノート: 200円
降順の第 2 条件には、ThenByDescending を使います。
Reverse で順序を反転する
Reverse は、現在の並び順を反転します。
var result = products
.OrderBy(product => product.Price)
.Reverse();
ただし、最初から降順にしたいだけなら、OrderByDescending の方が意図が明確です。
var result = products.OrderByDescending(product => product.Price);
Reverse は、すでにある並びをそのまま逆向きにしたいときに使います。
非ジェネリックコレクションに LINQ を使う
古い API では、ArrayList のような非ジェネリックコレクションを扱うことがあります。
using System;
using System.Collections;
using System.Linq;
class Program
{
static void Main()
{
ArrayList values = new ArrayList
{
"Alice",
123,
"Bob",
DateTime.Today,
"Carol"
};
var names = values.OfType<string>();
foreach (string name in names)
{
Console.WriteLine(name);
}
}
}
実行結果です。
Alice
Bob
Carol
OfType<T>() は、指定した型の要素だけを取り出します。
非ジェネリックコレクションや、複数の型が混ざったコレクションから特定の型だけを扱いたいときに使えます。
Cast と OfType の違い
Cast<T>() は、すべての要素を指定した型に変換しようとします。
var names = values.Cast<string>();
この場合、int や DateTime が混ざっていると、列挙時に例外が発生します。
一方、OfType<T>() は、指定した型ではない要素を無視します。
var names = values.OfType<string>();
| メソッド | 動き |
|---|---|
Cast<T>() |
すべての要素を T として扱う。変換できない要素があると例外 |
OfType<T>() |
T の要素だけを取り出す |
混在データから特定の型だけを取り出すなら、OfType<T>() が使いやすいです。
まとめ
LINQ to Objects では、フィルター、射影、並べ替えを組み合わせてデータを扱います。
Whereは条件に合う要素だけを残すSelectは要素を別の形に変換する- 匿名型を使うと、一時的な結果形状を簡潔に作れる
SelectManyは入れ子のコレクションを平らにするOrderBy/OrderByDescendingは並べ替えを行うThenBy/ThenByDescendingは第 2 条件以降の並べ替えに使うReverseは現在の順序を反転する- 非ジェネリックコレクションには
Cast<T>()やOfType<T>()を使える
次回は、ページング、件数取得、集合演算、集計処理を見ていきます。