LINQ クエリ

Last Updated 2011/09/21


LINQ クエリとは、LINQ によるクエリ操作と考えてください。LINQ クエリの基本的な手順は次の 3 段階に分かれます。

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

データソースとは、LINQ クエリの操作対象となるもので、.NET Framework における LINQ クエリの操作対象は IEnumerable または IEnumerable<T> インターフェースを継承するオブジェクトです。

LINQ クエリは、標準クエリ演算子を使ってクエリ式を作成します。

LINQ クエリの結果は、標準クエリ演算子で説明するとおり、通常は遅延実行されます。

標準クエリ演算子の機能は、次の 5 つに分類できます。

フィルター処理指定の条件を満足する要素だけを抜き出す操作です。
ソート指定のキーに基づいて要素を昇順または降順で並び替えます。
グループ化指定のキーに基づいて要素をグループ化します。
結合異なるコレクション間で要素どうしを関連付けることです。
選択(投影)実行結果の型を指定します。

C# クエリキーワード

C# コードによるクエリ式を構築するときに便利なようにキーワードが設定されています。これらはコードをコンパイルするときに、Enumerable<T> クラスまたはその派生クラスで実装されたメソッドに変換されます。

処理内容キーワード関連するキーワード
フィルター処理where
ソートorderbyascending
descending
グループ化groupby
into
結合joinequals
in
into
on
選択(投影)select
データソースの指定fromin
変数への代入let

これらのキーワードの使い方は、NETClassを参照してください。


メソッド構文

LINQ クエリは、前の項で説明したクエリキーワードを使う方法がもっとも便利ですが、その実装メソッドを直接呼び出すことも可能です。こういう手順を総称して、「メソッド構文」と呼びます。また、クエリキーワードとメソッド構文とを混在させることも可能です。

次のコードはクエリ式による方法です。

private void button1_Click(object sender, EventArgs e)
{
  string[] data = { "北海道", "青森", "新潟", "愛知", "和歌山", "石川", "兵庫", "広島", "鹿児島" };

  // 3 文字のデータだけを選択し、昇順でソートするクエリ
  // ここでは where、orderby、select クエリ演算子を使う
  var query = from s in data
              where s.Length == 3
              orderby s
              select s;

  foreach (string s in query)
  {
    textBox1.Text += s + "\r\n";
  }
}

実行結果は以下のとおり。

鹿児島
北海道
和歌山

次はメソッド構文による方法です。実行結果は当然ですが、前の例と同じです。

private void button2_Click(object sender, EventArgs e)
{
  string[] data = { "北海道", "青森", "新潟", "愛知", "和歌山", "石川", "兵庫", "広島", "鹿児島" };

  // 3 文字のデータだけを選択し、昇順でソートするメソッドベースのクエリ
  // ここでは where、orderby、select メソッドを使い、引数にはラムダ式を使う
  var query = data
              .Where(s => s.Length == 3)
              .OrderBy(s => s)
              .Select(s => s);

  foreach (string s in query)
  {
    textBox1.Text += s + "\r\n";
  }
}

クエリキーワードだけで構成するほうが直感的であることは一目瞭然ですね。

いずれにしろ、クエリキーワードだけですべての処理が間に合うわけではありません。適宜使い分けることが肝要です。どんなメソッドがあるかについては、標準クエリ演算子のページをご覧ください。


null 値の処理

PLINQ のページで紹介する使用例を見てもらえば分かりますが、操作対象のテキストファイルの文字コードを判定するとき、判定に失敗すると null を返す場合があります。文字コードが不明であればそれ以上の処理を続けることはできませんからそのファイルは操作対象から除外しなければなりません。

以下は部分的なコードですが、null 値を処理する例です。where キーワードを使って null のデータを除外しています。

  var query1 = from c in categories
               where c != null
               join p in products on c.ID equals (p == null ? null : p.CategoryID)
               select new { Category = c.Name, Name = p.Name };

例外の処理

クエリ式内で任意のメソッドを呼び出すことができますが、メソッドを呼び出したときに例外が発生した場合の処理方法について説明します。

クエリ式を作成する過程で例外が発生したときに、クエリ式の処理から抜け出る例

private void button1_Click(object sender, RoutedEventArgs e)
{
  IEnumerable<int> dataSource;

  try
  {
    dataSource = this.GetData();
  }
  catch (InvalidOperationException)
  {
    MessageBox.Show("Invalid operation");
    goto Exit;
  }

  // 例外が発生しなかったとき(もちろん、今回はありえないが)
  var query = from i in dataSource select i * i;

  foreach (var i in query)
  {
    textBox.Text += String.Format("{0}\r\n", i);
  }

// 例外発生時、ここにジャンプしてくる
Exit:
  MessageBox.Show("Exit");
}	

// クエリ式を作成するメソッド
// ただし、今回は例外を発生させるだけ
private IEnumerable<int> GetData()
{
  throw new InvalidOperationException();
}

クエリ実行時に強制的に例外を発生させる例

場合によっては、クエリ式の実行を即座に停止することがクエリ内で発生する例外に対する最良の対処法になることがあります。

private void button1_Click(object sender, RoutedEventArgs e)
{
  string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" };

  var exceptionDemoQuery = from file in files
                           let n = SomeMethodThatMightThrow(file)
                           select n;

  // クエリを実行したときに例外を発生するので、ここで例外を捕捉する
  try
  {
    foreach (var item in exceptionDemoQuery)
    {
      textBox.Text += String.Format("Processing {0}\r\n", item);
    }
  }
  catch (InvalidOperationException ex)
  {
    textBox.Text += ex.Message;
  }
  finally
  {
    // 必要なら
  }
}	

private string SomeMethodThatMightThrow(string s)
{
  // 5 文字目が "C" のとき、例外を発生させる
  if (s[4] == 'C')
    throw new InvalidOperationException();

  return @"C:\newFolder\" + s;
}

実行結果:

Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
オブジェクトの現在の状態に問題があるため、操作は有効ではありません。

−以上−