C# での LINQ クエリの概要

"クエリ" は、データ ソースからデータを取得する式です。 リレーショナル データベース用の SQL や XML 用の XQuery など、データ ソースによってネイティブ クエリ言語は異なります。 開発者は、サポートする必要のあるデータ ソースの種類やデータ形式ごとに、新しいクエリ言語を習得しなければなりません。 LINQ は、さまざまな種類のデータ ソースやデータ形式に対して一貫した C# 言語モデルを提供することにより、この負担を軽減します。 LINQ クエリでは、常に C# オブジェクトを操作することになります。 共通の基本的なコーディング パターンを使用することで、LINQ プロバイダーが利用可能なときに、XML ドキュメント、SQL データベース、.NET コレクション、その他の任意の形式のデータを照会したり変換したりできます。

クエリ操作の 3 つの手順

LINQ クエリ操作はすべて、次の 3 つの手順で構成されます。

  1. データ ソースを取得します。
  2. クエリを作成します。
  3. クエリを実行します。

クエリ操作の 3 つの手順がソース コードでどのように表されるかを次の例に示します。 この例では、わかりやすくするために整数の配列をデータ ソースとして使用していますが、他のデータ ソースを使用する場合にも同じ概念が当てはまります。 この例は、この記事の残りの部分全体を通して参照されます。

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

次の図は、クエリ操作全体を表しています。 LINQ では、クエリの実行はクエリ自体とは別個のものです。 つまり、クエリ変数を作成しても、データは取得されません。

完全な LINQ クエリ操作の図。

データ ソース

前の例ではデータ ソースが配列で、ジェネリック IEnumerable<T> インターフェイスがサポートされています。 つまり、LINQ でクエリを実行できるということです。 クエリは foreach ステートメントで実行されますが、foreach には IEnumerable または IEnumerable<T> が必要です。 IEnumerable<T> をサポートする型や、ジェネリック IQueryable<T> などの派生インターフェイスは、クエリ可能型と呼ばれます。

クエリ可能型は、変更や特別な処理を行わなくても、LINQ データ ソースとして使用できます。 ソース データがメモリ内にクエリ可能型として存在していない場合、LINQ プロバイダーは、そのような型としてソース データを表す必要があります。 たとえば、LINQ to XML では、クエリ可能な XElement 型に XML ドキュメントが読み込まれます。

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

EntityFramework では、C# クラスとデータベース スキーマの間にオブジェクト リレーショナル マッピングを作成します。 オブジェクトに対するクエリを記述すると、実行時には、EntityFramework によってデータベースとの通信が処理されます。 次の例では、Customers がデータベース内の特定のテーブルを表し、クエリ結果の型 IQueryable<T>IEnumerable<T> から派生しています。

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

それぞれの種類のデータ ソースを作成する方法の詳細については、対応する LINQ プロバイダーのドキュメントを参照してください。 ただし、基本的な規則は単純です。LINQ データ ソースは、ジェネリック IEnumerable<T> インターフェイス、またはこれを継承するインターフェイスをサポートする任意のオブジェクト (通常は IQueryable<T>) です。

Note

非ジェネリック IEnumerable インターフェイスをサポートする ArrayList などの型も、LINQ データ ソースとして使用できます。 詳細については、「LINQ を使用して ArrayList にクエリを実行する方法 (C#)」を参照してください。

クエリ

クエリでは、データ ソースからどのような情報を取得するかを指定します。 オプションとして、情報が返される前に、その情報を並べ替え、グループ化し、構造化する方法を指定することもできます。 クエリはクエリ変数に格納され、クエリ式で初期化されます。 C# クエリ構文を使用してクエリを記述します。

前の例のクエリでは、整数の配列からすべての偶数が返されます。 このクエリ式には、fromwhereselect の 3 つの句が含まれています。 (SQL に詳しい方は、句の順番が SQL での順番とは逆になっていることに気が付いたと思います)。from 句はデータ ソースを指定し、where 句はフィルターを適用し、select 句は返される要素の種類を指定します。 このセクションでは、すべてのクエリ句について詳しく説明しています。 今の段階で重要な点は、LINQ では、クエリ変数自体は何も処理を行わず、データを返さないという点です。 この時点では、後でクエリが実行されるときに結果の生成に必要となる情報が格納されるだけです。 どのようにクエリが構築されるかについては、「標準クエリ演算子の概要 (C#)」をご覧ください。

Note

クエリは、メソッド構文を使用して表すこともできます。 詳細については、「LINQ でのクエリ構文とメソッド構文」を参照してください。

実行方法による標準クエリ演算子の分類

標準クエリ演算子メソッドの LINQ to Objects の実装は、主要な 2 つの方法である "即時" と "遅延" のいずれかで実行されます。 遅延実行を使うクエリ演算子は、さらに 2 つのカテゴリ "ストリーミング" と "非ストリーミング" に分けることができます。

イミディエイト

即時実行とは、データ ソースが読み取られ、演算が 1 回実行されることを意味します。 スカラー結果を返す標準クエリ演算子はすべて即時に実行されます。 このようなクエリには、CountMaxAverageFirst などがあります。 これらのメソッドでは、明示的に foreach ステートメントを使用しなくても処理が実行されます。これは、結果を返すためにクエリ自体が foreach を使用する必要があるからです。 これらのクエリでは、IEnumerable コレクションではなく、単一の値が返されます。 Enumerable.ToList または Enumerable.ToArray メソッドを使って、"任意の" クエリを強制的に即時実行できます。 即時実行では、クエリ宣言ではなく、クエリ結果を再利用できます。 結果は 1 回取得され、後で使用するために格納されます。 次のクエリは、ソース配列に含まれている偶数の数を返します。

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

クエリの即時実行を強制し、その結果をキャッシュするには、ToList メソッドまたは ToArray メソッドを呼び出します。

List<int> numQuery2 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToArray();

クエリ式の直後に foreach ループを配置することでも実行を強制できます。 ただし、ToList または ToArray を呼び出した場合は、単一のコレクション オブジェクトにすべてのデータをキャッシュする処理も行われます。

Deferred

遅延実行とは、クエリが宣言されているコード内の位置では演算が実行されないことを意味します。 演算は、foreach ステートメントを使用するなどの方法により、クエリ変数が列挙されたときにのみ実行されます。 クエリの実行結果は、クエリが定義されたときではなく、クエリが実行されたときのデータ ソースの内容に依存します。 クエリ変数が複数回列挙される場合は、そのたびに結果が変わる可能性があります。 戻り値の型が IEnumerable<T> または IOrderedEnumerable<TElement> の標準クエリ演算子は、ほとんどが遅延実行されます。 遅延実行では、クエリ結果が反復されるたびに、クエリでデータ ソースから更新されたデータがフェッチされるため、クエリの再利用機能が提供されます。 次に示すコードは、遅延実行の例です。

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

foreach ステートメントでは、クエリ結果の取得も行われます。 たとえば、前のクエリでは、返されるシーケンスの各値が反復変数 num に (一度に 1 つずつ) 格納されます。

クエリ変数自体にはクエリ結果は格納されないので、クエリを繰り返し実行して更新データを取得することができます。 たとえば、別のアプリケーションがデータベースを継続的に更新している場合があります。 "自分の" アプリケーションでは、最新のデータを取得するクエリを 1 つ作成し、それを定期的に実行して更新された結果を取得できます。

遅延実行を使うクエリ演算子は、さらにストリーミングまたは非ストリーミングに分類できます。

ストリーミング

ストリーミング演算子では、要素を生成する前にすべてのソース データを読み取る必要はありません。 実行時に、ストリーミング演算子は読み取ったソース要素ごとに演算を実行し、必要に応じて要素を生成します。 ストリーミング演算子は、結果の要素を生成できるまでソース要素の読み取りを続行します。 つまり、結果の要素を 1 つ生成するために複数のソース要素が読み取られる場合があります。

非ストリーミング

非ストリーミング演算子では、結果の要素を生成する前にすべてのソース データを読み取る必要があります。 並べ替えやグループ化などの演算はこのカテゴリに分類されます。 実行時に、非ストリーミング クエリ演算子はすべてのソース データを読み取ってデータ構造に格納し、演算を実行して結果の要素を生成します。

分類表

次の表では、各標準クエリ演算子メソッドを、その実行方法に基づいて分類しています。

注意

2 つの列にマークが付けられている演算子では、2 つの入力シーケンスが演算に使用され、各シーケンスの評価は異なります。 この場合、遅延実行のストリーミングで評価されるのは、常にパラメーター リストの最初のシーケンスになります。

標準クエリ演算子 の戻り値の型 : 即時実行 遅延実行 (ストリーミング) 遅延非ストリーミング実行
Aggregate TSource x
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average 1 つの数値 x
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max 単一数値、 TSource または TResult? X
Min 単一数値、 TSource または TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum 1 つの数値 x
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource[] 配列 X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> x

LINQ to Objects

"LINQ to Objects" とは、任意の IEnumerable または IEnumerable<T> コレクションで直接 LINQ クエリを使うことです。 LINQ を使って、List<T>ArrayDictionary<TKey,TValue> などの任意の列挙可能なコレクションのクエリを実行できます。 コレクションは、ユーザー定義でも、.NET API によって返される型でもかまいません。 LINQ を使用する場合は、何を取得するかを表す宣言コードを記述します。 LINQ to Objects は、LINQ でのプログラミングを始めるのに最適です。

LINQ クエリには、従来の foreach ループと比べて、3 つの重要な利点があります。

  • 簡潔で読みやすい (特に複数の条件をフィルター処理する場合)。
  • 強力なフィルター処理、並べ替え、およびグループ化機能を最小限のアプリケーション コードで実現できる。
  • ほとんど、またはまったく変更せずに、他のデータ ソースに移植できる。

データに対して実行する操作が複雑になるほど、従来の反復処理手法の代わりに LINQ を使用する利便性が高くなります。

クエリ結果をメモリに格納する

クエリは、基本的に、データの取得方法と編成方法を指示するための一連の命令です。 結果の各項目は順次要求されるため、クエリは遅延実行されます。 foreach を使用して結果を反復すると、項目はアクセスのたびに返されます。 クエリを評価し、foreach のループを実行せずに結果を格納するには、クエリ変数で次のメソッドのいずれかを呼び出します。

次の例で示すように、クエリ結果を格納するときは、返されたコレクション オブジェクトを新しい変数に代入する必要があります。

List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];

IEnumerable<int> queryFactorsOfFour =
    from num in numbers
    where num % 4 == 0
    select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

関連項目