LINQ クエリ操作での型の関係 (C#)

クエリを効果的に記述するには、クエリ操作全体における変数の型の相互関係を理解する必要があります。 これらの関係を理解しておくと、このドキュメント内の LINQ のサンプルやコード例を理解しやすくなります。 さらに、var を使用して変数を暗黙的に型指定した場合に何が起こるかも理解できます。

LINQ クエリ操作は、データ ソース、クエリ自体、およびクエリの実行において厳密に型指定されます。 クエリの変数の型には、データ ソース内の要素の型および foreach ステートメントの反復変数の型との互換性が必要です。 この厳密な型指定により、コンパイル時に型のエラーが検出され、実際にエラーが発生する前にそのエラーを修正できます。

これらの型の関係を示すために、後の例の大部分では、すべての変数に明示的な型指定を使用しています。 最後の例は、var を使用することで暗黙的型指定を使用する場合でも、同じ基本原則が適用されることを示しています。

ソース データを変換しないクエリ

次の図は、データの変換を行わない LINQ to Objects クエリ操作を示しています。 ソースには文字列のシーケンスが含まれているので、クエリ出力も文字列のシーケンスです。

Diagram that shows the relation of data types in a LINQ query.

  1. データ ソースの型引数によって、範囲変数の型が決まります。
  2. 選択したオブジェクトの型によって、クエリ変数の型が決まります。 ここでは、name は文字列です。 したがって、クエリ変数は IEnumerable<string> になります。
  3. クエリ変数は、foreach ステートメントで反復処理されます。 クエリ変数は文字列のシーケンスなので、反復変数も文字列です。

ソース データを変換するクエリ

次の図は、単純なデータ変換を行う LINQ to SQL クエリ操作を示しています。 このクエリは、Customer オブジェクトのシーケンスを入力として受け取り、Name プロパティのみを結果に選択します。 Name は文字列なので、クエリは出力として文字列のシーケンスを作成します。

Diagram showing a query that transforms the data type.

  1. データ ソースの型引数によって、範囲変数の型が決まります。
  2. select ステートメントは、完全な Name オブジェクトではなく、Customer プロパティを返します。 Name は文字列なので、custNameQuery の型引数は string ではなく Customer になります。
  3. custNameQuery は文字列のシーケンスなので、foreach ループの反復変数も string です。

次の図は、もう少し複雑な変換を示しています。 select ステートメントは、元の Customer オブジェクトのメンバーを 2 つだけ取り込む匿名型を返します。

Diagram showing a more complex query that transforms the data type.

  1. データ ソースの型引数は、常にクエリの範囲変数の型です。
  2. select ステートメントによって匿名型が生成されるため、クエリ変数は var を使用して暗黙的に型指定する必要があります。
  3. クエリ変数の型が暗黙的なので、foreach ループの反復変数も暗黙的にする必要があります。

コンパイラによる型情報の推論

クエリ操作における変数の関係を理解することは大切ですが、この処理をコンパイラで自動的に行う方法もあります。 var キーワードは、クエリ操作の任意のローカル変数に使用できます。 次の図は、前に説明した例 2 と類似しています。 ここでは、コンパイラがクエリ操作の各変数について、厳密な型を指定します。

Diagram that shows the type flow with implicit typing.

LINQ とジェネリック型 (C#)

LINQ クエリは、ジェネリック型に基づいています。 クエリを記述するために、ジェネリックについて詳しく知っておく必要ありません。 ただし、次の 2 つの基本的な概念を理解しておくと役立ちます。

  1. List<T> などのジェネリック コレクション クラスのインスタンスを作成するときに、リストに保持されるオブジェクトの型で "T" を置き換えます。 たとえば、文字列のリストは List<string> で表され、Customerオブジェクトのリストは List<Customer> で表されます。 ジェネリック リストは厳密に型指定されるため、要素を Object として格納するコレクションと比べて多くの利点があります。 CustomerList<string> に追加しようとすると、コンパイル時にエラーが発生します。 実行時に型キャストを実行する必要がないため、ジェネリック コレクションを使用するのは簡単です。
  2. IEnumerable<T> は、foreach ステートメントを使用してジェネリック コレクションのクラスを列挙できるようにするインターフェイスです。 ArrayList などの非ジェネリック コレクション クラスが IEnumerable をサポートするように、ジェネリック コレクション クラスは、IEnumerable<T> をサポートします。

ジェネリックの詳細については、「ジェネリック」を参照してください。

LINQ クエリの IEnumerable<T> 変数

LINQ クエリ変数は、IEnumerable<T>、または IQueryable<T> などの派生型として型指定されます。 IEnumerable<Customer> として型指定されたクエリ変数が見つかった場合は、クエリが実行されたときに 0 個以上の Customer オブジェクトのシーケンスが作成されることを意味します。

IEnumerable<Customer> customerQuery =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach (Customer customer in customerQuery)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

コンパイラがジェネリック型の宣言を処理できるようにする

必要に応じて、var キーワードを使用することにより、ジェネリックの構文を回避することもできます。 var キーワードは、from 句で指定されたデータ ソースを調べてクエリ変数の型を推論するようにコンパイラに指示します。 次の例では、前の例と同じコンパイル済みのコードが生成されます。

var customerQuery2 =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach(var customer in customerQuery2)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

var キーワードは、変数の型が明らかな場合、または入れ子にされたジェネリック型 (グループ クエリで生成されたものなど) を明示的に指定する必要がない場合に便利です。 一般に、var を使用すると、他の開発者にとってコードが読みにくくなる可能性があることを理解しておくことをお勧めします。 詳細については、「暗黙的に型指定されるローカル変数」を参照してください。