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 するには ToList や ToArray を使用します。
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; }
}