LINQのクエリ構文
クエリ構文とは
LINQの項では以下のようなコードが登場しました。
List<int> nums = new List<int>()
{
1, 2, 5, 9, 11, 14, 18, 21
};
//偶数を取り出す
IEnumerable<int> evens = nums.Where(n => n % 2 == 0);
これは今まで説明してきたC#の文法通りのコードです。
(ラムダ式の部分は慣れるまでは違和感があるかもしれませんが)
LINQのメソッドはクエリ構文(クエリ式)という記法もあります。
上のコードをクエリ構文で書き直すと以下のようになります。
List<int> nums = new List<int>()
{
1, 2, 5, 9, 11, 14, 18, 21
};
//偶数を取り出す
IEnumerable<int> evens =
from n in nums
where n % 2 == 0
select n;
今までやってきたC#の文法とは全く異なりますが、このような記法がクエリ構文です。
記法は異なりますが、やっていること自体はどちらも全く同じです。
クエリ構文はメソッド構文の糖衣構文(シンタックスシュガー)と呼ばれるもので、同じ処理の呼び出しを別の形式で記述できるようにしたものです。
コンパイル時に同じコードに置き換えられるので、単に同じ結果になるというだけでなく全く同じ動作になります。
同じ処理でもクエリ構文のほうが簡潔に記述できる場合もあります。
ただし、メソッド構文のすべてがクエリ構文に置き換え可能なわけではなく、クエリ構文に対応しないメソッドもあります。
その場合はメソッド構文と組み合わせます。
(後述)
このページのサンプルコードでは変数の宣言時にデータ型を明示していますが、型推論(var)を使用したほうが良いです。
クエリ構文の文法
まずは簡単なクエリ構文の例を示します。
以下の二つの式はどちらも同じ動作となります。
List<int> nums = new List<int>()
{
1, 2, 5, 9, 11, 14, 18, 21
};
//クエリ構文
//「from n in nums」は
//「numsから要素をひとつ取り出しnに格納」
//という処理
IEnumerable<int> query =
from n in nums
where n % 2 == 0
select n;
//メソッド構文
//「nums.○○(n =>」は
//「numsから要素をひとつ取り出しnに格納」
//という処理
IEnumerable<int> method =
nums.Where(n => n % 2 == 0);
from句
クエリ構文は必ずfrom ... inというキーワードから始まります。
クエリ構文のキーワードは句と呼ばれます。
from句はシーケンス(データソース)から要素をひとつ取り出し、変数に格納します。
from句に直接対応するメソッド構文は存在しませんが、あえて言うならラムダ式による値の取り出し部分に該当します。
ラムダ式の場合とは「取り出し元」と「格納先」の出現順序が逆になっているので注意してください。
from句で取り出した要素「n」はfrom句以降で使用します。
これを範囲変数といいます。
範囲変数名は自由です。
これはforeach文の書き方とほぼ同じと考えると良いです。
List<int> nums = new List<int>()
{
};
foreach(var n in nums)
{
}
select句
とりあえず途中は飛ばして先にselect句を説明します。
select句はメソッド構文のSelectメソッドに該当します。
select句でクエリ式の最終的な出力形式を決定します。
クエリ式はselect句かgroup句のどちらかが出現すると終了します。
(group句については次ページで解説)
上述のサンプルコードのメソッド構文にはselect句に対応するメソッドは登場しませんが、あえて書くと以下のようになります。
IEnumerable<int> evens =
nums.Where(n => n % 2 == 0)
.Select(n => n);
今回は何もしないので単に「select n」とだけ記述していますが、Selectメソッドと同じなのでデータを加工して出力することも可能です。
//該当要素を二倍する
IEnumerable<int> evens =
from n in nums
where n % 2 == 0
select n * 2;
4 28 36
where句
from句とselect句(またはgroup句)は必須ですが、その間の処理は自由に記述できます。
今回はwhere句を利用して、偶数のみの要素を取り出しています。
これはWhereメソッドに該当します。
キーワード名の衝突
クエリ構文では専用のキーワードを使用するのですが、これが変数名等と被ってしまうことがあります。
そのような場合は名前が衝突する変数等の前に@(アットマーク)を記述することで回避できます。
static void Method(string from)
{
//「from」はクエリキーワード
//「@from」は仮引数
var query =
from f in @from
select f;
}
クラスなどのメンバーにクエリキーワードと衝突する名前がある場合は以下のようにします。
static void Method(Test test)
{
//「from」はクエリキーワード
//「test.@from」はTestクラスのメンバー
var query =
from f in test.@from
select f;
}
クエリ構文とメソッド呼び出し
クエリ構文はメソッド構文のシンタックスシュガーですが、すべてのメソッドに対応するクエリキーワードがあるわけではありません。
対応するキーワードがない場合はメソッド構文と組み合わせて使用します。
クエリ式内でLINQ以外のメソッドを呼び出すこともできます。
例えばLINQのAverageメソッドは対応するクエリキーワードが存在しないのでクエリ式内でメソッド構文を使用して呼び出します。
List<int> nums = new List<int>()
{
1, 2, 5, 9, 11, 14, 18, 21
};
//シーケンスの平均より大きい要素を取り出し
IEnumerable<int> query =
from n in nums
let ave = nums.Average()
where n > ave
select n;
foreach (var x in query)
Console.Write("{0} ", x);
11 14 18 21