LINQ: .NET Language-Integrated クエリ

 

Don Box、Anders Hejlsberg

2007 年 2 月

適用対象:
   Visual Studio Code 名 "Orcas"
   .NET Framework 3.5

概要:.NET Frameworkに追加された汎用クエリ機能は、リレーショナル データや XML データだけでなく、すべての情報源に適用されます。 この機能は、.NET Language-Integrated クエリ (LINQ) と呼ばれます。 (32ページ印刷)

内容

.NET Language-Integrated クエリ
標準クエリ演算子を使用したはじめに
LINQ プロジェクトをサポートする言語機能
その他の標準クエリ演算子
クエリ構文
LINQ to SQL: SQL 統合
LINQ to XML: XML 統合
まとめ

.NET Language-Integrated クエリ

20 年後、業界はオブジェクト指向 (OO) プログラミング テクノロジの進化において安定したポイントに達しました。 プログラマは、クラス、オブジェクト、メソッドなどの機能を許可するようになりました。 現在および次世代のテクノロジを見ると、プログラミング テクノロジの次の大きな課題は、OO テクノロジを使用してネイティブに定義されていない情報へのアクセスと統合の複雑さを軽減することです。 非 OO 情報の最も一般的な 2 つのソースは、リレーショナル データベースと XML です。

LINQ プロジェクトでは、リレーショナルまたは XML 固有の機能をプログラミング言語とランタイムに追加するのではなく、より一般的なアプローチを採用し、リレーショナルデータや XML データだけでなく、すべての情報源に適用される汎用クエリ機能を.NET Frameworkに追加しています。 この機能は、.NET Language-Integrated クエリ (LINQ) と呼ばれます。

言語統合クエリという用語を使用して、クエリが開発者の主要なプログラミング言語 (Visual C#、Visual Basic など) の統合機能であることを示します。 言語統合クエリを使用すると、 クエリ式 は、以前は命令型コードでのみ使用できた豊富なメタデータ、コンパイル時の構文チェック、静的な型指定、IntelliSense の恩恵を受けることができます。 言語統合クエリでは、単一の汎用宣言型クエリ機能を、外部ソースからの情報だけでなく、すべてのメモリ内情報に適用することもできます。

.NET Language-Integrated クエリでは、トラバーサル、フィルター、プロジェクション操作を任意の で直接宣言的な方法で表現できる汎用 標準クエリ演算子 のセットを定義します。NET ベースのプログラミング言語。 標準クエリ演算子を使用すると、 任意の IEnumerable<T> ベースの情報ソースにクエリを適用できます。 LINQ を使用すると、サード パーティは、ターゲット ドメインまたはテクノロジに適した新しいドメイン固有の演算子を使用して、標準クエリ演算子のセットを拡張できます。 さらに重要なのは、サード パーティは、標準クエリ演算子を、リモート評価、クエリ変換、最適化などの追加サービスを提供する独自の実装に置き換えることもできます。 LINQ パターンの規則に従うことで、このような実装では、標準のクエリ演算子と同じ言語統合とツールサポートを利用できます。

クエリ アーキテクチャの拡張性は、LINQ プロジェクト自体で使用され、XML データと SQL データの両方に対して機能する実装を提供します。 XML (LINQ to XML) に対するクエリ演算子は、効率的で使いやすいメモリ内 XML 機能を使用して、ホスト プログラミング言語で XPath/XQuery 機能を提供します。 リレーショナル データ (LINQ to SQL) に対するクエリ演算子は、SQL ベースのスキーマ定義を共通言語ランタイム (CLR) 型システムに統合することに基づいています。 この統合により、リレーショナル モデルの表現力と、基になるストアでクエリ評価のパフォーマンスを直接維持しながら、リレーショナル データに対する強力な入力が提供されます。

標準クエリ演算子を使用したはじめに

動作中の言語統合クエリを確認するには、標準のクエリ演算子を使用して配列の内容を処理する単純な C# 3.0 プログラムから始めます。

using System;
using System.Linq;
using System.Collections.Generic;

class app {
  static void Main() {
    string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

    IEnumerable<string> query = from s in names 
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();

    foreach (string item in query)
      Console.WriteLine(item);
  }
}

このプログラムをコンパイルして実行すると、出力として表示されます。

BURKE
DAVID
FRANK
To understand how language-integrated query works, we need to dissect the
 first statement of our program.
IEnumerable<string> query = from s in names 
                           where s.Length == 5
                           orderby s
                           select s.ToUpper();

ローカル変数クエリはクエリで初期化されます。 クエリ式は、標準クエリ演算子またはドメイン固有の演算子から 1 つ以上のクエリ演算子を適用することで、1 つ以上の情報ソースに対して動作します。 この式では、3 つの標準クエリ演算子 (WhereOrderBySelect) を使用します。

Visual Basic 9.0 では LINQ もサポートされています。 Visual Basic 9.0 で記述された上記のステートメントを次に示します。

Dim query As IEnumerable(Of String) = From s in names _
                                     Where s.Length = 5 _
                   Order By s _
                   Select s.ToUpper()

ここに示す C# ステートメントと Visual Basic ステートメントの両方でクエリ式が使用されます。 foreach ステートメントと同様に、クエリ式は、手動で記述できるコードよりも便利な宣言型の短縮形です。 上記のステートメントは、C# に示されている次の明示的な構文と意味的に同じです。

IEnumerable<string> query = names 
                            .Where(s => s.Length == 5) 
                            .OrderBy(s => s)
                            .Select(s => s.ToUpper());

この形式のクエリは、 メソッドベースの クエリと呼ばれます。 WhereOrderBy、および Select 演算子の引数はラムダ式と呼ばれ、デリゲートと同様にコードのフラグメントです。 これにより、標準クエリ演算子をメソッドとして個別に定義し、ドット表記を使用して一緒に連結することができます。 これらのメソッドを組み合わせることで、拡張可能なクエリ言語の基礎が形成されます。

LINQ プロジェクトをサポートする言語機能

LINQ は完全に汎用言語機能に基づいて構築されており、その一部は C# 3.0 と Visual Basic 9.0 の新機能です。 これらの各機能には独自のユーティリティが備わっており、これらの機能をまとめて、クエリとクエリ可能な API を定義するための拡張可能な方法を提供します。 このセクションでは、これらの言語機能と、それらがより直接的で宣言型のクエリスタイルにどのように寄与するかについて説明します。

ラムダ式と式ツリー

多くのクエリ演算子を使用すると、ユーザーはフィルター処理、プロジェクション、またはキー抽出を実行する関数を提供できます。 クエリ機能はラムダ式の概念に基づいて構築されています。これにより、開発者は、後続の評価の引数として渡すことができる関数を簡単に記述できます。 ラムダ式は CLR デリゲートに似ていますが、デリゲート型で定義されたメソッド シグネチャに従う必要があります。 これを説明するために、 Func デリゲート型を使用して、上記のステートメントを同等のより明示的な形式に拡張できます。

Func<string, bool>   filter  = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();

IEnumerable<string> query = names.Where(filter) 
                                 .OrderBy(extract)
                                 .Select(project);

ラムダ式は、C# 2.0 での匿名メソッドの自然な進化です。 たとえば、次のような匿名メソッドを使用して、前の例を記述できます。

Func<string, bool>   filter  = delegate (string s) {
                                   return s.Length == 5; 
                               };

Func<string, string> extract = delegate (string s) { 
                                   return s; 
                               };

Func<string, string> project = delegate (string s) {
                                   return s.ToUpper(); 
                               };

IEnumerable<string> query = names.Where(filter) 
                                 .OrderBy(extract)
                                 .Select(project);

一般に、開発者は、名前付きメソッド、匿名メソッド、またはラムダ式をクエリ演算子と共に自由に使用できます。 ラムダ式には、作成に最も直接的でコンパクトな構文を提供できるという利点があります。 さらに重要なのは、ラムダ式をコードまたはデータとしてコンパイルできることです。これにより、ラムダ式をオプティマイザー、トランスレーター、エバリュエーターによって実行時に処理できます。

名前空間 System.Linq.Expressions は、従来の IL ベースのメソッド本体ではなく、特定のラムダ式に対して式ツリーが必要であることを示す、識別ジェネリック型の式<T> を定義します。 式ツリーは、ラムダ式の効率的なメモリ内データ表現であり、式の構造を透過的かつ明示的にします。

コンパイラが実行可能 IL または式ツリーを出力するかどうかの決定は、ラムダ式の使用方法によって決まります。 型がデリゲートである変数、フィールド、またはパラメーターにラムダ式が割り当てられると、コンパイラは匿名メソッドと同じ IL を出力します。 型が一部のデリゲート型T の式<T> である変数、フィールド、またはパラメーターにラムダ式が割り当てられると、コンパイラは代わりに式ツリーを出力します。

たとえば、次の 2 つの変数宣言について考えてみましょう。

Func<int, bool>             f = n => n < 5;
Expression<Func<int, bool>> e = n => n < 5;

変数 f は、直接実行可能なデリゲートへの参照です。

bool isSmall = f(2); // isSmall is now true

変数 e は、直接実行可能ではない式ツリーへの参照です。

bool isSmall = e(2); // compile error, expressions == data

効果的に不透明なコードであるデリゲートとは異なり、プログラム内の他のデータ構造と同様に式ツリーと対話できます。

Expression<Func<int, bool>> filter = n => n < 5;

BinaryExpression    body  = (BinaryExpression)filter.Body;
ParameterExpression left  = (ParameterExpression)body.Left;
ConstantExpression  right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}", 
                  left.Name, body.NodeType, right.Value);

上の例では、実行時に式ツリーを分解し、次の文字列を出力します。

n LessThan 5

実行時に式をデータとして扱うこの機能は、プラットフォームの一部である基本クエリ抽象化を活用するサードパーティライブラリのエコシステムを有効にするために重要です。 LINQ to SQL データ アクセス実装では、この機能を利用して、式ツリーをストアでの評価に適した T-SQL ステートメントに変換します。

拡張メソッド

ラムダ式は、クエリ アーキテクチャの重要な部分の 1 つです。 拡張メソッドは 別のメソッドです。 拡張メソッドは、動的言語で普及した "アヒルタイピング" の柔軟性と、静的に型指定された言語のパフォーマンスとコンパイル時の検証を組み合わせます。 拡張メソッドを使用すると、サード パーティは、型のパブリック コントラクトを新しいメソッドで拡張しながら、個々の型の作成者がそれらのメソッドの独自の特殊な実装を提供できるようにすることができます。

拡張メソッドは静的クラスで静的メソッドとして定義されますが、CLR メタデータの [System.Runtime.CompilerServices.Extension] 属性でマークされます。 言語は、拡張メソッドの直接構文を提供することをお勧めします。 C# では、拡張メソッドは、拡張メソッドの最初のパラメーターに適用する必要がある この 修飾子によって示されます。 最も単純なクエリ演算子の定義を見てみましょう。 ここで

namespace System.Linq {
  using System;
  using System.Collections.Generic;

  public static class Enumerable {
    public static IEnumerable<T> Where<T>(
             this IEnumerable<T> source,
             Func<T, bool> predicate) {

      foreach (T item in source)
        if (predicate(item))
          yield return item;
    }
  }
}

拡張メソッドの最初のパラメーターの型は、拡張機能が適用される型を示します。 上の例では、Where 拡張メソッドによって IEnumerable<T> 型が拡張されています。 Where は静的メソッドであるため、他の静的メソッドと同様に直接呼び出すことができます。

IEnumerable<string> query = Enumerable.Where(names, 
                                          s => s.Length < 6);

ただし、拡張メソッドが一意になるのは、インスタンス構文を使用して呼び出すこともできます。

IEnumerable<string> query = names.Where(s => s.Length < 6);

拡張メソッドは、スコープ内の拡張メソッドに基づいてコンパイル時に解決されます。 C# の using ステートメントまたは Visual Basic の Import ステートメントを使用して名前空間をインポートすると、その名前空間の静的クラスによって定義されているすべての拡張メソッドがスコープに取り込まれます。

標準クエリ演算子は、 System.Linq.Enumerable 型の拡張メソッドとして定義されます。 標準のクエリ演算子を調べると、 IEnumerable<T> インターフェイスの観点から、その一部を含むすべてが定義されていることがわかります。 つまり、 すべての IEnumerable<T> 互換情報ソースは、C# で次の using ステートメントを追加するだけで、標準のクエリ演算子を取得します。

using System.Linq; // makes query operators visible

特定の型の標準クエリ演算子を置き換えるユーザーは、特定の型に対して独自の同じ名前のメソッドを互換性のあるシグネチャで定義するか、特定の型を拡張する新しい同じ名前の拡張メソッドを定義します。 標準のクエリ演算子を完全に除外したいユーザーは、 単に System.Linq をスコープに入れて 、IEnumerable<T> 用の独自の拡張メソッドを記述することはできません。

拡張メソッドには、解決の観点から最も低い優先順位が与えられ、ターゲット型とその基本型に適切な一致がない場合にのみ使用されます。 これにより、ユーザー定義型は、標準演算子よりも優先される独自のクエリ演算子を提供できます。 たとえば、次のカスタム コレクションを考えてみましょう。

public class MySequence : IEnumerable<int> {
  public IEnumerator<int> GetEnumerator() {
    for (int i = 1; i <= 10; i++) 
      yield return i; 
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator(); 
  }

  public IEnumerable<int> Where(Func<int, bool> filter) {
    for (int i = 1; i <= 10; i++) 
      if (filter(i)) 
        yield return i;
  }
}

このクラス定義を指定すると、インスタンス メソッドが拡張メソッドよりも優先されるため、次のプログラムは拡張メソッドではなく MySequence.Where 実装を使用します。

MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
    Console.WriteLine(item);

OfType 演算子は、IEnumerable<T> ベースの情報ソースを拡張しない数少ない標準クエリ演算子の 1 つです。 OfType クエリ演算子を見てみましょう。

public static IEnumerable<T> OfType<T>(this IEnumerable source) {
  foreach (object item in source) 
    if (item is T) 
      yield return (T)item;
}

OfType、IEnumerable<T> ベースのソースだけでなく、.NET Frameworkのバージョン 1.0 に存在していたパラメーター化されていない IEnumerable インターフェイスに対して書き込まれたソースも受け入れます。 OfType 演算子を使用すると、ユーザーは次のような従来の .NET コレクションに標準のクエリ演算子を適用できます。

// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();

// "modern" can be used directly with query operators
IEnumerable<object> modern = classic.OfType<object>();

この例では、変数 modernクラシックと同じ値のシーケンスを生成します。 ただし、その型は、標準のクエリ演算子を含む最新の IEnumerable<T> コードと互換性があります。

OfType 演算子は、型に基づいてソースから値をフィルター処理できるため、新しい情報ソースにも役立ちます。 新しいシーケンスを生成する場合、 OfType は、型引数と互換性のない元のシーケンスのメンバーを省略するだけです。 異種配列から文字列を抽出するこの単純なプログラムについて考えてみましょう。

object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable<string> justStrings = vals.OfType<string>();

foreach ステートメントで justStrings 変数を列挙すると、"Hello" と "World" の 2 つの文字列のシーケンスが取得されます。

遅延クエリの評価

監視読者は、標準の Where 演算子が C# 2.0 で導入された yield コンストラクトを使用して実装されていることにご理解ください。 この実装手法は、値のシーケンスを返すすべての標準演算子で一般的です。 yield の使用には興味深い利点があります。これは、foreach ステートメントを使用するか、基になる GetEnumerator メソッドと MoveNext メソッドを手動で使用して、クエリが反復処理されるまで実際に評価されないことです。 この遅延評価により、クエリは IEnumerable<T> ベースの値として保持され、毎回複数回評価され、結果が異なる可能性があります。

多くのアプリケーションでは、これはまさに目的の動作です。 クエリ評価の結果をキャッシュするアプリケーションの場合は、 ToListToArray の 2 つの演算子が提供され、クエリの即時評価が強制され、クエリ評価の結果を含む List<T> または配列が返されます。

遅延クエリ評価のしくみを確認するには、配列に対して単純なクエリを実行するこのプログラムを検討してください。

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents a query
IEnumerable<string> ayes = names.Where(s => s[0] == 'A');

// evaluate the query
foreach (string item in ayes) 
  Console.WriteLine(item);

// modify the original information source
names[0] = "Bob";

// evaluate the query again, this time no "Allen"
foreach (string item in ayes) 
    Console.WriteLine(item);

クエリは、変数 ayes が反復処理されるたびに評価されます。 結果のキャッシュされたコピーが必要であることを示すには、 次のように ToList 演算子または ToArray 演算子をクエリに追加するだけです。

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();

// iterate over the cached query results
foreach (string item in ayes) 
    Console.WriteLine(item);

// modifying the original source has no effect on ayes
names[0] = "Bob";

// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);

ToArrayToList の両方で、クエリの即時評価が強制されます。 シングルトン値を返す標準クエリ演算子 ( FirstElementAtSumAverageAllAny など) についても同様です。

IQueryable<T> インターフェイス

通常、LINQ to SQLなどの式ツリーを使用してクエリ機能を実装するデータ ソースには、同じ遅延実行モデルが必要です。 これらのデータ ソースは、LINQ パターンで必要なすべてのクエリ演算子が式ツリーを使用して実装される IQueryable<T> インターフェイスを実装することでメリットを得ることができます。 各 IQueryable<T> には、式ツリーの形式で "クエリの実行に必要なコード" という表現があります。 遅延クエリ演算子はすべて、そのクエリ演算子の呼び出しの表現でその式ツリーを拡張する新しい IQueryable<T> を返します。 したがって、クエリを評価する時間になると、通常 は IQueryable<T> が列挙されるため、データ ソースはクエリ全体を表す式ツリーを 1 つのバッチで処理できます。 たとえば、クエリ演算子の多数の呼び出しによって取得される複雑なLINQ to SQL クエリでは、1 つの SQL クエリのみがデータベースに送信される可能性があります。

IQueryable<T> インターフェイスを実装することでこの遅延機能を再利用するデータ ソース実装者の利点は明らかです。 一方、クエリを記述するクライアントにとっては、リモート情報ソースに共通の型を持つことは大きな利点です。 さまざまなデータ ソースに対して使用できるポリモーフィック なクエリを記述できるだけでなく、ドメイン間で実行されるクエリを記述する可能性も広がっています。

複合値の初期化

ラムダ式と拡張メソッドは、値のシーケンスからメンバーをフィルター処理するだけのクエリに必要なすべてのものを提供します。 ほとんどのクエリ式では、これらのメンバーに対してプロジェクションも実行され、元のシーケンスのメンバーが、元の値と型と異なる可能性があるメンバーに効果的に変換されます。 これらの変換の記述をサポートするために、LINQ は オブジェクト初期化子 と呼ばれる新しいコンストラクトに依存して、構造化型の新しいインスタンスを作成します。 このドキュメントの残りの部分では、次の型が定義されていることを前提としています。

public class Person {
  string name;
  int age;
  bool canCode;

  public string Name {
    get { return name; } set { name = value; }
  }

  public int Age {
    get { return age; } set { age = value; }
  }

  public bool CanCode {
    get { return canCode; } set { canCode = value; }
  }
}

オブジェクト初期化子を使用すると、型のパブリック フィールドとプロパティに基づいて値を簡単に構築できます。 たとえば、 Person 型の新しい値を作成するには、次のステートメントを記述します。

Person value = new Person {
    Name = "Chris Smith", Age = 31, CanCode = false
};

意味的には、このステートメントは次の一連のステートメントと同じです。

Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.CanCode = false;

オブジェクト初期化子は、式のみが許可されるコンテキスト (ラムダ式や式ツリー内など) で新しい構造化値を構築できるため、言語統合クエリの重要な機能です。 たとえば、入力シーケンス内の値ごとに新しい Person 値を作成する次のクエリ式を考えてみましょう。

IEnumerable<Person> query = names.Select(s => new Person {
    Name = s, Age = 21, CanCode = s.Length == 5
});

オブジェクト初期化構文は、構造化値の配列を初期化する場合にも便利です。 たとえば、個々のオブジェクト初期化子を使用して初期化される次の配列変数を考えてみましょう。

static Person[] people = {
  new Person { Name="Allen Frances", Age=11, CanCode=false },
  new Person { Name="Burke Madison", Age=50, CanCode=true },
  new Person { Name="Connor Morgan", Age=59, CanCode=false },
  new Person { Name="David Charles", Age=33, CanCode=true },
  new Person { Name="Everett Frank", Age=16, CanCode=true },
};

構造化された値と型

LINQ プロジェクトでは、データ中心のプログラミング スタイルがサポートされています。一部の型は主に存在し、状態と動作の両方を持つ本格的なオブジェクトではなく、構造化された値に静的な "図形" を提供します。 この前提を論理的な結論に達すると、開発者が気にするのは値の構造であり、その図形の名前付き型の必要性はほとんど使用されません。 これにより、新しい構造体を初期化時に "インライン" に定義できる 匿名型 が導入されます。

C# では、匿名型の構文はオブジェクト初期化構文に似ていますが、型の名前は省略されます。 たとえば、次の 2 つのステートメントを考えてみましょう。

object v1 = new Person {
    Name = "Brian Smith", Age = 31, CanCode = false
};

object v2 = new { // note the omission of type name
    Name = "Brian Smith", Age = 31, CanCode = false
};

変数 v1v2 はどちらも、CLR 型に 3 つのパブリック プロパティ NameAgeCanCode を持つメモリ内オブジェクトを指します。 変数は、 v2匿名型のインスタンスを参照するという点で異なります。 CLR の用語では、匿名型は他のどの型と変もありません。 匿名型が特別なのは、プログラミング言語に意味のある名前が付かないということです。 匿名型のインスタンスを作成する唯一の方法は、上記の構文を使用することです。

変数が匿名型のインスタンスを参照し、静的な型指定の利点を引き続き得ることができるように、C# では暗黙的に型指定されたローカル変数が導入されます。var キーワード (keyword)は、ローカル変数宣言の型名の代わりに使用できます。 たとえば、次の有効な C# 3.0 プログラムを考えてみましょう。

var s = "Bob";
var n = 32;
var b = true;

var キーワード (keyword)は、変数の初期化に使用される式の静的型から変数の型を推論するようにコンパイラに指示します。 この例では、 snb の型はそれぞれ stringintbool です。 このプログラムは、次のプログラムと同じです。

string s = "Bob";
int    n = 32;
bool   b = true;

var キーワード (keyword)は、型に意味のある名前を持つ変数の利便性ですが、匿名型のインスタンスを参照する変数には必須です。

var value = new { 
  Name = " Brian Smith", Age = 31, CanCode = false
};

上記の例では、変数 は匿名型で、その定義は次の擬似 C# と等価です。

internal class ??? {
  string _Name;
  int    _Age;
  bool   _CanCode;

  public string Name { 
    get { return _Name; } set { _Name = value; }
  }

  public int Age{ 
    get { return _Age; } set { _Age = value; }
  }

  public bool CanCode { 
    get { return _CanCode; } set { _CanCode = value; }
  }

  public bool Equals(object obj) { ... }

  public bool GetHashCode() { ... }
}

匿名型はアセンブリ境界を越えて共有できません。ただし、コンパイラは、各アセンブリ内の特定のプロパティ名/型ペアのシーケンスに対して匿名型が最大 1 つ存在することを保証します。

匿名型は、既存の構造化値の 1 つ以上のメンバーを選択するためにプロジェクションでよく使用されるため、匿名型の初期化で別の値からフィールドまたはプロパティを参照できます。 これにより、新しい匿名型は、名前、型、および値がすべて参照先のプロパティまたはフィールドからコピーされるプロパティを取得します。

たとえば、次の例では、他の値のプロパティを組み合わせて新しい構造化値を作成します。

var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };

var couple = new {
    Husband = new { bob.Name, bob.Age },
    Wife = new { Name = jane.FirstName, jane.Age }
};

int    ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name;   // wn == "Jane"

上記のフィールドまたはプロパティの参照は、次のより明示的な形式を記述するための便利な構文です。

var couple = new {
    Husband = new { Name = bob.Name, Age = bob.Age },
    Wife = new { Name = jane.FirstName, Age = jane.Age }
};

どちらの場合も、2 つの変数は、bobjane から Name プロパティと Age プロパティの独自のコピーを取得します。

匿名型は、クエリの select 句で最もよく使用されます。 たとえば、次のクエリを考えてみましょう。

var query = people.Select(p => new { 
               p.Name, BadCoder = p.Age == 11
           });

foreach (var item in query) 
  Console.WriteLine("{0} is a {1} coder", 
                     item.Name,
                     item.BadCoder ? "bad" : "good");

この例では、処理コードに必要な図形と正確に一致する Person 型に対して新しいプロジェクションを作成できましたが、静的型の利点は引き続き得られました。

その他の標準クエリ演算子

前述の基本的なクエリ機能の上に、多数の演算子がシーケンスを操作し、クエリを作成する便利な方法を提供します。これにより、ユーザーは標準クエリ演算子の便利なフレームワーク内で結果を高度に制御できます。

並べ替えとグループ化

一般に、クエリを評価すると、基になる情報ソースに組み込まれている順序で生成される一連の値が生成されます。 これらの値が生成される順序を開発者が明示的に制御できるようにするために、標準クエリ演算子は順序を制御するために定義されます。 これらの演算子の最も基本的なは 、OrderBy 演算子です。

OrderBy 演算子と OrderByDescending 演算子は、任意の情報ソースに適用でき、ユーザーは結果の並べ替えに使用される値を生成するキー抽出関数を提供できます。 OrderByOrderByDescending では、キーに部分的な順序を適用するために使用できるオプションの比較関数も受け入れられます。 基本的な例を見てみましょう。

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

// unity sort
var s1 = names.OrderBy(s => s); 
var s2 = names.OrderByDescending(s => s);

// sort by length
var s3 = names.OrderBy(s => s.Length); 
var s4 = names.OrderByDescending(s => s.Length);

最初の 2 つのクエリ式では、文字列比較に基づいてソースのメンバーの並べ替えに基づく新しいシーケンスが生成されます。 2 番目の 2 つのクエリでは、各文字列の長さに基づいてソースのメンバーの並べ替えに基づく新しいシーケンスが生成されます。

複数の並べ替え条件を許可するために、OrderByOrderByDescending の両方で、ジェネリック IEnumerable<T ではなく OrderedSequence T> が返されます。>< 2 つの演算子は、 OrderedSequence<T> でのみ定義されます。つまり、追加の (下位) 並べ替え条件を適用する ThenByThenByDescending です。 ThenBy/ThenByDescending 自体は OrderedSequence<T を>返し、任意の数の ThenBy/ThenByDescending 演算子を 適用できます。

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);

この例で s1 によって参照されるクエリを評価すると、次の一連の値が生成されます。

"Burke", "David", "Frank", 
"Albert", "Connor", "George", "Harris", 
"Everett"

OrderBy 演算子ファミリに加えて、標準クエリ演算子には Reverse 演算子も含まれます。 Reverse は単にシーケンスを列挙し、同じ値を逆の順序で生成します。 OrderBy とは異なり、Reverse では実際の値自体が順序を決定することは考慮されません。代わりに、値が基になるソースによって生成される順序のみに依存します。

OrderBy 演算子は、一連の値に対して並べ替え順序を設定します。 標準クエリ演算子には GroupBy 演算子も含まれます。これにより、キー抽出関数に基づいて一連の値に対してパーティション分割が行われます。 GroupBy 演算子は、検出された個別のキー値ごとに 1 つずつ、IGrouping 値のシーケンスを返します。 IGrouping は、コンテンツの抽出に使用されたキーを追加で含む IEnumerable です。

public interface IGrouping<K, T> : IEnumerable<T> {
  public K Key { get; }
}

GroupBy の最も簡単なアプリケーションは次のようになります。

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

// group by length
var groups = names.GroupBy(s => s.Length);

foreach (IGrouping<int, string> group in groups) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (string value in group)
        Console.WriteLine("  {0}", value);
}    

このプログラムを実行すると、次の情報が出力されます。

Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank
Strings of length 7
  Everett

GroupBy を 選択すると、 グループ のメンバーを設定するために使用されるプロジェクション関数を提供できます。

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

// group by length
var groups = names.GroupBy(s => s.Length, s => s[0]);
foreach (IGrouping<int, char> group in groups) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (char value in group)
        Console.WriteLine("  {0}", value);
}  

このバリエーションでは、次の情報が出力されます。

Strings of length 6
  A
  C
  G
  H
Strings of length 5
  B
  D
  F
Strings of length 7
  E

メモ この例では、投影された型がソースと同じである必要はありません。 この場合、文字列のシーケンスから文字への整数のグループ化を作成しました。

集計演算子

一連の値を 1 つの値に集計するために、いくつかの標準クエリ演算子が定義されています。 最も一般的な集計演算子は Aggregate です。これは次のように定義されています。

public static U Aggregate<T, U>(this IEnumerable<T> source, 
                                U seed, Func<U, T, U> func) {
  U result = seed;

  foreach (T element in source) 
      result = func(result, element);

  return result;
}

Aggregate 演算子を使用すると、一連の値に対して計算を簡単に実行できます。 集計 は、基になるシーケンスのメンバーごとにラムダ式を 1 回呼び出すことで機能します。 Aggregate がラムダ式を呼び出すたびに、シーケンスのメンバーと集計値の両方が渡されます (初期値は Aggregate のシード パラメーターです)。 ラムダ式の結果は、前の集計値を置き換え、 Aggregate はラムダ式の最終的な結果を返します。

たとえば、このプログラムでは Aggregate を使用して、文字列の配列に対する合計文字数を累積します。

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

int count = names.Aggregate(0, (c, s) => c + s.Length);
// count == 46

標準クエリ演算子には、汎用 集計 演算子に加えて、汎用 の Count 演算子と、これらの一般的な集計操作を簡略化する 4 つの数値集計演算子 (MinMaxSumAverage) も含まれています。 数値集計関数は、シーケンスのメンバーを数値型にプロジェクトする関数が指定されている限り、数値型のシーケンス ( intdoubledecimal など) または任意の値のシーケンスに対して機能します。

このプログラムは、説明した Sum 演算子の両方の形式を示しています。

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

int total1 = numbers.Sum();            // total1 == 55
int total2 = names.Sum(s => s.Length); // total2 == 46

メモ 2 番目の Sum ステートメントは、 Aggregate を使用した前の例と同じです。

Select vs. SelectMany

Select 演算子では、変換関数がソース シーケンス内の値ごとに 1 つの値を生成する必要があります。 変換関数がそれ自体がシーケンスである値を返す場合、サブシーケンスを手動で走査するのはコンシューマー次第です。 たとえば、既存の String.Split メソッドを使用して文字列をトークンに分割する次のプログラムを考えてみましょう。

string[] text = { "Albert was here", 
                  "Burke slept late", 
                  "Connor is happy" };

var tokens = text.Select(s => s.Split(' '));

foreach (string[] line in tokens)
    foreach (string token in line)
        Console.Write("{0}.", token);

このプログラムを実行すると、次のテキストが出力されます。

Albert.was.here.Burke.slept.late.Connor.is.happy.

理想的には、クエリが合体した一連のトークンを返し、中間 文字列 [] を コンシューマーに公開しないようにするのが望ましいでしょう。 これを実現するには、Select 演算子の代わりに SelectMany 演算子を使用します。 SelectMany 演算子は、Select 演算子と同様に機能します。 変換関数は、 SelectMany 演算子によって展開されるシーケンスを返す必要がある点で異なります。 SelectMany を使用して書き換えられたプログラムを次に示します。

string[] text = { "Albert was here", 
                  "Burke slept late", 
                  "Connor is happy" };

var tokens = text.SelectMany(s => s.Split(' '));

foreach (string token in tokens)
    Console.Write("{0}.", token);

SelectMany を使用すると、各中間シーケンスが通常の評価の一部として展開されます。

SelectMany は、次の 2 つの情報ソースを組み合わせるのに最適です。

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.SelectMany(n => 
                     people.Where(p => n.Equals(p.Name))
                 );

SelectMany に渡されるラムダ式では、入れ子になったクエリは別のソースに適用されますが、スコープ n内には外部ソースから渡されたパラメーターが含まれます。 したがって 、人々。はn ごとに 1 回呼び出され、最終的な出力では SelectMany によって結果のシーケンスがフラット化されます。 結果は、 名前 配列に名前が表示されるすべてのユーザーのシーケンスです。

結合演算子

オブジェクト指向プログラムでは、相互に関連するオブジェクトは、通常、ナビゲートしやすいオブジェクト参照とリンクされます。 通常、外部の情報ソースでは同じことが当てはまらないので、データ エントリは、多くの場合、指し示されているエンティティを一意に識別できる ID やその他のデータを使用して、相互にシンボリックに "ポイント" する以外の選択肢はありません。 結合の概念は、シーケンスの要素を、別のシーケンスから "一致する" 要素と一緒に取り込む操作を指します。

SelectMany を使用した前の例では、実際にはそれが正確に実行され、それらの文字列である名前を持つユーザーと文字列が一致します。 ただし、この特定の目的のために、 SelectMany アプローチはあまり効率的ではありません。名前の要素ごとに 、人 のすべての要素をループ処理 します。 このシナリオのすべての情報 (2 つの情報ソースと、それらが一致する "キー") を 1 つのメソッド呼び出しにまとめることで、 Join 演算子ははるかに優れたジョブを実行できます。

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.Join(people, n => n, p => p.Name, (n,p) => p);

これは少し一口ですが、部分がどのように組み合わされているかを確認してください。 Join メソッドは、"外部" データ ソースの名前で呼び出 されます。 最初の引数は、"内部" データ ソースである people です。 2 番目と 3 番目の引数は、外部ソースと内部ソースの要素からそれぞれキーを抽出するラムダ式です。 これらのキーは、 Join メソッドが要素を照合するために使用するものです。 ここでは、名前自体がユーザーの Name プロパティと一致するようにします。 最終的なラムダ式は、結果のシーケンスの要素を生成します。これは、一致する要素 np の各ペアで呼び出され、結果の整形に使用されます。 この場合、 n を破棄して p を返します。 最終的な結果は、名前が名前の一覧にあるユーザーPerson 要素の一覧です

Join のより強力ないとこは GroupJoin 演算子です。 GroupJoin は、結果整形ラムダ式の使用方法で Join とは異なります。外部要素と内部要素の個々のペアで呼び出されるのではなく、外側の要素ごとに 1 回だけ呼び出され、その外側の要素と一致するすべての内部要素のシーケンスが含まれます。 その具体的な作り方:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.GroupJoin(people, n => n, p => p.Name,                   
                 (n, matching) => 
                      new { Name = n, Count = matching.Count() }
);

この呼び出しは、開始した名前のシーケンスを、その名前を持つユーザーの数と組み合わせて生成します。 したがって、 GroupJoin 演算子を使用すると、外側の要素の "一致のセット" 全体に基づいて結果を基にできます。

クエリ構文

C# の既存 の foreach ステートメントは、.NET Frameworks IEnumerable/IEnumerator メソッドを反復処理するための宣言構文を提供します。 foreach ステートメントは厳密に省略可能ですが、非常に便利で一般的な言語メカニズムであることが証明されています。

この優先順位に基づいて、クエリ式を使用すると、最も一般的なクエリ演算子 (WhereJoinGroupJoinSelectSelectManyGroupBy、OrderByThenByOrderByDescendingThenByDescending、ThenByDescendingCast) の宣言構文を使用してクエリが簡略化されます。

まず、この論文で始めた簡単なクエリを見てみましょう。

IEnumerable<string> query = names 
                            .Where(s => s.Length == 5) 
                            .OrderBy(s => s)
                            .Select(s => s.ToUpper());

クエリ式を使用すると、次のような正確なステートメントを書き換えることができます。

IEnumerable<string> query = from s in names 
                            where s.Length == 5
                            orderby s
                            select s.ToUpper();

C# の foreach ステートメントと同様に、クエリ式の方がコンパクトで読みやすくなりますが、完全に省略可能です。 クエリ式として記述できるすべての式には、ドット表記を使用した対応する (より詳細な) 構文があります。

まず、クエリ式の基本的な構造を見てみましょう。 C# のすべての構文クエリ式は from 句で始まり、 select 句または group 句で終わります。 最初の from 句の後には、0 個以上の from 句、 let 句、 where 句、 join 句、 orderby 句を指定できます。 各 from 句は、シーケンスに対して範囲変数を導入するジェネレーターです。各 let 句は、式の結果に名前を付けます。各 where 句は、結果から項目を除外するフィルターです。 すべての 結合 句は、新しいデータ ソースを前の句の結果と関連付けます。 orderby 句は、結果の順序を指定します。

query-expression ::= from-clause query-body

query-body ::= 

      query-body-clause* final-query-clause query-continuation?

query-body-clause ::=
 (from-clause 
      | join-clause 
      | let-clause 
      | where-clause 
      | orderby-clause)

from-clause ::=from itemName in srcExpr

join-clause ::=join itemName in srcExpr on keyExpr equals keyExpr 
       (into itemName)?

let-clause ::=let itemName = selExpr

where-clause ::= where predExpr

orderby-clause ::= orderby (keyExpr (ascending | descending)?)*

final-query-clause ::=
 (select-clause | groupby-clause)

select-clause ::= select selExpr

groupby-clause ::= group selExpr by keyExprquery-continuation ::= intoitemName query-body

たとえば、次の 2 つのクエリ式を考えてみましょう。

var query1 = from p in people
             where p.Age > 20
             orderby p.Age descending, p.Name
             select new { 
                 p.Name, Senior = p.Age > 30, p.CanCode
             };

var query2 = from p in people
             where p.Age > 20
             orderby p.Age descending, p.Name
             group new { 
                p.Name, Senior = p.Age > 30, p.CanCode
             } by p.CanCode;

コンパイラは、これらのクエリ式を、次の明示的なドット表記を使用して記述されたかのように扱います。

var query1 = people.Where(p => p.Age > 20)
                   .OrderByDescending(p => p.Age)
                   .ThenBy(p => p.Name)
                   .Select(p => new { 
                       p.Name, 
                       Senior = p.Age > 30, 
                       p.CanCode
                   });

var query2 = people.Where(p => p.Age > 20)
                   .OrderByDescending(p => p.Age)
                   .ThenBy(p => p.Name)
                   .GroupBy(p => p.CanCode, 
                            p => new {
                                   p.Name, 
                                   Senior = p.Age > 30, 
                                   p.CanCode
                   });

クエリ式は、特定の名前を持つメソッドの呼び出しに機械的に変換されます。 したがって、選択される正確なクエリ演算子の 実装 は、クエリ対象の変数の型とスコープ内の拡張メソッドの両方によって異なります。

これまでに示したクエリ式では、ジェネレーターが 1 つだけ使用されています。 複数のジェネレーターを使用する場合、後続の各ジェネレーターは、先行するジェネレーターのコンテキストで評価されます。 たとえば、クエリに対するこのわずかな変更を考えてみましょう。

var query = from s1 in names 
            where s1.Length == 5
            from s2 in names 
            where s1 == s2
            select s1 + " " + s2;

この入力配列に対して を実行する場合:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

次の結果が得られます。

Burke Burke
Frank Frank
David David

上記のクエリ式は、次のドット表記式に展開されます。

var query = names.Where(s1 => s1.Length == 5)
                 .SelectMany(s1 => names, (s1,s2) => new {s1,s2})
                 .Where($1 => $1.s1 == $1.s2) 
                 .Select($1 => $1.s1 + " " + $1.s2);

メモ このバージョンの SelectMany は、外部シーケンスと内部シーケンスの要素に基づいて結果を生成するために使用される追加のラムダ式を受け取ります。 このラムダ式では、2 つの範囲変数が匿名型で収集されます。 コンパイラは、後続のラムダ式でその匿名型を示すために変数名 $1 を作成します。

特殊な種類のジェネレーターは join 句であり、指定されたキーに従って、前の句の要素と一致する別のソースの要素が導入されます。 join 句は一致する要素を 1 つずつ生成できますが、into 句で指定した場合、一致する要素はグループとして指定されます。

var query = from n in names
            join p in people on n equals p.Name into matching
            select new { Name = n, Count = matching.Count() };

当然のことながら、このクエリは、前に見たクエリに直接拡張されます。

var query = names.GroupJoin(people, n => n, p => p.Name,                   
           (n, matching) => 
                      new { Name = n, Count = matching.Count() }
);

多くの場合、1 つのクエリの結果を後続のクエリのジェネレーターとして扱うと便利です。 これをサポートするために、クエリ式では、 を使用して、select 句または group 句の後に新しいクエリ式を結合するために、キーワード (keyword)します。 これは クエリ継続と呼ばれます。

キーワード (keyword)への は、group by 句の結果を後処理する場合に特に便利です。 たとえば、次のプログラムを考えてみましょう。

var query = from item in names
            orderby item
            group item by item.Length into lengthGroups
            orderby lengthGroups.Key descending
            select lengthGroups;

foreach (var group in query) { 
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (var val in group)
        Console.WriteLine("  {0}", val);
}

このプログラムでは、次の出力が行われます。

Strings of length 7
  Everett
Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank

このセクションでは、C# がクエリ式を実装する方法について説明しました。 他の言語では、明示的な構文を使用して追加のクエリ演算子をサポートするか、クエリ式をまったく持たないようにすることもできます。

クエリ構文は、決して標準のクエリ演算子にハードワイヤードされていないことに注意することが重要です。 これは純粋に構文的な機能であり、適切な名前とシグネチャを使用して基になるメソッドを実装することで 、クエリ パターン を満たすあらゆるものに適用されます。 前述の標準クエリ演算子では、拡張メソッドを使用して IEnumerable<T> インターフェイスを拡張します。 開発者は、必要なメソッドを直接実装するか、拡張メソッドとして追加することによって、クエリ パターンに準拠していることを確認する限り、任意の型でクエリ構文を利用できます。

この拡張性は、LINQ 対応 API (つまり、SQL ベースのデータ アクセス用の LINQ パターンを実装する LINQ to SQL) と、XML データに対する LINQ クエリを可能にするLINQ to XMLの 2 つの LINQ 対応 API のプロビジョニングによって、LINQ プロジェクト自体で悪用されます。 これらの両方について、次のセクションで説明します。

LINQ to SQL: SQL 統合

.NET Language-Integrated クエリを使用すると、ローカル プログラミング言語の構文やコンパイル時環境を離れることなく、リレーショナル データ ストアに対してクエリを実行できます。 この機能 (コード名LINQ to SQL) では、SQL スキーマ情報を CLR メタデータに統合できます。 この統合により、SQL テーブルとビューの定義が、任意の言語からアクセスできる CLR 型にコンパイルされます。

LINQ to SQLでは、外部 SQL データに対応する CLR の型とプロパティを示す [Table][Column] という 2 つの主要な属性を定義します。 [Table] 属性をクラスに適用し、CLR 型を名前付き SQL テーブルまたはビューに関連付けることができます。 [Column] 属性は、任意のフィールドまたはプロパティに適用でき、メンバーを名前付き SQL 列に関連付けます。 どちらの属性もパラメーター化され、SQL 固有のメタデータを保持できます。 たとえば、次の単純な SQL スキーマ定義を考えてみましょう。

create table People (
    Name nvarchar(32) primary key not null, 
    Age int not null, 
    CanCode bit not null
)

create table Orders (
    OrderID nvarchar(32) primary key not null, 
    Customer nvarchar(32) not null, 
    Amount int
)

同等の CLR は次のようになります。

[Table(Name="People")]
public class Person {
  [Column(DbType="nvarchar(32) not null", Id=true)]
  public string Name; 

  [Column]
  public int Age;

  [Column]
  public bool CanCode;
}

[Table(Name="Orders")]
public class Order {
  [Column(DbType="nvarchar(32) not null", Id=true)]
  public string OrderID; 

  [Column(DbType="nvarchar(32) not null")]        
  public string Customer; 

  [Column]
  public int? Amount; 
}

メモこの例では、NULL 許容列は CLR 内の null 許容型にマップされます (null 許容型は、.NET Frameworkのバージョン 2.0 で最初に出現しました)、CLR 型 (nvarcharchartext など) と 1 対 1 の対応を持たない SQL 型の場合、元の SQL 型は CLR メタデータに保持されます。

リレーショナル ストアに対してクエリを発行するために、LINQ パターンのLINQ to SQL実装では、クエリを式ツリー形式から SQL 式に変換し、リモート評価に適した DbCommand オブジェクト ADO.NET します。 たとえば、次の単純なクエリを考えてみましょう。

// establish a query context over ADO.NET sql connection
DataContext context = new DataContext(
     "Initial Catalog=petdb;Integrated Security=sspi");

// grab variables that represent the remote tables that 
// correspond to the Person and Order CLR types
Table<Person> custs = context.GetTable<Person>();
Table<Order> orders   = context.GetTable<Order>();

// build the query
var query = from c in custs
            from o in orders
            where o.Customer == c.Name
            select new { 
                       c.Name, 
                       o.OrderID,
                       o.Amount,
                       c.Age
            }; 

// execute the query
foreach (var item in query) 
    Console.WriteLine("{0} {1} {2} {3}", 
                      item.Name, item.OrderID, 
                      item.Amount, item.Age);

DataContext 型は、標準のクエリ演算子を SQL に変換する軽量のトランスレーターを提供します。 DataContext は、ストアへのアクセスに既存の ADO.NET IDbConnection を使用し、確立された ADO.NET 接続オブジェクトまたは接続文字列を使用して初期化できます。

GetTable メソッドは、リモート テーブルまたはビューを表すためにクエリ式で使用できる IEnumerable 互換変数を提供します。 GetTable を呼び出してもデータベースとの対話は発生せず、クエリ式を使用してリモート テーブルまたはビューと対話する可能性を表します。 上記の例では、プログラムがクエリ式を反復処理するまで、クエリはストアに送信されません。この場合は、C# で foreach ステートメントを使用します。 プログラムが最初にクエリを反復処理すると、 DataContext 機構は式ツリーをストアに送信される次の SQL ステートメントに変換します。

SELECT [t0].[Age], [t1].[Amount], 
       [t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]

ローカル プログラミング言語にクエリ機能を直接組み込むことで、開発者はリレーションシップを CLR 型に静的にベイクすることなく、リレーショナル モデルを最大限に活用できます。 前述の包括的なオブジェクト/リレーショナル マッピングでは、その機能を必要とするユーザーに対して、このコア クエリ機能を利用することもできます。 LINQ to SQLは、開発者がオブジェクト間のリレーションシップを定義および移動できるオブジェクト リレーショナル マッピング機能を提供します。 マッピングを使用して Customer クラスのプロパティとして Orders を参照できるため、2 つを結び付けるために明示的な結合は必要ありません。 外部マッピング ファイルを使用すると、マッピングをオブジェクト モデルから分離して、より豊富なマッピング機能を実現できます。

LINQ to XML: XML 統合

.NET Language-Integrated QUERY for XML (LINQ to XML) を使用すると、標準のクエリ演算子と、子孫、先祖、兄弟を介して XPath のようなナビゲーションを提供するツリー固有の演算子を使用して XML データを照会できます。 既存の System.Xml リーダー/ライター インフラストラクチャと統合され、W3C DOM よりも使いやすい XML の効率的なメモリ内表現が提供されます。 XML とクエリを統合する作業のほとんどは、 XNameXElementXAttribute の 3 種類があります。

XName は、要素名と属性名の両方として使用される名前空間修飾識別子 (QNames) を扱うための使いやすい方法を提供します。 XName は識別子の効率的なアトミック化を透過的に処理し、QName が必要な場所でシンボルまたはプレーン文字列を使用できるようにします。

XML 要素と属性は、それぞれ XElementXAttribute を 使用して表されます。 XElementXAttribute では 、通常の構築構文がサポートされており、開発者は自然な構文を使用して XML 式を記述できます。

var e = new XElement("Person", 
                     new XAttribute("CanCode", true),
                     new XElement("Name", "Loren David"),
                     new XElement("Age", 31));

var s = e.ToString();

これは、次の XML に対応します。

<Person CanCode="true">
  <Name>Loren David</Name> 
  <Age>31</Age> 
</Person>

XML 式を作成するために DOM ベースのファクトリ パターンが必要なく、 ToString 実装によってテキスト XML が生成されたことに注意してください。 XML 要素は、既存の XmlReader または文字列リテラルから構築することもできます。

var e2 = XElement.Load(xmlReader);
var e1 = XElement.Parse(
@"<Person CanCode='true'>
  <Name>Loren David</Name>
  <Age>31</Age>
</Person>");

XElement では、既存の XmlWriter 型を使用した XML の出力もサポートされています。

XElement にはクエリ演算子が含まれており、開発者は select 句の本体で XElements を構築することで、XML 以外の情報に対してクエリを記述し、XML 結果を生成できます。

var query = from p in people 
            where p.CanCode
            select new XElement("Person", 
                                  new XAttribute("Age", p.Age),
                                  p.Name);

このクエリは、一連の XElements を返します。 この種のクエリの結果から XElements を構築できるようにするために、 XElement コンストラクターを使用すると、要素のシーケンスを引数として直接渡すことができます。

var x = new XElement("People",
                  from p in people 
                  where p.CanCode
                  select 
                    new XElement("Person", 
                                   new XAttribute("Age", p.Age),
                                   p.Name));

この XML 式は、次の XML になります。

<People>
  <Person Age="11">Allen Frances</Person> 
  <Person Age="59">Connor Morgan</Person> 
</People>

上記のステートメントには、Visual Basic への直接翻訳があります。 ただし、Visual Basic 9.0 では XML リテラルの使用もサポートされています。これにより、Visual Basic から直接宣言型 XML 構文を使用してクエリ式を表現できます。 前の例は、Visual Basic ステートメントを使用して構築できます。

 Dim x = _
        <People>
             <%= From p In people __
                 Where p.CanCode _

                 Select <Person Age=<%= p.Age %>>p.Name</Person> _
             %>
        </People>

これまでの例では、言語統合クエリを使用して新しい XML 値を 構築 する方法を示しています。 XElement 型と XAttribute 型を使用すると、XML 構造体からの情報の抽出も簡略化されます。 XElement には、クエリ式を従来の XPath 軸に適用できるアクセサー メソッドが用意されています。 たとえば、次のクエリでは、上記の XElement から名前のみを抽出します。

IEnumerable<string> justNames =
    from e in x.Descendants("Person")
    select e.Value;

//justNames = ["Allen Frances", "Connor Morgan"]

XML から構造化値を抽出するには、select 句でオブジェクト初期化子式を使用するだけです。

IEnumerable<Person> persons =
    from e in x.Descendants("Person")
    select new Person { 
        Name = e.Value,
        Age = (int)e.Attribute("Age") 
    };

XAttributeXElement の両方で、テキスト値をプリミティブ型として抽出するための明示的な変換がサポートされていることに注意してください。 不足しているデータに対処するために、null 許容型にキャストするだけです。

IEnumerable<Person> persons =
    from e in x.Descendants("Person")
    select new Person { 
        Name = e.Value,
        Age = (int?)e.Attribute("Age") ?? 21
    };

この場合、Age 属性が見つからない場合は、既定値の 21 を使用します。

Visual Basic 9.0 では、XElementElementsAttribute、および Descendants アクセサー メソッドを直接言語でサポートしているため、XML 軸プロパティと呼ばれるよりコンパクトで直接的な構文を使用して XML ベースのデータにアクセスできます。 この機能を使用して、前述の C# ステートメントを次のように記述できます。

Dim persons = _
      From e In x...<Person> _   
      Select new Person { _
          .Name = e.Value, _
          .Age = IIF(e.@Age, 21) _
      } 

Visual Basic では、x...<Person> は、Person という名前の x の Descendants コレクション内のすべてのアイテムを取得します。式 e.@AgeAge. The Value プロパティという名前のすべての XAttributes を検索し、コレクション内の最初の属性を取得し、その属性の Value プロパティを呼び出します。

まとめ

.NET Language-Integrated クエリは、CLR とそれを対象とする言語にクエリ機能を追加します。 クエリ機能はラムダ式と式ツリーに基づいて構築されており、述語、プロジェクション、およびキー抽出式を不透明な実行可能コードとして使用したり、ダウンストリームの処理や変換に適した透過的なメモリ内データとして使用したりできます。 LINQ プロジェクトによって定義される標準クエリ演算子は、IEnumerable<T> ベースの情報ソースに対して機能し、ADO.NET (LINQ to SQL) とSystem.Xml (LINQ to XML) と統合され、リレーショナルデータと XML データが言語統合クエリの利点を得ることができます。

簡単に言うと、標準クエリ演算子

演算子 説明
Where 述語関数に基づく制限演算子
Select/SelectMany セレクター関数に基づく射影演算子
Take/Skip/TakeWhile/SkipWhile 位置または述語関数に基づくパーティション分割演算子
Join/GroupJoin キー セレクター関数に基づく結合演算子
Concat Concatenation 操作
OrderBy/ThenBy/OrderByDescending/ThenByDescending オプションのキー セレクター関数と比較関数に基づいて昇順または降順で並べ替える演算子の並べ替え
Reverse シーケンスの順序を逆にする並べ替え演算子
GroupBy オプションのキー セレクター関数と比較子関数に基づくグループ化演算子
Distinct 重複を削除する演算子を設定する
和集合/交差 集合和集合または交差を返すセット演算子
除く セットの差を返す Set 演算子
AsEnumerable IEnumerable<T> への変換演算子
ToArray/ToList 配列またはリスト<T> への変換演算子
ToDictionary/ToLookup キー セレクター関数に基づく辞書<K、T>、Lookup<K、T> (マルチディクショナリ) への変換演算子
OfType/Cast フィルター処理または型引数への変換に基づく IEnumerable<T> への変換演算子
SequenceEqual ペアごとの要素の等価性をチェックする等値演算子
First/FirstOrDefault/Last/LastOrDefault/Single/SingleOrDefault 省略可能な述語関数に基づいて初期/最終/のみ要素を返す要素演算子
ElementAt/ElementAtOrDefault 位置に基づいて要素を返す要素演算子
DefaultIfEmpty 空のシーケンスを既定値のシングルトン シーケンスに置き換える要素演算子
Range 範囲内の数値を返す生成演算子
Repeat 指定された値の複数の出現箇所を返す生成演算子
Empty 空のシーケンスを返す生成演算子
Any/All 述語関数の存在またはユニバーサル満足度の量指定子チェック
Contains 特定の要素の存在を確認する量指定子
Count/LongCount 省略可能な述語関数に基づいて要素をカウントする集計演算子
Sum/Min/Max/Average オプションのセレクター関数に基づく集計演算子
Aggregate 累積関数とオプションのシードに基づいて複数の値を累積する集計演算子