【C#】LINQの使い方とサンプルコード

前置き

Linqに関してよく忘れるので備忘録です。

Linqとは

C#LINQ (Language Integrated Query) には、さまざまな種類のメソッドがあります。これらのメソッドは、コレクション (配列、リストなど) に対してクエリを実行し、データのフィルタリング、並べ替え、変換、グループ化などの操作を行うために使用されます。

LINQ のメソッドは、大きく分けて以下のカテゴリに分類できます。

フィルタリング:

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

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

    // クエリ構文
    var evenNumbersQuery = from num in numbers
                           where num % 2 == 0
                           select num;

    // メソッド構文
    var evenNumbersMethod = numbers.Where(num => num % 2 == 0);
    Console.WriteLine("偶数 (クエリ構文): " + string.Join(", ", evenNumbersQuery));
    Console.WriteLine("偶数 (メソッド構文): " + string.Join(", ", evenNumbersMethod));

実行結果


    偶数 (クエリ構文): 2, 4, 6, 8, 10
    偶数 (メソッド構文): 2, 4, 6, 8, 10
OfType: 指定された型である要素のみを選択します。

    List<object> mixedList = new List<object> { 1, "hello", 2.5, 3, "world" };

    // クエリ構文
    var integersQuery = from item in mixedList
                        where item is int
                        select item;

    // メソッド構文
    var integersMethod = mixedList.OfType<int>();

    Console.WriteLine("整数 (クエリ構文): " + string.Join(", ", integersQuery));
    Console.WriteLine("整数 (メソッド構文): " + string.Join(", ", integersMethod));

実行結果


    整数 (クエリ構文): 1, 3
    整数 (メソッド構文): 1, 3

射影:

Select: 各要素を新しい形式に変換します。

    List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

    // クエリ構文
    var nameLengthsQuery = from name in names
                           select name.Length;

    // メソッド構文
    var nameLengthsMethod = names.Select(name => name.Length);

    Console.WriteLine("名前の長さ (クエリ構文): " + string.Join(", ", nameLengthsQuery));
    Console.WriteLine("名前の長さ (メソッド構文): " + string.Join(", ", nameLengthsMethod));

実行結果


    名前の長さ (クエリ構文): 5, 3, 7
    名前の長さ (メソッド構文): 5, 3, 7
SelectMany: 各要素からコレクションを抽出し、それらを 1 つのフラットなコレクションに結合します。

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

    // クエリ構文
    var allNumbersQuery = from list in listOfLists
                          from num in list
                          select num;

    // メソッド構文
    var allNumbersMethod = listOfLists.SelectMany(list => list);

    Console.WriteLine("すべての数値 (クエリ構文): " + string.Join(", ", allNumbersQuery));
    Console.WriteLine("すべての数値 (メソッド構文): " + string.Join(", ", allNumbersMethod));

実行結果


    すべての数値 (クエリ構文): 1, 2, 3, 4, 5, 6
    すべての数値 (メソッド構文): 1, 2, 3, 4, 5, 6

3. 並べ替え:

  • OrderBy: 要素を昇順に並べ替えます。
  • OrderByDescending: 要素を降順に並べ替えます。
  • ThenBy: 最初の並べ替えの後に、さらに昇順で並べ替えます。
  • ThenByDescending: 最初の並べ替えの後に、さらに降順で並べ替えます。
  • Reverse: 要素の順序を反転します。

    List<string> fruits = new List<string> { "Banana", "Apple", "Orange", "Grape" };

    // クエリ構文 (OrderBy)
    var sortedFruitsAscQuery = from fruit in fruits
                               orderby fruit
                               select fruit;

    // メソッド構文 (OrderByDescending)
    var sortedFruitsDescMethod = fruits.OrderByDescending(fruit => fruit);

    Console.WriteLine("昇順 (クエリ構文): " + string.Join(", ", sortedFruitsAscQuery));
    Console.WriteLine("降順 (メソッド構文): " + string.Join(", ", sortedFruitsDescMethod));

    List<Person> people = new List<Person> {
        new Person { FirstName = "太郎", LastName = "山田", Age = 30 },
        new Person { FirstName = "花子", LastName = "佐藤", Age = 25 },
        new Person { FirstName = "健太", LastName = "山田", Age = 20 }
    };
    // クエリ構文 (OrderBy, ThenBy)
    var sortedPeopleQuery = from person in people
                            orderby person.LastName, person.FirstName descending
                            select person;

    // メソッド構文 (OrderBy, ThenBy)
    var sortedPeopleMethod = people.OrderBy(p => p.LastName).ThenByDescending(p => p.FirstName);

    Console.WriteLine("複合ソート (クエリ構文):");
    foreach (var person in sortedPeopleQuery)
    {
        Console.WriteLine($"  {person.LastName} {person.FirstName} ({person.Age})");
    }
    Console.WriteLine("複合ソート (メソッド構文):");
    foreach (var person in sortedPeopleMethod)
    {
        Console.WriteLine($"  {person.LastName} {person.FirstName} ({person.Age})");
    }

実行結果


    昇順 (クエリ構文): Apple, Banana, Grape, Orange
    降順 (メソッド構文): Orange, Grape, Banana, Apple
    複合ソート (クエリ構文):
      佐藤 花子 (25)
      佐藤 優子 (35)
      山田 健太 (20)
      山田 太郎 (30)
    複合ソート (メソッド構文):
      佐藤 花子 (25)
      佐藤 優子 (35)
      山田 健太 (20)
      山田 太郎 (30)

4. グループ化:

  • GroupBy: 共通のキーに基づいて要素をグループ化します。

    List<Person> people = new List<Person> {
        new Person { FirstName = "太郎", LastName = "山田", Age = 30 },
        new Person { FirstName = "花子", LastName = "佐藤", Age = 25 },
        new Person { FirstName = "健太", LastName = "山田", Age = 20 },
        new Person { FirstName = "優子", LastName = "佐藤", Age = 35 }
    };

    // クエリ構文
    var groupedByLastNameQuery = from person in people
                                 group person by person.LastName into g
                                 select new { LastName = g.Key, People = g };

    // メソッド構文
    var groupedByLastNameMethod = people.GroupBy(person => person.LastName)
                                      .Select(g => new { LastName = g.Key, People = g });

    Console.WriteLine("グループ化 (クエリ構文):");
    foreach (var group in groupedByLastNameQuery)
    {
        Console.WriteLine($"  姓: {group.LastName}");
        foreach (var person in group.People)
        {
            Console.WriteLine($"    - {person.FirstName} {person.LastName} ({person.Age})");
        }
    }

    Console.WriteLine("グループ化 (メソッド構文):");
    foreach (var group in groupedByLastNameMethod)
    {
        Console.WriteLine($"  姓: {group.LastName}");
        foreach (var person in group.People)
        {
            Console.WriteLine($"    - {person.FirstName} {person.LastName} ({person.Age})");
        }
    }

実行結果


    グループ化 (クエリ構文):
      姓: 山田
        - 太郎 山田 (30)
        - 健太 山田 (20)
      姓: 佐藤
        - 花子 佐藤 (25)
        - 優子 佐藤 (35)
    グループ化 (メソッド構文):
      姓: 山田
        - 太郎 山田 (30)
        - 健太 山田 (20)
      姓: 佐藤
        - 花子 佐藤 (25)
        - 優子 佐藤 (35)

5. 結合:

Join: 2 つのコレクションの関連する要素を結合します。


    List<Product> products = new List<Product> {
        new Product { ProductId = 1, Name = "Laptop" },
        new Product { ProductId = 2, Name = "Mouse" },
        new Product { ProductId = 3, Name = "Keyboard" }
    };

    List<Order> orders = new List<Order> {
        new Order { OrderId = 101, ProductId = 1, Quantity = 1 },
        new Order { OrderId = 102, ProductId = 2, Quantity = 2 },
        new Order { OrderId = 103, ProductId = 1, Quantity = 3 }
    };

    // クエリ構文
    var productOrdersQuery = from product in products
                             join order in orders on product.ProductId equals order.ProductId
                             select new { product.Name, order.OrderId, order.Quantity };

    // メソッド構文
    var productOrdersMethod = products.Join(orders,
                                            product => product.ProductId,
                                            order => order.ProductId,
                                            (product, order) => new { product.Name, order.OrderId, order.Quantity });

    Console.WriteLine("結合 (クエリ構文):");
    foreach (var po in productOrdersQuery)
    {
        Console.WriteLine($"  製品名: {po.Name}, 注文ID: {po.OrderId}, 数量: {po.Quantity}");
    }

    Console.WriteLine("結合 (メソッド構文):");
    foreach (var po in productOrdersMethod)
    {
        Console.WriteLine($"  製品名: {po.Name}, 注文ID: {po.OrderId}, 数量: {po.Quantity}");
    }

実行結果



    結合 (クエリ構文):
      製品名: Laptop, 注文ID: 101, 数量: 1
      製品名: Mouse, 注文ID: 102, 数量: 2
      製品名: Laptop, 注文ID: 103, 数量: 3
    結合 (メソッド構文):
      製品名: Laptop, 注文ID: 101, 数量: 1
      製品名: Mouse, 注文ID: 102, 数量: 2
      製品名: Laptop, 注文ID: 103, 数量: 3

集計:

  • Count: コレクション内の要素数を返します。
  • Sum: 数値コレクションの要素の合計を計算します。
  • Min: 数値コレクションの最小値を返します。
  • Max: 数値コレクションの最大値を返します。
  • Average: 数値コレクションの要素の平均値を計算します。

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

    int count = numbers.Count();
    int sum = numbers.Sum();
    int min = numbers.Min();
    int max = numbers.Max();
    double average = numbers.Average();

    Console.WriteLine($"要素数: {count}");
    Console.WriteLine($"合計: {sum}");
    Console.WriteLine($"最小値: {min}");
    Console.WriteLine($"最大値: {max}");
    Console.WriteLine($"平均値: {average}");

実行結果


    要素数: 5
    合計: 19
    最小値: 1
    最大値: 8
    平均値: 3.8

要素の取得:

  • First: シーケンスの最初の要素を返します。
  • FirstOrDefault: シーケンスの最初の要素を返します。シーケンスが空の場合は既定値を返します。
  • Last: シーケンスの最後の要素を返します。
  • LastOrDefault: シーケンスの最後の要素を返します。シーケンスが空の場合は既定値を返します。
  • Single: シーケンスの唯一の要素を返します。シーケンスが空であるか、複数の要素が含まれている場合は例外をスローします。
  • SingleOrDefault: シーケンスの唯一の要素を返します。シーケンスが空の場合は既定値を返します。複数の要素が含まれている場合は例外をスローします。
  • ElementAt: 指定されたインデックスにある要素を返します。
  • ElementAtOrDefault: 指定されたインデックスにある要素を返します。インデックスが範囲外の場合は既定値を返します。

    List<string> colors = new List<string> { "Red", "Green", "Blue" };

    string firstColor = colors.First();
    string lastColor = colors.Last();
    string secondColor = colors.ElementAt(1);
    string defaultColor = colors.FirstOrDefault(c => c.StartsWith("Y")); // 条件に一致しない場合は null

    Console.WriteLine($"最初の色: {firstColor}");
    Console.WriteLine($"最後の色: {lastColor}");
    Console.WriteLine($"2番目の色: {secondColor}");
    Console.WriteLine($"デフォルトの色: {defaultColor ?? "(null)"}");

実行結果


    最初の色: Red
    最後の色: Blue
    2番目の色: Green
    デフォルトの色: (null)

集合操作:

Distinct: 重複する要素を削除します。

Union: 2 つのコレクションの和集合を生成します。

Intersect: 2 つのコレクションの積集合を生成します。

Except: 最初のコレクションにあり、2 番目のコレクションにない要素を返します。


List<int> list1 = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
List<int> list2 = new List<int> { 4, 5, 6, 7 };

var distinctNumbers = list1.Distinct();
var unionNumbers = list1.Union(list2);
var intersectNumbers = list1.Intersect(list2);
var exceptNumbers = list1.Except(list2);

Console.WriteLine("重複なし:string.Join(",",distinctNumbers)");Console.WriteLine("和集合: {string.Join(", ", unionNumbers)}");
Console.WriteLine("積集合:string.Join(",",intersectNumbers)");Console.WriteLine("差集合 (list1 - list2): {string.Join(", ", exceptNumbers)}");

実行結果


重複なし: 1, 2, 3, 4, 5
和集合: 1, 2, 3, 4, 5, 6, 7
積集合: 4, 5
差集合 (list1 - list2): 1, 2, 3

これらの実行結果は、それぞれの LINQ メソッドが期待通りに動作していることを示しています。状況に応じてクエリ構文とメソッド構文を使い分けることで、より可読性の高い、効率的なコードを書くことができます。