【初心者向け】C# LINQの使い方を完全解説|実践例付きでやさしく理解!

C#

C#でプログラムを書いていて、こんな経験ありませんか?

「100個のデータから、条件に合うものだけを取り出したい」
「リストの中身を全部2倍にしたい」
「一番大きい値を見つけたい」

そのたびに、for文やforeach文を書いて、if文で条件をチェックして、新しいリストを作って…と、何行ものコードを書く必要がありました。

実は、C#にはLINQ(リンク)という素晴らしい機能があります。
LINQを使えば、複雑なデータ処理も驚くほどシンプルに書けるんです。

この記事では、プログラミング初心者の方でも理解できるように、LINQの基本から実用的な使い方まで、段階的に解説します。

スポンサーリンク

LINQって何?なぜこんなに便利なの?

LINQの正体

LINQは「Language Integrated Query(言語統合クエリ)」の略です。

難しそうに聞こえますが、簡単に言うと「C#の中に組み込まれた、データを簡単に操作するための仕組み」です。

従来の方法(LINQ使用前)

// 偶数だけを取り出したい場合
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evenNumbers = new List<int>();

foreach (int number in numbers)
{
    if (number % 2 == 0)
    {
        evenNumbers.Add(number);
    }
}
// 結果: 2, 4, 6, 8, 10

LINQを使った方法

// 偶数だけを取り出したい場合
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
// 結果: 2, 4, 6, 8, 10

7行が1行に! これがLINQの威力です。

LINQが使えるデータの種類

LINQは、様々な種類のデータに使えます。

基本的なコレクション

  • 配列int[] numbers = {1, 2, 3, 4, 5};
  • ListList<string> names = new List<string>();
  • DictionaryDictionary<string, int> scores;

データベース

  • Entity Frameworkを使ったデータベース操作
  • SQL ServerMySQLなどとの連携

その他

  • XMLファイルの操作
  • JSONデータの処理

LINQのメリット

1. 圧倒的にコードが短くなる

  • 10行のループが1行になることも
  • 可読性が大幅に向上

2. 直感的で理解しやすい

  • 英語のような自然な表現
  • 「どこから」「何を」「どうする」が明確

3. バグが起きにくい

  • ループの書き間違いが減る
  • インデックスエラーの心配がない

4. 保守しやすい

  • 修正が簡単
  • 他の人が読みやすい

LINQの基本構文を覚えよう

ラムダ式の基本

LINQではラムダ式という記法を使います。

最初は慣れないかもしれませんが、とても便利です。

ラムダ式の基本形

(引数) => 処理

具体例

// xという引数を受け取って、x * 2を返す
x => x * 2

// numberという引数を受け取って、偶数かどうかを判定
number => number % 2 == 0

// personという引数を受け取って、年齢を返す
person => person.Age

従来の書き方との比較

// 従来の方法(無名メソッド)
numbers.Where(delegate(int n) { return n % 2 == 0; });

// ラムダ式(簡潔!)
numbers.Where(n => n % 2 == 0);

LINQの基本パターン

データ.メソッド(条件) のパターン

// データから条件に合うものを抽出
var result = データ.Where(条件);

// データの各要素を変換
var result = データ.Select(変換処理);

// データを並び替え
var result = データ.OrderBy(並び順);

よく使うLINQメソッド完全ガイド

Where:条件に合うデータを抽出

基本的な使い方

// 数値のリストから10より大きいものを抽出
List<int> numbers = new List<int> { 5, 10, 15, 20, 25 };
var bigNumbers = numbers.Where(n => n > 10);
// 結果: 15, 20, 25

// 文字列リストから特定の文字を含むものを抽出
List<string> names = new List<string> { "Alice", "Bob", "Charlie", "Diana" };
var aNames = names.Where(name => name.Contains("a"));
// 結果: "Alice", "Diana"

複数条件の指定

// 10より大きく、25未満の数値
var middleNumbers = numbers.Where(n => n > 10 && n < 25);

// 文字数が4文字以上で、Aで始まる名前
var longANames = names.Where(name => name.Length >= 4 && name.StartsWith("A"));

Select:データを変換

基本的な使い方

// 数値を2倍にする
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var doubledNumbers = numbers.Select(n => n * 2);
// 結果: 2, 4, 6, 8, 10

// 文字列を大文字に変換
List<string> names = new List<string> { "alice", "bob", "charlie" };
var upperNames = names.Select(name => name.ToUpper());
// 結果: "ALICE", "BOB", "CHARLIE"

複雑な変換

// オブジェクトのプロパティを取得
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Person> people = new List<Person>
{
    new Person { Name = "Alice", Age = 25 },
    new Person { Name = "Bob", Age = 30 }
};

// 名前だけのリストを作成
var names = people.Select(p => p.Name);

// 新しいオブジェクトを作成
var summaries = people.Select(p => new { 
    Name = p.Name, 
    Category = p.Age >= 30 ? "大人" : "若者" 
});

OrderBy / OrderByDescending:並び替え

基本的な並び替え

List<int> numbers = new List<int> { 3, 1, 4, 1, 5, 9, 2, 6 };

// 昇順(小さい順)
var ascending = numbers.OrderBy(n => n);
// 結果: 1, 1, 2, 3, 4, 5, 6, 9

// 降順(大きい順)
var descending = numbers.OrderByDescending(n => n);
// 結果: 9, 6, 5, 4, 3, 2, 1, 1

複数条件での並び替え

// 年齢順、同じ年齢なら名前順
var sortedPeople = people
    .OrderBy(p => p.Age)
    .ThenBy(p => p.Name);

FirstOrDefault / LastOrDefault:最初・最後の要素

基本的な使い方

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };

// 最初の要素
var first = numbers.FirstOrDefault();
// 結果: 10

// 条件に合う最初の要素
var firstBig = numbers.FirstOrDefault(n => n > 25);
// 結果: 30

// 最後の要素
var last = numbers.LastOrDefault();
// 結果: 50

注意:null チェックが重要

List<int> emptyList = new List<int>();
var result = emptyList.FirstOrDefault();
// result は 0(intのデフォルト値)

List<string> emptyStringList = new List<string>();
var stringResult = emptyStringList.FirstOrDefault();
// stringResult は null

// 安全な使い方
if (stringResult != null)
{
    Console.WriteLine(stringResult);
}

Count / Any / All:件数と存在チェック

Count:件数を数える

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 全体の件数
var totalCount = numbers.Count();
// 結果: 10

// 条件に合う件数
var evenCount = numbers.Count(n => n % 2 == 0);
// 結果: 5

Any:条件に合うものが存在するか

// 偶数が存在するか
var hasEven = numbers.Any(n => n % 2 == 0);
// 結果: true

// 100より大きい数が存在するか
var hasBig = numbers.Any(n => n > 100);
// 結果: false

All:すべてが条件に合うか

// すべてが正数か
var allPositive = numbers.All(n => n > 0);
// 結果: true

// すべてが偶数か
var allEven = numbers.All(n => n % 2 == 0);
// 結果: false

メソッドチェーンで複雑な処理も簡単に

メソッドチェーンの基本

LINQの真骨頂は、複数のメソッドを**チェーン(数珠つなぎ)**にできることです。

基本的なチェーン

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 偶数を抽出して、2倍にして、降順に並べる
var result = numbers
    .Where(n => n % 2 == 0)      // 偶数を抽出: 2, 4, 6, 8, 10
    .Select(n => n * 2)          // 2倍にする: 4, 8, 12, 16, 20
    .OrderByDescending(n => n);  // 降順に並べる: 20, 16, 12, 8, 4

実用的なメソッドチェーン例

例1:学生の成績処理

public class Student
{
    public string Name { get; set; }
    public int Score { get; set; }
    public string Subject { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Name = "Alice", Score = 85, Subject = "Math" },
    new Student { Name = "Bob", Score = 92, Subject = "Math" },
    new Student { Name = "Charlie", Score = 78, Subject = "English" },
    new Student { Name = "Diana", Score = 95, Subject = "Math" }
};

// 数学の成績が80点以上の学生を、得点順(降順)で取得
var topMathStudents = students
    .Where(s => s.Subject == "Math")    // 数学の学生のみ
    .Where(s => s.Score >= 80)          // 80点以上
    .OrderByDescending(s => s.Score)    // 得点順(高い順)
    .Select(s => s.Name)                // 名前だけ取得
    .ToList();                          // リストに変換

// 結果: ["Diana", "Bob", "Alice"]

例2:売上データの集計

public class Sale
{
    public string Product { get; set; }
    public decimal Amount { get; set; }
    public DateTime Date { get; set; }
}

List<Sale> sales = new List<Sale>
{
    new Sale { Product = "PC", Amount = 1000, Date = new DateTime(2024, 1, 15) },
    new Sale { Product = "Phone", Amount = 800, Date = new DateTime(2024, 1, 20) },
    new Sale { Product = "PC", Amount = 1200, Date = new DateTime(2024, 2, 10) }
};

// 今年のPC売上の平均金額
var avgPcSales = sales
    .Where(s => s.Date.Year == 2024)           // 今年の売上
    .Where(s => s.Product == "PC")             // PC の売上
    .Select(s => s.Amount)                     // 金額のみ
    .Average();                                // 平均値

// 結果: 1100

実践的なLINQ活用例

データベース風の操作

Join操作(結合)

// 部署情報
public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// 従業員情報
public class Employee
{
    public string Name { get; set; }
    public int DepartmentId { get; set; }
    public decimal Salary { get; set; }
}

List<Department> departments = new List<Department>
{
    new Department { Id = 1, Name = "開発部" },
    new Department { Id = 2, Name = "営業部" }
};

List<Employee> employees = new List<Employee>
{
    new Employee { Name = "田中", DepartmentId = 1, Salary = 500000 },
    new Employee { Name = "佐藤", DepartmentId = 2, Salary = 450000 },
    new Employee { Name = "鈴木", DepartmentId = 1, Salary = 550000 }
};

// 従業員と部署を結合して、部署名付きの情報を取得
var employeeDetails = employees
    .Join(departments,                          // 結合するコレクション
          emp => emp.DepartmentId,              // 従業員側のキー
          dept => dept.Id,                      // 部署側のキー
          (emp, dept) => new                    // 結合結果
          {
              EmployeeName = emp.Name,
              DepartmentName = dept.Name,
              Salary = emp.Salary
          })
    .ToList();

GroupBy操作(グループ化)

// 部署別の平均給与を計算
var avgSalaryByDept = employees
    .GroupBy(emp => emp.DepartmentId)           // 部署IDでグループ化
    .Select(group => new
    {
        DepartmentId = group.Key,               // グループのキー
        AverageSalary = group.Average(emp => emp.Salary), // 平均給与
        EmployeeCount = group.Count()           // 人数
    })
    .ToList();

ファイル・文字列処理

CSVファイルの処理

// CSVデータの読み込みと処理
string csvData = @"
Name,Age,City
Alice,25,Tokyo
Bob,30,Osaka
Charlie,35,Tokyo
Diana,28,Kyoto";

var people = csvData
    .Split('\n')                                // 行に分割
    .Skip(1)                                    // ヘッダー行をスキップ
    .Where(line => !string.IsNullOrEmpty(line)) // 空行を除外
    .Select(line => line.Split(','))            // カンマで分割
    .Select(parts => new
    {
        Name = parts[0],
        Age = int.Parse(parts[1]),
        City = parts[2]
    })
    .Where(person => person.Age >= 30)          // 30歳以上のみ
    .OrderBy(person => person.Name)             // 名前順
    .ToList();

ログファイルの分析

List<string> logLines = new List<string>
{
    "2024-01-15 10:30:15 ERROR Database connection failed",
    "2024-01-15 10:30:20 INFO User login successful",
    "2024-01-15 10:30:25 ERROR File not found",
    "2024-01-15 10:30:30 INFO Data processing completed"
};

// エラーログだけを抽出して時刻順に並べる
var errorLogs = logLines
    .Where(log => log.Contains("ERROR"))        // ERRORを含む行のみ
    .Select(log => new
    {
        Timestamp = log.Substring(0, 19),       // 日時部分
        Message = log.Substring(26)             // メッセージ部分
    })
    .OrderBy(log => log.Timestamp)              // 時刻順
    .ToList();

LINQを使うときの注意点と落とし穴

遅延実行(Lazy Evaluation)の理解

LINQの重要な特徴の一つが遅延実行です。

遅延実行とは

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// この時点では実際の処理は実行されない!
var query = numbers.Where(n => n > 3);

// ToList()やforeach等で初めて実行される
var result = query.ToList();  // ここで実際に処理が実行

遅延実行のメリット

  • メモリ効率が良い
  • 必要になるまで処理を遅らせられる
  • クエリの組み立てが柔軟

注意が必要な場面

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var query = numbers.Where(n => n > 3);

// 元のリストを変更
numbers.Add(6);
numbers.Add(7);

// この時点で実行されるため、追加された6,7も処理対象になる
var result = query.ToList();  // 結果: 4, 5, 6, 7

パフォーマンスの考慮事項

メモリ使用量に注意

// 悪い例:不必要にメモリを消費
var badExample = hugelist
    .Where(x => x.IsActive)
    .ToList()                    // 全件メモリに読み込み
    .Where(x => x.Score > 80)
    .ToList();                   // また全件メモリに読み込み

// 良い例:遅延実行を活用
var goodExample = hugelist
    .Where(x => x.IsActive)
    .Where(x => x.Score > 80)    // 条件を組み合わせる
    .ToList();                   // 最後にまとめて実行

複数回の列挙を避ける

// 悪い例:同じクエリを複数回実行
var expensiveQuery = database.Users.Where(u => ComplexOperation(u));
var count = expensiveQuery.Count();        // 1回目の実行
var first = expensiveQuery.FirstOrDefault(); // 2回目の実行

// 良い例:一度だけ実行して結果を保存
var results = expensiveQuery.ToList();     // 1回だけ実行
var count = results.Count();
var first = results.FirstOrDefault();

null チェックの重要性

FirstOrDefault の罠

List<string> names = new List<string>();

// 空のリストの場合、nullが返される
var firstName = names.FirstOrDefault();
if (firstName != null)  // 必須のチェック
{
    Console.WriteLine(firstName.Length);
}

// より安全な書き方
var firstName2 = names.FirstOrDefault();
Console.WriteLine(firstName2?.Length ?? 0);  // null条件演算子を使用

Where の後の FirstOrDefault

List<Person> people = GetPeopleList();

// 条件に合う人が見つからない場合、nullが返される
var targetPerson = people
    .Where(p => p.Age > 100)  // 100歳超の人はいない?
    .FirstOrDefault();

if (targetPerson != null)
{
    Console.WriteLine(targetPerson.Name);
}

読みやすさとのバランス

複雑すぎるクエリは避ける

// 悪い例:読みにくい
var result = data
    .Where(x => x.Status == "Active" && x.Category != null)
    .GroupBy(x => x.Category)
    .Where(g => g.Count() > 5)
    .SelectMany(g => g.Where(x => x.Score > g.Average(y => y.Score)))
    .OrderByDescending(x => x.Score)
    .Take(10)
    .Select(x => new { x.Name, x.Score, x.Category })
    .ToList();

// 良い例:段階的に分割
var activeItems = data.Where(x => x.Status == "Active" && x.Category != null);
var largeCategories = activeItems
    .GroupBy(x => x.Category)
    .Where(g => g.Count() > 5);

var result = largeCategories
    .SelectMany(g => g.Where(x => x.Score > g.Average(y => y.Score)))
    .OrderByDescending(x => x.Score)
    .Take(10)
    .Select(x => new { x.Name, x.Score, x.Category })
    .ToList();

LINQ クエリ構文も知っておこう

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

これまで紹介してきたのはメソッド構文でしたが、LINQにはクエリ構文という書き方もあります。

メソッド構文(今まで使ってきた方法)

var result = students
    .Where(s => s.Score >= 80)
    .OrderByDescending(s => s.Score)
    .Select(s => s.Name);

クエリ構文(SQL風の書き方)

var result = from s in students
             where s.Score >= 80
             orderby s.Score descending
             select s.Name;

クエリ構文の利点

複雑な結合処理

// メソッド構文(ちょっと読みにくい)
var result1 = employees
    .Join(departments,
          emp => emp.DepartmentId,
          dept => dept.Id,
          (emp, dept) => new { emp, dept })
    .Where(x => x.emp.Salary > 500000)
    .Select(x => new { x.emp.Name, x.dept.Name });

// クエリ構文(SQL に慣れていれば読みやすい)
var result2 = from emp in employees
              join dept in departments on emp.DepartmentId equals dept.Id
              where emp.Salary > 500000
              select new { emp.Name, dept.Name };

複数の from(複数のコレクションを扱う)

List<string> categories = new List<string> { "食品", "電化製品" };
List<string> products = new List<string> { "りんご", "テレビ", "みかん", "冷蔵庫" };

// すべての組み合わせを作成
var combinations = from category in categories
                   from product in products
                   select new { Category = category, Product = product };

まとめ

LINQの基本から実践的な使い方まで、いかがでしたか?

今日覚えた重要なポイント

  • LINQ は複雑なデータ処理を1行で書ける魔法の機能
  • Where, Select, OrderBy が基本の3大メソッド
  • メソッドチェーンで複雑な処理も簡潔に
  • 遅延実行とパフォーマンスに注意

コメント

タイトルとURLをコピーしました