C#におけるLINQの基本概念と使い方

LINQとは何か

LINQ(言語統合クエリ、Language Integrated Query)は、.NETフレームワークの強力な拡張機能です。これにより、データベースを照会するのと同様の感覚で、オブジェクトのコレクション、XMLドキュメント、データベースなど、様々なデータソースをクエリできるようになります。LINQを使用すると、データ操作のコードがより読みやすく、簡潔になります。

LINQプロバイダ

異なる種類のデータソースに対してLINQクエリを実行するには、そのデータソースに特化したコードモジュール、いわゆるLINQプロバイダが必要です。Microsoftは、一般的なデータソース向けにいくつかのLINQプロバイダを提供しています。

Visual C# 標準LINQサポート ADO.NET LINQサポート
LINQ to Objects LINQ to SQL
LINQ to XML LINQ to Datasets
BLINQ (ASP.NET) LINQ to Entities

匿名型

匿名型は、名前のないクラス型を簡単に作成できる機能です。主にLINQクエリの結果を保持するために使用されます。匿名型の変数を作成するには、クラス名とコンストラクタを省略した形式を使用します。

new { プロパティ名 = 値, プロパティ名 = 値, ... };

以下は、匿名型の変数を作成し、そのプロパティにアクセスする例です。

static void Main(string[] args)
{
    var product = new { Name = "Super Gadget", Price = 299.99, Category = "Electronics" };
    Console.WriteLine($"{product.Name}, {product.Price}, {product.Category}");
}

注意点:

  • 匿名型はローカル変数でのみ使用できます。クラスのメンバーとして使用することはできません。
  • 匿名型には名前がないため、varキーワードを使用する必要があります。
  • 匿名型のオブジェクトのプロパティは読み取り専用です。コンパイラは、匿名型のオブジェクト初期化子を検出すると、プライベートな名前を持つ新しいクラス型を生成し、各メンバー初期化子の型を推論して、同じ名前を持つ読み取り専用プロパティを作成します。

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

LINQには、クエリを記述する2つの主な方法があります。

  • クエリ構文: SQLに似た宣言的な構文です。何を取得したいかを記述しますが、どのように実行するかは指定しません。可読性が高く、複雑なクエリに適しています。
  • メソッド構文: 標準クエリ演算子を使用した命令的な構文です。メソッド呼び出しの順序を明示的に指定します。

両方の構文は組み合わせて使用でき、実行時のパフォーマンスに差はありません。Microsoftはクエリ構文を推奨しています。

static void Main(string[] args)
{
    int[] data = { 2, 5, 28, 31, 17, 16, 42 };

    // クエリ構文
    var filteredDataQuery = from d in data
                          where d < 20
                          select d;

    // メソッド構文
    var filteredDataMethod = data.Where(d => d < 20);

    // 両方の構文を組み合わせる
    int count = (from d in data
                 where d < 20
                 select d).Count();

    foreach (var item in filteredDataQuery)
    {
        Console.WriteLine($"{item} ");
    } // 2 5 17 16

    Console.WriteLine();
    foreach (var item in filteredDataMethod)
    {
        Console.WriteLine($"{item} ");
    } // 2 5 17 16

    Console.WriteLine(count); // 4
}

クエリ変数

LINQクエリは、2種類の結果を返すことができます。

  • 列挙可能なコレクション: クエリ条件を満たす項目のリストです。この場合、クエリは列挙子が処理されるまで実行されません(遅延実行)。
  • スカラー値: クエリ結果の要約(例:合計、平均、カウント)です。この場合、クエリはすぐに実行され、結果がクエリ変数に格納されます。
int[] values = { 2, 5, 28 };
// 列挙可能なコレクションを返すクエリ
IEnumerable<int> lowValues = from v in values where v < 20 select v;

// スカラー値を返すクエリ
int totalCount = (from v in values where v < 20 select v).Count();

クエリ変数(上記のlowValues)は、クエリの結果を直接保持しません。代わりに、クエリを実行するコードを生成します。クエリの実行タイミングは以下のようになります。

  • 列挙可能なコレクションを返すクエリは、列挙子が処理されるまで実行されません。
  • 列挙子が複数回処理されると、クエリも複数回実行されます。
  • 列挙が開始される前にデータが変更されると、新しいデータが使用されます。
  • スカラー値を返すクエリは、すぐに実行され、結果がクエリ変数に格納されます。

クエリ式の構造

LINQクエリ式は、特定の順序で句を記述する必要があります。

  • from句とselect...group句は必須です。
  • 他の句(where, orderbyなど)はオプションです。
  • select句は式の最後に記述します。
  • 任意の数のfrom...let...where句を含めることができます。

from

クエリのデータソースを指定し、反復変数を導入します。

from 型 反復変数 in コレクション

反復変数は、データソースの各要素を表します。型はコンパイラが推論できるため、省略可能です。

int[] arr1 = { 10, 11, 12, 13 };
var query = from item in arr1 where item < 13 select item;
foreach (var item in query)
{
    Console.Write($"{item} ");
}

join

2つ以上のコレクションを結合するために使用されます。結合操作は、2つのコレクションを受け取り、各オブジェクトに両方の元のコレクションのすべてのフィールドを含む一時的なオブジェクトのコレクションを作成します。

join 型 結合変数 in コレクション2 on フィールド1 equals フィールド2
class Member
{
    public int Id;
    public string LastName;
    public Member(int id, string lastName)
    {
        Id = id;
        LastName = lastName;
    }
}

class Enrollment
{
    public string CourseName;
    public int MemberId;
    public Enrollment(string courseName, int memberId)
    {
        CourseName = courseName;
        MemberId = memberId;
    }
}

static Member[] members =
{
    new Member(1, "Tanaka"),
    new Member(2, "Sato"),
    new Member(3, "Suzuki"),
};

static Enrollment[] enrollments =
{
    new Enrollment("Math", 1),
    new Enrollment("Math", 2),
    new Enrollment("History", 1),
    new Enrollment("History", 3),
    new Enrollment("Physics", 3)
};

static void Main(string[] args)
{
    var query = from m in members
                join e in enrollments on m.Id equals e.MemberId
                where e.CourseName == "History"
                select m.LastName;

    foreach (var result in query)
    {
        Console.WriteLine($"History course student: {result}");
    }
}

let

式の計算結果を、後続の操作で使用する識別子に代入します。

let 識別子 = 式
int[] groupA = { 3, 4, 5, 6 };
int[] groupB = { 6, 7, 8, 9 };
var query = from a in groupA
           from b in groupB
           let sum = a + b
           where sum == 12
           select new { a, b, sum };

foreach (var item in query)
{
    Console.WriteLine($"{item}");
}

where

指定された条件に基づいて項目をフィルタリングします。

where ブール式
int[] groupA = { 3, 4, 5, 6 };
int[] groupB = { 6, 7, 8, 9 };
var query = from a in groupA
           from b in groupB
           let sum = a + b
           where sum >= 11
           where a == 4
           select new { a, b, sum };

foreach (var item in query)
{
    Console.WriteLine($"{item}");
}

orderby

式に基づいて結果を並べ替えます。デフォルトは昇順ですが、ascendingまたはdescendingキーワードで順序を指定できます。

orderby 式 ascending, 式 descending, ...
var members = new[]
{
    new { LastName = "Jones", FirstName = "Mary", Age = 19, Major = "History" },
    new { LastName = "Smith", FirstName = "Bob", Age = 20, Major = "ComputerScience" },
    new { LastName = "Fleming", FirstName = "Carol", Age = 20, Major = "History" }
};

var query = from m in members
            orderby m.Age ascending, m.LastName descending
            select m;

foreach (var item in query)
{
    Console.WriteLine($"{item}");
}

select...group

クエリの結果として返す項目を指定します。

  • select:選択するオブジェクトの一部(フィールドや新しいオブジェクト)を指定します。
  • group...by:選択された項目をグループ化するために使用します。
select 式
group 式1 by 式2
var members = new[]
{
    new { LastName = "Jones", FirstName = "Mary", Age = 19, Major = "History" },
    new { LastName = "Smith", FirstName = "Bob", Age = 20, Major = "ComputerScience" },
    new { LastName = "Fleming", FirstName = "Carol", Age = 20, Major = "History" }
};

// 複数のフィールドからなる新しい匿名オブジェクトを作成
var query = from m in members
            select new { m.LastName, m.FirstName, Token = "2023Member2023" };

foreach (var item in query)
{
    Console.WriteLine($"{item.LastName}, {item.FirstName}, {item.Token}");
}

group by

指定された基準に基づいて選択されたオブジェクトをグループ化します。

group 型 項目 by 項目.フィールド
var members = new[]
{
    new { LastName = "Jones", FirstName = "Mary", Age = 19, Major = "History" },
    new { LastName = "Smith", FirstName = "Bob", Age = 20, Major = "ComputerScience" },
    new { LastName = "Fleming", FirstName = "Carol", Age = 20, Major = "History" }
};

var query = from m in members
            group m by m.Major;

foreach (var group in query)
{
    Console.WriteLine($"----- {group.Key} -----");
    foreach (var member in group)
    {
        Console.WriteLine($"{member.LastName} · {member.FirstName}");
    }
}

into句(クエリ継続)

クエリの一部の結果を名前を付けて、後続のクエリで使用できるようにします。

into 変数
join 型 結合変数 in 式 on 式 equals 式 into 変数
int[] groupA = { 3, 4, 5, 6 };
int[] groupB = { 6, 7, 8, 9 };
var query = from a in groupA
           join b in groupB on a equals b
           into joinedGroup
           from c in joinedGroup
           select c;

foreach (var item in query)
{
    Console.WriteLine($"aとbの共通要素: {item}");
}

標準クエリ演算子

標準クエリ演算子は、LINQクエリを構成する一連のAPIメソッドです。これらの演算子は、IEnumerable<T>インターフェイスを実装する任意の.NET配列またはコレクションをクエリするために使用できます。

演算子 説明
Where 指定された述語に基づいてシーケンスをフィルタリングします。
Select オブジェクトまたはオブジェクトの一部を指定して選択します。
SelectMany コレクションのコレクションを返し、それらの結果を1つのコレクションにマージします。
Take シーケンスの先頭から指定された数の要素を返します。
Skip シーケンスの先頭から指定された数の要素をスキップします。
TakeWhile 述語がtrueを返す限り、現在の項目を選択し、falseになった時点で残りの項目を破棄します。
SkipWhile 述語がtrueを返す限り、現在の項目をスキップし、falseになった時点で残りの項目を選択します。
Join 2つのシーケンスに対して内部結合を実行します。
GroupJoin 階層的な結果を生成する結合。最初のシーケンスの各要素が、2番目のシーケンスの要素に関連付けられます。
Concat 2つのシーケンスを連結します。
OrderBy/ThenBy 1つ以上のキーに基づいてシーケンスの要素を昇順で並べ替えます。
Reverse シーケンスの要素の順序を反転させます。
GroupBy シーケンスの要素をグループ化します。
Distinct シーケンスから重複する要素を削除します。
Union 2つのシーケンスの和集合を返します。
Intersect 2つのシーケンスの積集合を返します。
Except 最初のシーケンスの要素から、2番目のシーケンスにも存在する重複要素を削除して返します。
ToArray シーケンスを配列として返します。
ToList シーケンスをList<T>として返します。
First/FirstOrDefault シーケンスの最初の要素、または述語に一致する最初の要素を返します。
Last/LastOrDefault シーケンスの最後の要素、または述語に一致する最後の要素を返します。
Single/SingleOrDefault シーケンスの唯一の要素、または述語に一致する唯一の要素を返します。
Count/Sum/Min/Max/Average シーケンスの要素数、合計、最小値、最大値、平均値を計算します。

LINQ to XML

LINQ to XMLは、メモリ内でXMLツリーを作成、操作、およびクエリするためのAPIを提供します。単一の言語を使用してXMLツリーをトップダウンで構築できます。

XMLツリーの作成と操作

関数型構築を使用して、1つのステートメントでXMLツリー全体を作成できます。

XDocument doc = new XDocument(
    new XElement("Employees",
        new XElement("Employee",
            new XElement("Name", "Alice"),
            new XElement("Department", "HR")
        ),
        new XElement("Employee",
            new XElement("Name", "Bob"),
            new XElement("Department", "IT")
        )
    )
);
Console.WriteLine(doc);

XMLツリーのクエリ

XMLツリーをクエリするには、LINQクエリを使用します。

XDocument doc = XDocument.Load("employees.xml");
XElement root = doc.Element("Employees");

var query = from e in root.Elements("Employee")
            where (string)e.Element("Department") == "IT"
            select e.Element("Name").Value;

foreach (var name in query)
{
    Console.WriteLine($"IT Department Employee: {name}");
}

タグ: C# LINQ .NET クエリ構文 メソッド構文

8月2日 09:50 投稿