C# における LINQ 拡張メソッドの実践ガイド

LINQ と IEnumerable の基本概念

LINQ (Language Integrated Query) は、.NET Framework および .NET Core において、コレクションに対するデータ操作を宣言的に行うための強力な機能です。拡張メソッドを利用することで、配列やリストなどのコレクションを効率的に処理できます。

基本的な操作は IEnumerable<T> インターフェースを通じて行われ、遅延評価(Deferred Execution)の特性を持ちます。これは、クエリが定義された時点では実行されず、実際にデータが要求された時点で評価されることを意味します。

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

// 基本的なフィルタリングの例
public class Demo
{
    public static void ExecuteBasicQuery()
    {
        int[] numbers = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
        
        // 50 より大きい数値を抽出
        IEnumerable<int> filtered = numbers.Where(n => n > 50);

        foreach (var num in filtered)
        {
            Console.WriteLine(num);
        }
    }
}

データのフィルタリング (Where)

特定の条件に合致する要素のみを取得するには Where メソッドを使用します。ラムダ式を引数に渡すことで、柔軟な条件定義が可能です。

var staffList = new List<StaffMember>
{
    new StaffMember { Id = 101, FullName = "Tanaka", Age = 29, IsMale = true, Income = 500000 },
    new StaffMember { Id = 102, FullName = "Suzuki", Age = 34, IsMale = true, Income = 300000 },
    new StaffMember { Id = 103, FullName = "Sato", Age = 36, IsMale = false, Income = 900000 },
    new StaffMember { Id = 104, FullName = "Yamada", Age = 18, IsMale = false, Income = 200000 },
    new StaffMember { Id = 105, FullName = "Watanabe", Age = 26, IsMale = true, Income = 100000 },
    new StaffMember { Id = 106, FullName = "Ito", Age = 36, IsMale = false, Income = 800000 },
    new StaffMember { Id = 107, FullName = "Kobayashi", Age = 36, IsMale = true, Income = 850000 },
    new StaffMember { Id = 108, FullName = "Kato", Age = 34, IsMale = true, Income = 800000 }
};

// 年齢が 30 歳以上のスタッフを抽出
var seniorStaff = staffList.Where(s => s.Age >= 30);

foreach (var member in seniorStaff)
{
    Console.WriteLine(member.FullName);
}

要素数の取得と存在確認 (Count / Any)

コレクションの要素数を取得するには Count を使用します。条件付きでカウントすることも可能です。ただし、要素が存在するかどうかのみを確認したい場合は、Any メソッドを使用する方が性能面で優れています。Any は条件に合致する要素を 1 つ見つけた時点で処理を終了するためです。

// 全要素数の取得
int total = staffList.Count();

// 条件に合致する要素数の取得
int seniorCount = staffList.Count(s => s.Age > 30);

// 存在確認(推奨)
bool exists = staffList.Any(s => s.Income > 1000000);

単一要素の取得 (Single / First)

コレクションから特定の要素を 1 つだけ取得する際、状況に応じて以下のメソッドを使い分けます。

  • Single: 該当要素がちょうど 1 つであることを期待。0 または 2 以上だと例外が発生。
  • SingleOrDefault: 該当要素が 1 つなら取得、なければ null (またはデフォルト値) を返す。
  • First: 該当する最初の要素を取得。なければ例外。
  • FirstOrDefault: 該当する最初の要素を取得。なければ null (またはデフォルト値) を返す。
// 名前が一致するものがちょうど 1 つであることを前提
var target = staffList.Where(s => s.FullName == "Tanaka").Single();

// 存在しない場合でもエラーにならない
var missing = staffList.SingleOrDefault(s => s.FullName == "Unknown");
Console.WriteLine(missing == null);

// 先頭の要素を取得
var firstMember = staffList.First();

// 先頭要素、なければ null
var safeFirst = staffList.FirstOrDefault(s => s.FullName == "Unknown");

排序 (OrderBy / OrderByDescending)

データ並び替えには OrderBy (昇順) と OrderByDescending (降順) を使用します。

// 年齢で昇順排序
var sortedByAgeAsc = staffList.OrderBy(s => s.Age);

// 年齢で降順排序
var sortedByAgeDesc = staffList.OrderByDescending(s => s.Age);

foreach (var member in sortedByAgeDesc)
{
    Console.WriteLine($"{member.FullName}: {member.Age}");
}

結果セットの制限 (Skip / Take)

ページネーション実装などに利用できるよう、先頭からのスキップや取得件数の制限が可能です。

// 先頭 3 件をスキップし、次の 2 件を取得
var pageData = staffList.Skip(3).Take(2);

// 複合的なクエリ例
var complexQuery = staffList
    .Where(s => s.Age > 30)
    .OrderBy(s => s.Age)
    .Skip(1)
    .Take(2);

foreach (var item in complexQuery)
{
    Console.WriteLine(item.FullName);
}

集計操作 (Aggregates)

数値データに対する集計関数も用意されています。

  • Max(), Min(): 最大値・最小値
  • Average(): 平均値
  • Sum(): 合計値

グループ化 (GroupBy)

特定のキーに基づいてデータをグループ分けします。

var groupedByAge = staffList.GroupBy(s => s.Age);

foreach (var group in groupedByAge)
{
    Console.WriteLine($"Age: {group.Key}");
    foreach (var member in group)
    {
        Console.WriteLine($" - {member.FullName}");
    }
}

投影操作 (Select) と匿名型

Select メソッドを用いて、データ形状を変換したり、特定のフィールドのみを抽出したりできます。匿名型を使用することで、一時的なデータ構造を簡単に定義できます。

// 年齢のみを抽出
var ages = staffList.Select(s => s.Age);

// 文字列への加工
var descriptions = staffList
    .Where(s => s.Age > 20)
    .Select(s => $"Name: {s.FullName}, Age: {s.Age}");

// 匿名型による投影
var projection = staffList.Select(s => new 
{ 
    Name = s.FullName, 
    YearOld = s.Age, 
    Pay = s.Income 
});

foreach (var item in projection)
{
    Console.WriteLine($"{item.Name} - {item.Pay}");
}

コレクションの変換 (ToList / ToArray)

遅延評価されているクエリ結果を、実際のコレクション matérialize するには ToListToArray を使用します。

IEnumerable<StaffMember> query = staffList.Where(s => s.Income > 600000);
List<StaffMember> highEarners = query.ToList();

メソッドチェーンによる複雑なクエリ

複数の拡張メソッドを連鎖させることで、複雑なデータ処理を簡潔に記述できます。

var report = staffList
    .Where(s => s.Id > 102)
    .GroupBy(s => s.Age)
    .OrderBy(g => g.Key)
    .Take(3)
    .Select(g => new 
    { 
        AgeGroup = g.Key, 
        Count = g.Count(), 
        AvgIncome = g.Average(s => s.Income) 
    });

文字列処理への応用

LINQ はコレクションだけでなく、文字列処理にも応用可能です。例えば、カンマ区切り文字列の平均値計算や、文字出現頻度の解析などが効率的に行えます。

// カンマ区切り数値の平均計算
string csv = "10,232,213,45,12,512,543,234";
double averageValue = csv.Split(',')
    .Select(int.Parse)
    .Average();

// 文字列内の文字出現頻度解析
string text = "dasgdagfdbncewvrthjytbhdgeqcvreq";
var charStats = text
    .Where(char.IsLetter)
    .Select(char.ToLower)
    .GroupBy(c => c)
    .Select(g => new { Char = g.Key, Count = g.Count() })
    .OrderByDescending(x => x.Count)
    .Where(x => x.Count > 2);

foreach (var stat in charStats)
{
    Console.WriteLine($"{stat.Char}: {stat.Count}");
}
class StaffMember { public int Id { get; set; } public string FullName { get; set; } public int Age { get; set; } public bool IsMale { get; set; } public int Income { get; set; } }

タグ: C# LINQ .NET Lambda Expressions Extension Methods

8月1日 13:21 投稿