Blog

ブログ

LINQ の基本を理解しよう

LINQ とは

LINQ とは コレクション・XML・SQLなど様々なデータソースに対する検索・操作を行うもので、System.Linq を参照することにより提供される拡張メソッド群(標準クエリ演算子)ことを指します。
本稿では特に利用する機会の多いコレクション(IEnumeable)に対する LINQ to Object の解説を行います。

メソッド構文とクエリ構文

LINQ は拡張メソッドで提供されることから、当然コレクションのメソッドとして利用する(メソッド構文)ことが可能です。
また一部の標準クエリ演算子に対しては言語仕様としてキーワードが割り当てられており、それを用いることでSQLライクに記述する(クエリ構文)ことも可能です。

例)メソッド構文とクエリ構文
 ※本稿のサンプルコードではメソッドの引数としてラムダ式を使用します。

var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// いずれも結果で得られるコレクションの型はIEnumerable<int>となり、
// 列挙することで { 1, 2, 3, 4, 5 } の結果(要素)を得ることができます。

// メソッド構文
// 処理しない値を取り出す場合はSelectを書略可能
var methodResult = source.Where(x => x <= 5);

// クエリ構文
// クエリ構文ではselectを省略不可
var queryResult =
    from x in source
    where x <= 5
    select x;

またクエリ構文で記述したコードはコンパイルを通して標準クエリ演算子に変換されますので、最終的に得られる結果は同一になります。
前述のコードから得られる実行形式のファイルを逆コンパイルした結果は下記となります。

// Program
using System.Collections.Generic;
using System.Linq;

private static void <Main>$(string[] args)
{
	int[] source = new int[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	IEnumerable<int> methodResult = source.Where((int x) => x <= 5);
	IEnumerable<int> queryResult = source.Where((int x) => x <= 5);
}

なお全ての標準クエリ演算子をクエリ構文で記述することはできないため、それらの機能が必要な場合はメソッド構文で記述する必要があります。
※メソッド構文とクエリ構文を混ぜて使用することも可能ですが、式を分けるかどちらかに統一したほうが可読性の面からも無難です。

基本的な使い方

Where によるデータの抽出と Select によるデータの選択・処理

Where メソッドでは、コレクションから条件に合致する要素を抽出することが可能です。
Select メソッドでは、コレクションの全要素に対して処理を行った結果を取得することができます。
Where メソッドと Select メソッドを組み合わせてコレクションの条件に合致した要素に処理を行った結果を取得する、といった用途で利用することが多いようです。
ここでは、数値のコレクションから条件に合致する要素を抽出し二乗したコレクションを取得する例を示します。

var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// メソッド構文
var methodResult = source.Where(x => x <= 5).Select(x => x * x);

// クエリ構文
var queryResult =
    from x in source
    where x <= 5
    select x * x;

// 結果は { 1, 4, 9, 16, 25 } となる。

また、数値型のコレクションで利用する以外にも、オブジェクトのコレクションに対して処理を行うことも可能です。
例えば人物を定義するPersonクラスのコレクションから20歳以上の人を抽出して氏名をつなげた文字列を取得するといった処理の場合、下記のように書くことができます。
※本稿のサンプルコードではnullチェック等を行っていませんが、実際に利用する場合は例外が発生しないように条件式に気を付けましょう。

/// <summary>
/// 人物を定義するPersonクラス
/// </summary>
class Person
{
    /// <summary>名</summary>
    public string FirstName { get; set; }
    /// <summary>姓</summary>
    public string LastName { get; set; }
    /// <summary>年齢</summary>
    public int Age { get; set; }

    public Person(string firstName, string lastName, int age)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }
}

/// <summary>
/// サンプルクラス
/// </summary>
class Sample
{
    static void Main()
    {
        var persons = new[] {
            new Person("Terrance", "Huff", 18),
            new Person("Deven", "Cyrus", 26),
            new Person("Dave", "Corbett", 53),
            new Person("Brion", "Shoebridge", 12),
            new Person("Terence", "Long", 31)
        };

        // メソッド構文
        var methodResult = persons.Where(x => x.Age >= 20).Select(x => $"{x.FirstName} {x.LastName}");

        // クエリ構文
        var queryResult =
            from x in persons
            where x.Age >= 20
            select $"{x.FirstName} {x.LastName}";

        // 結果は { "Deven Cyrus", "Dave Corbett", "Terence Long" } となる。
    }
}

GroupBy によるデータの組み分け

GroupBy メソッドでは、コレクションの要素から取得した値をもとにグルーピングを行い、グループごとにデータを抽出することが可能です。
下記のサンプルコードでは、前述のPersonクラスのコレクションを元にFirstNameの頭文字でグルーピングを行っています。

// メソッド構文
var methodResult = persons.GroupBy(x => x.FirstName[0], x => $"{x.FirstName} {x.LastName}");

// クエリ構文
var queryResult =
    from x in persons
    group $"{x.FirstName} {x.LastName}" by x.FirstName[0];

// 結果は、下記となる。
// Key 'T'
//   { "Terrance Huff", "Terence Long" }
// Key 'D'
//   { "Deven Cyrus", "Dave Corbett" }
// Key 'B'
//   { "Brion Shoebridge" }

OrderBy / OrderByDescending による並び替え

OrderBy メソッドは昇順、OrderByDescending メソッドは降順で、コレクションの要素の並び替えを行います。
クエリ構文の場合は orderby 句と ascending 句または descending 句の組み合わせで並び替える順序を指定します。
数値を降順で並び替える場合は、下記のようなコードになります。

var source = new int[] { 3, 5, 7, 1, 10, 8, 4, 6, 2, 9 };

// メソッド構文
var methodResult = source.OrderByDescending(x => x);

// クエリ構文
var queryResult =
    from x in source
    orderby x descending
    select x;

// 結果は { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } となる。

最後に

今回は割と利用頻度が高いと思われるメソッドを簡単に説明しましたが、これらを組み合わせることで複雑なデータの抽出処理を可読性を上げつつ簡単に記述することができます。
ただし LINQ は気軽に使える反面、書き方によっては非常に大量のメモリを使うことがあるため、Web アプリケーションのように複数スレッドで同時実行されるようなプログラムで大量のデータを扱う場合は、問題がないか十分気を付ける必要があります。
適切な場所でうまく活用できるようにしましょう。