前回は、ラムダ式について見ました。
今回からは、LINQ to Objects を整理します。
LINQ は、配列、リスト、辞書などのデータに対して、検索、絞り込み、変換、並べ替え、集計などを統一的に書くための仕組みです。
その中でも LINQ to Objects は、メモリ上にある .NET オブジェクトのコレクションを対象にします。
int[] numbers = { 1, 2, 3, 4, 5, 6 };
IEnumerable<int> evenNumbers = numbers.Where(number => number % 2 == 0);
このように、配列に対して条件を指定し、偶数だけを取り出せます。
LINQ to Objects とは
LINQ は、Language Integrated Query の略です。
日本語にすると、「言語に統合された問い合わせ機能」と考えるとよいでしょう。
LINQ to Objects は、その LINQ を .NET のオブジェクトコレクションに対して使うものです。
対象になる代表例は次のようなものです。
- 配列
List<T>Dictionary<TKey, TValue>HashSet<T>IEnumerable<T>を実装した独自コレクション
たとえば、次のような商品一覧があるとします。
using System;
using System.Collections.Generic;
using System.Linq;
class Product
{
public string Name { get; }
public int Price { get; }
public bool InStock { get; }
public Product(string name, int price, bool inStock)
{
Name = name;
Price = price;
InStock = inStock;
}
}
この商品一覧から、在庫がある商品だけを取り出せます。
List<Product> products = new List<Product>
{
new Product("ノート", 200, true),
new Product("キーボード", 4500, true),
new Product("マウス", 2500, false)
};
IEnumerable<Product> inStockProducts =
products.Where(product => product.InStock);
foreach (Product product in inStockProducts)
{
Console.WriteLine(product.Name);
}
実行結果です。
ノート
キーボード
Where は、条件に合う要素だけを残す LINQ メソッドです。
LINQ が解決すること
LINQ を使わない場合、条件に合う要素を取り出すには foreach と if を組み合わせます。
List<Product> result = new List<Product>();
foreach (Product product in products)
{
if (product.InStock)
{
result.Add(product);
}
}
もちろん、この書き方でも正しく動きます。
しかし、データ処理が複雑になると、次のような処理が積み重なります。
- 条件で絞り込む
- 必要な形に変換する
- 並べ替える
- 件数を数える
- 合計を出す
- 重複を取り除く
LINQ を使うと、これらを「何をしたいか」に近い形で書けます。
List<string> names = products
.Where(product => product.InStock)
.OrderBy(product => product.Price)
.Select(product => product.Name)
.ToList();
このコードは、次のように読めます。
- 在庫がある商品に絞る
- 価格順に並べる
- 商品名だけを取り出す
- リストにする
処理の意図が、コードの流れとして見えやすくなります。
LINQ を使うための using
LINQ の多くのメソッドは、System.Linq 名前空間にあります。
using System.Linq;
Where、Select、OrderBy、Count、ToList などは、Enumerable クラスの拡張メソッドです。
そのため、using System.Linq; がないと、次のようなメソッドを呼び出せません。
numbers.Where(number => number > 0);
LINQ を使うコードでは、まず using System.Linq; を確認するとよいでしょう。
配列に LINQ を適用する
LINQ to Objects は、配列にも使えます。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
int[] numbers = { 10, 3, 7, 20, 1, 15 };
IEnumerable<int> result = numbers
.Where(number => number >= 10)
.OrderBy(number => number);
foreach (int number in result)
{
Console.WriteLine(number);
}
}
}
実行結果です。
10
15
20
配列は IEnumerable<T> として列挙できるため、LINQ の対象になります。
クエリ構文
LINQ には、SQL に似たクエリ構文があります。
IEnumerable<int> result =
from number in numbers
where number >= 10
orderby number
select number;
完全な例です。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
int[] numbers = { 10, 3, 7, 20, 1, 15 };
IEnumerable<int> result =
from number in numbers
where number >= 10
orderby number
select number;
foreach (int number in result)
{
Console.WriteLine(number);
}
}
}
実行結果です。
10
15
20
from でデータソースから要素を取り出し、where で条件を指定し、orderby で並べ替え、select で結果を選択しています。
メソッド構文
同じ処理は、メソッド構文でも書けます。
IEnumerable<int> result = numbers
.Where(number => number >= 10)
.OrderBy(number => number)
.Select(number => number);
メソッド構文では、Where や OrderBy などの拡張メソッドをつないで書きます。
クエリ構文とメソッド構文は、どちらも LINQ の書き方です。
| 構文 | 特徴 |
|---|---|
| クエリ構文 | SQL に近く、複雑な問い合わせを読みやすく書ける |
| メソッド構文 | ラムダ式と相性がよく、すべての LINQ メソッドを使いやすい |
実務では、メソッド構文を見かけることが多いです。
ただし、join や複数の from を使う場合は、クエリ構文の方が読みやすいこともあります。
暗黙的に型指定されたローカル変数
LINQ の結果は、型が長くなることがあります。
たとえば、Where の結果は IEnumerable<T> です。
IEnumerable<Product> result = products.Where(product => product.InStock);
このような場合、var を使うと簡潔に書けます。
var result = products.Where(product => product.InStock);
var は、型がなくなるわけではありません。コンパイラが右辺から型を推論します。
LINQ では匿名型を扱うこともあるため、var がよく使われます。
var summaries = products.Select(product => new
{
product.Name,
TaxIncludedPrice = product.Price * 1.1
});
このような匿名型には明示的な型名がないため、var が必要になります。
オブジェクト初期化子とコレクション初期化子
LINQ のサンプルでは、オブジェクト初期化子やコレクション初期化子もよく使います。
class Customer
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
List<Customer> customers = new List<Customer>
{
new Customer { Name = "Alice", Age = 30 },
new Customer { Name = "Bob", Age = 22 },
new Customer { Name = "Carol", Age = 41 }
};
このようにデータを短く用意できると、LINQ の処理部分に集中できます。
var adults = customers.Where(customer => customer.Age >= 30);
LINQ そのものではありませんが、LINQ と一緒によく使う C# 構文です。
拡張メソッドとしての LINQ
LINQ の Where や Select は、コレクション型そのものに定義されているメソッドではありません。
多くは System.Linq.Enumerable クラスにある拡張メソッドです。
次の 2 つは、ほぼ同じ意味です。
var result1 = numbers.Where(number => number >= 10);
var result2 = Enumerable.Where(numbers, number => number >= 10);
通常は、拡張メソッド構文で書きます。
numbers.Where(number => number >= 10)
この書き方により、配列やリストに直接 Where が生えているように見えます。
遅延実行
LINQ を理解するうえで重要なのが、遅延実行です。
多くの LINQ メソッドは、クエリを作った時点では実行されません。
実際に列挙されたときに実行されます。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(number =>
{
Console.WriteLine($"判定: {number}");
return number >= 2;
});
Console.WriteLine("クエリを作成しました");
foreach (int number in query)
{
Console.WriteLine($"結果: {number}");
}
}
}
実行結果です。
クエリを作成しました
判定: 1
判定: 2
結果: 2
判定: 3
結果: 3
Where を呼んだ時点では、まだ判定処理は実行されていません。
foreach で列挙されたタイミングで、初めて処理が動きます。
遅延実行で結果が変わる例
遅延実行では、クエリを作ったあとに元データを変更すると、列挙時のデータが反映されます。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(number => number >= 2);
numbers.Add(4);
foreach (int number in query)
{
Console.WriteLine(number);
}
}
}
実行結果です。
2
3
4
query を作った時点では 4 はありませんでした。
しかし、列挙する前に numbers.Add(4) しているため、結果に 4 が含まれます。
これは便利なこともありますが、意図しない結果になることもあります。
即時実行
LINQ の結果をその場で確定したい場合は、ToList() や ToArray() を使います。
List<int> result = numbers
.Where(number => number >= 2)
.ToList();
ToList() を呼んだ時点でクエリが実行され、結果がリストとして確定します。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3 };
List<int> result = numbers
.Where(number => number >= 2)
.ToList();
numbers.Add(4);
foreach (int number in result)
{
Console.WriteLine(number);
}
}
}
実行結果です。
2
3
ToList() の時点で結果が確定しているため、その後に追加した 4 は含まれません。
LINQ クエリの結果を返す
メソッドから LINQ の結果を返す場合、遅延実行のまま返すか、即時実行して返すかを意識します。
static IEnumerable<Product> FindInStockProducts(IEnumerable<Product> products)
{
return products.Where(product => product.InStock);
}
このメソッドは、遅延実行のクエリを返します。
呼び出し側が列挙したタイミングで、実際の処理が動きます。
結果を確定して返したいなら、ToList() を使います。
static List<Product> FindInStockProducts(List<Product> products)
{
return products
.Where(product => product.InStock)
.ToList();
}
どちらが正しいかは状況によります。
| 戻り値 | 向いている場面 |
|---|---|
IEnumerable<T> |
呼び出し側に列挙タイミングを任せたい |
List<T> |
結果をその時点で確定したい |
T[] |
配列として扱いたい |
遅延実行は LINQ の強力な特徴ですが、結果をいつ確定するかを意識することが大切です。
まとめ
LINQ to Objects は、メモリ上の .NET オブジェクトコレクションに対して問い合わせを行う仕組みです。
- 配列や
List<T>など、IEnumerable<T>として列挙できるものに使える Where、Select、OrderByなどはEnumerableの拡張メソッドである- クエリ構文とメソッド構文の 2 種類がある
- LINQ では
var、ラムダ式、匿名型、オブジェクト初期化子がよく使われる - 多くの LINQ メソッドは遅延実行される
ToList()やToArray()を使うと即時実行して結果を確定できる- メソッドから返すときは、遅延実行のまま返すか、結果を確定して返すかを意識する
次回は、LINQ to Objects の中心になるフィルター、射影、並べ替えを見ていきます。