データのクエリを実行する C# LINQ クエリを記述します

統合言語クエリ (LINQ) の入門的なドキュメントでは、ほとんどのクエリが、LINQ の宣言型クエリ構文を使用して記述されています。 ただし、クエリ構文は、コードのコンパイル時に、.NET 共通言語ランタイム (CLR) 用のメソッド呼び出しに変換する必要があります。 これらのメソッド呼び出しが、標準クエリ演算子 (WhereSelectGroupByJoinMaxAverage など) を呼び出します。 これらは、クエリ構文ではなくメソッド構文を使用して直接呼び出すことができます。

クエリの構文とメソッドの構文は意味的には同じものですが、多くの場合、クエリ構文のほうがシンプルで読みやすいと感じられます。 一部のクエリは、メソッド呼び出しとして表現する必要があります。 たとえば、指定した条件に一致する要素の数を取得するクエリを表すには、メソッド呼び出しを使用する必要があります。 また、ソース シーケンスで最大の値を持つ要素を取得するクエリにも、メソッド呼び出しを使用する必要があります。 System.Linq 名前空間の標準クエリ演算子のリファレンス ドキュメントでは、通常、メソッド構文が使用されます。 クエリやクエリ式自体でのメソッド構文の使い方を、よく理解しておく必要があります。

標準クエリ演算子の拡張メソッド

次の例は、シンプルなクエリ式と、メソッド ベースのクエリとして記述された、意味的に同等のクエリを示したものです。

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
    from num in numbers
    where num % 2 == 0
    orderby num
    select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)
{
    Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
    Console.Write(i + " ");
}

2 つの例からの出力は同じです。 クエリ変数の型は、どちらの形式でも同じです: IEnumerable<T>

メソッド ベースのクエリを理解するために、より詳しく調べていきましょう。 式の右辺で、where 句が IEnumerable<int> 型の numbers オブジェクトのインスタンス メソッドとして表されていることに注意してください。 ジェネリック型の IEnumerable<T> インターフェイスについて知識があれば、それには Where メソッドがないことがわかります。 しかし、Visual Studio IDE で IntelliSense の入力候補一覧を呼び出すと、Where メソッドだけでなく、SelectSelectManyJoinOrderby など、他にも多くのメソッドが表示されます。 これらのメソッドでは、標準クエリ演算子が実装されています。

Screenshot showing all the standard query operators in Intellisense.

IEnumerable<T> には追加のメソッドが含まれているように見えますが、含まれてはいません。 標準クエリ演算子は、"拡張メソッド" として実装されます。 拡張メソッドは、既存の型を "拡張" します。これらは、あたかもその型のインスタンス メソッドであるかのように呼び出すことができます。 標準クエリ演算子が IEnumerable<T> を拡張しているため、numbers.Where(...) を書き込むことができます。

拡張メソッドを使うには、using ディレクティブを使ってそれらをスコープ内に入れます。 アプリケーションの観点から見れば、拡張メソッドは通常のインスタンス メソッドと同じものです。

拡張メソッドについて詳しくは、「拡張メソッド」をご覧ください。 標準クエリ演算子について詳しくは、「標準クエリ演算子の概要 (C#)」をご覧ください。 一部の LINQ プロバイダー (Entity Framework や LINQ to XML など) では、IEnumerable<T> 以外の型のために、独自の標準クエリ演算子と拡張メソッドが実装されています。

ラムダ式

上の例で、条件式 (num % 2 == 0) がインライン引数として Enumerable.Where メソッドに渡さていることに注意してください: Where(num => num % 2 == 0).。このインライン式は、ラムダ式です。 この方法を使うと、本来であればより複雑な形式で記述しなければならないコードを、簡単に記述できます。 演算子の左側にある num は、クエリ式の num に対応する入力変数です。 コンパイラは、numbers がジェネリック IEnumerable<T> 型であることがわかっているため、num の型を推論できます。 ラムダの本体は、クエリ構文や、C# の他の式やステートメントでの式と同じです。 これには、メソッド呼び出しや他の複雑なロジックを含めることができます。 戻り値は式の結果にすぎません。 特定のクエリはメソッド構文でしか表現できず、その一部ではラムダ式が必要になります。 ラムダ式は、LINQ ツールボックスの強力で柔軟なツールです。

クエリの構成可能性

上のコード例の Enumerable.OrderBy メソッドは、Where の呼び出しでドット演算子を使って呼び出されています。 Where によってフィルター処理されたシーケンスが生成されます。その後、Where で生成されたシーケンスは、Orderby によって並べ替えられます。 クエリが IEnumerable を返すので、開発者は、メソッド呼び出しをつないでいきながら、メソッド構文でそれらを編成します。 クエリ構文を使ってクエリを記述すると、コンパイラによってこの構成が行われます。 クエリの結果はクエリ変数に格納されないので、いつでも (クエリを実行した後でも) それを変更したり、新しいクエリのベースとして使ったりできます。

以下の例では、上記の各アプローチを使用したシンプルな LINQ クエリを示します。

Note

これらのクエリでは、シンプルなメモリ内コレクションに対して操作を実行します。ただし、基本的な構文は、LINQ to Entities および LINQ to XML で使用されるのと同じものです。

例 - クエリ構文

ほとんどのクエリは、"クエリ構文" を使って "クエリ式" を作成することで記述します。 次の例は、3 つのクエリ式を示しています。 1 つ目のクエリ式は、where 句を使用した条件を適用することで、結果をフィルター処理または制限する方法を示しています。 このクエリは、値が 7 より大きいか、または 3 より小さいソース シーケンス内のすべての要素を返します。 2 つ目の式は、返された結果を並べ替える方法を示しています。 3 つ目の式は、キーに基づいて結果をグループ化する方法を示しています。 このクエリは、単語の頭文字に基づいて 2 つのグループを返します。

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

クエリの型は IEnumerable<T> です。 これらのクエリはすべて、次の例で示すように、var を使用しても記述できます。

var query = from num in numbers...

上の各例のクエリは、foreach ステートメントまたは他のステートメントでクエリ変数を反復処理するまで、実際には実行されません。

例 - メソッド構文

一部のクエリ操作は、メソッド呼び出しとして表す必要があります。 このようなメソッドで最も一般的なものは、SumMaxMinAverage など、単一の数値を返すメソッドです。 これらのメソッドは、単一の値を返し、追加のクエリ操作のソースとして使用できないため、常に、すべてのクエリの後で呼び出す必要があります。 次の例は、クエリ式でのメソッド呼び出しを示したものです。

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

メソッドに System.Action または System.Func<TResult> パラメーターがある場合、これらの引数は、次の例で示すように、ラムダ式の形式で提供されます。

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

前のクエリでは、単一の値を返し、ジェネリック IEnumerable<T> コレクションを返さないクエリ #4 のみが、すぐに実行されます。 メソッド自体では、その値を計算するために foreach または同様のコードが使われます。

上の各クエリは、次の例で示すように、`var`` で暗黙の型指定を使って記述できます。

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

例 - クエリとメソッド構文の併用

この例では、クエリ句の結果に対してメソッド構文を使用する方法を示します。 方法は、クエリ式をかっこで囲み、ドット演算子を適用して、メソッドを呼び出すだけです。 次の例では、Query #7 は、値が 3 ~ 7 である数値のカウントを返します。 ただし、一般には、2 番目の変数を使ってメソッド呼び出しの結果を格納することをお勧めします。 この方法のほうが、クエリをクエリ結果と混同する恐れがありません。

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

Query #7 はコレクションではなく単一の値を返すので、クエリはすぐに実行されます。

上記のクエリは、var による暗黙的型指定を使用しても記述できます。次に例を示します。

var numCount = (from num in numbers...

次のように、メソッド構文で記述することもできます。

var numCount = numbers.Count(n => n is > 3 and < 7);

次のように、明示的な型指定を使用して記述することもできます。

int numCount = numbers.Count(n => n is > 3 and < 7);

こちらもご覧ください