C#

最終更新日: 2022.12.21 (公開: 2022.12.20)

C#の「LINQ」は便利機能が満載!メソッドの使い方とメリットを解説

C#の「LINQ」は便利機能が満載!メソッドの使い方とメリットを解説

C#の「LINQ」は、コレクションの要素を操作するための便利機能を集めたものです。配列やリストなどのコレクションを扱うときに、要素の検索やソート(並び替え)、加工などをしたいシーンはよくあります。そのようなときに、自分で新たなメソッドやアルゴリズムを作成していては、バグやエラーが発生するリスクが高まります。

そのようなときにC#のLINQを活用すれば、あらかじめ搭載されている機能を利用して、コレクションのさまざまな加工が可能です。しかしLINQには独特の構文やメソッドがあるため、「使いづらい」「難しい」と感じることも少なくないでしょう。

そこで本記事では、C#のLINQの使い方やメソッドの詳細に加えて、LINQを使うことで得られるメリットについて、サンプルコード付きでわかりやすく解説します。

目次

C#の「LINQ」とは?

C#の「LINQ」とは?

C#の「LINQ」とは、コレクションの要素の検索・並び替え・加工などの機能を集めたものです。LINQを使うことで、コレクションを操作する複雑な処理を、簡潔に記載ができます。たとえば、リストの値の2乗値を表示する操作を、通常のやり方とLINQを活用したもので比較してみましょう。まずは通常のやり方で行うサンプルコードです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic」を使用する
using System.Collections.Generic;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 1から10までの整数値を格納したリストを生成する
      List nums = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

      // 2乗値を格納するためのリストを生成する
      List pows = new List(nums.Count);

      // 各値の2乗値を算出してpowsリストに格納する
      foreach(int num in nums)
      {
        pows.Add(num * num);
      }

      // リスト内の2乗値を画面上に表示する
      pows.ForEach(Console.WriteLine);
    }
  }
}

//実行結果

同様の操作をLINQで行う場合は、以下のようなサンプルコードになります。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 1から10までの整数値を格納したリストを生成する
      List nums = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

      // 2乗値を算出して新たなリストを生成する
      List pows = nums.Select(x => x * x).ToList();

      // リスト内の2乗値を画面上に表示する
      pows.ForEach(Console.WriteLine);
    }
  }
}

先ほどのソースコードと比較して、大幅に行数が減って簡潔になっていることがわかります。このように、C#のLINQを活用すると、高度な処理をより短いコードで実現できます。

C#のLINQを使うために知っておくべき5つの構文・テクニック

C#のLINQを使うために知っておくべき5つの構文・テクニック

C#のLINQを使いこなすためには、C#の応用的な構文やテクニックに関する知識が必要不可欠です。ここでは、とくに重要な以下5つの構文やテクニックについて確認しておきましょう。

  • 型推論
  • 匿名クラス
  • デリゲート
  • 匿名デリゲート
  • ラムダ式

型推論

C#では「var」キーワードによる「型推論」が使えます。以下の構文のように、変数の型名の代わりに「var」キーワードを使用すると、右辺値を評価して自動的に変数の型を決めてくれます。

var 変数名 = 値;

通常のC#でも型推論を使う場面は多いですが、LINQは特殊な型を扱うため、varキーワードを使うとソースコードを簡潔化できるので便利です。C#の型推論の使い方について、以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic」を使用する
using System.Collections.Generic;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 1から3までの整数値を格納したリストを生成する
      var nums = new List { 1, 2, 3 };

      // リスト内の整数値を画面上に表示する
      foreach (var x in nums)
      {
        Console.WriteLine(x);
      }
    }
  }
}

//実行結果

リスト生成時に「var nums = new List { 1, 2, 3 };」と記載していることがポイントです。変数名「num」の前に「var」を置くことで、自動的に右辺側の値をチェックして、変数の型を「List」に決めてくれます。ただし、右辺側の「List」は省略できません。ここを省略すると、そもそも変数の型推論ができなくなるからです。

匿名クラス

C#では、クラスを事前に定義せずに、インスタンス生成と同時にクラスを定義できます。そのときのクラスには名称がなく、「匿名クラス」つまり「名無しのクラス」となります。匿名クラスは通常あまり使用しない機能ですが、LINQではよく使うので必ず覚えておきましょう。匿名クラスの基本的な使い方は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // Fruitクラスは定義せず、匿名クラスとして変数のインスタンスを生成する
      // クラスの型名が存在しないため、varキーワードの使用が必須
      var fruit = new { Name = "高級メロン", Price = 5000 };

      // 匿名クラスの変数の中身を画面上に表示する
      Console.WriteLine(fruit + "\n");

      // Fruitクラスは定義せず、匿名クラスとして配列のインスタンスを生成する
      // クラスの型名が存在しないため、varキーワードの使用が必須
      var fruits = new[]
      {
        new { Name = "りんご", Price = 300 },
        new { Name = "バナナ", Price = 200 },
        new { Name = "ぶどう", Price = 500 },
        new { Name = "メロン", Price = 900 },
        new { Name = "いちご", Price = 600 },
      };

      // 匿名クラスの配列の中身を画面上に表示する
      Array.ForEach(fruits, Console.WriteLine);
    }
  }
}

//実行結果

重要なポイントは、fruits配列変数の生成時に、クラスの定義とインスタンス生成を同時に行っていることです。その際に「new { Name = “高級メロン”, Price = 5000 }」のように、変数名の設定と値の代入を同時に行っています。

匿名クラスは「読み取り専用」なので、インスタンス生成後は値を変更できません。そのため、変数の生成と同時に初期値を設定しないとコンパイルエラーになります。また、匿名クラスはメンバも匿名型になるため、「int」や「string」などの型名を記載してはいけません。

デリゲート

C#の「デリゲート」は、メソッドを参照するための機能です。たとえば「A」という名前のメソッドがあった場合、「X = A」と代入したり、メソッドの引数に「X (A)」のように引き渡したりできます。C#のデリゲートは、C++の「関数ポインタ」に相当する機能です。デリゲートを定義するための構文は以下のとおりです。

delegate 戻り値の型 デリゲート型名(引数リスト);

重要なポイントは、デリゲートではまず「デリゲート型名」を指定することです。さらに、定義したデリゲートは自動的に「System.Delegateクラス」の派生クラスになります。デリゲート型の変数には、 デリゲート型と同じ戻り値と引数を持つメソッドを代入し、そのデリゲートを介して呼び出せます。詳しい流れは以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

namespace Main
{
  class Program
  {
    // 戻り値なし・int型の引数を1つ取るデリゲートを定義
    delegate void TestDelegate(int a);

    public static void Main()
    {
      // デリゲート変数「test」を生成し、同型のMethodを代入する
      TestDelegate test = new TestDelegate(Method);

      // デリゲートを介してMethodを呼び出す
      // つまりMethod(1)が呼び出されるということ
      test(1);
    }

    static void Method(int num)
    {
      // 引数の値をそのまま表示する
      Console.WriteLine(num);
    }
  }
}

//実行結果

上記のサンプルプログラムでは、「TestDelegate」というデリゲートを定義しています。これは、「戻り値なし・int型の引数をひとつ取るメソッド」を格納するためのデリゲートです。その後、通常のクラスと同じようにTestDelegateのインスタンスを生成し、コンストラクタで「Method」を代入しています。あとはtest変数をメソッドのように呼び出せば、自動的にMethodが実行されます。

一見すると複雑なデリゲートですが、実際には単にメソッドの役割を「肩代わり」しているに過ぎません。「デリゲートの呼び出し = 中身のメソッドの呼び出し」です。そもそもDelegateは「代理」を意味する英単語なので、「メソッドの代理を立てるのがデリゲート」だと考えるとわかりやすくなるでしょう。

なお、デリゲートを宣言できるのはメソッドと同じ場所です。デリゲートは内部的にはクラスと同じ扱いなので、メソッド内部でデリゲートを宣言することはできません。

匿名デリゲート

実は先ほどのデリゲートには「省略形」があります。まずは、省略しない通常の書き方を、以下のサンプルコードで再確認しておきましょう。デリゲート変数を宣言し、実際に呼び出したいメソッドを格納し、デリゲートで間接的にメソッドを呼び出すという流れです。なお今回は、C#標準のデリゲート型「Predicate」と「Action」の使用が必要です。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // デリゲート変数「test」を生成し、同型のFindEvensメソッドを代入する
      // ちなみに「Predicate」はC#標準のデリゲート型で、後述のFindAllメソッドの引数型でもある
      Predicate find = new Predicate(FindEvens);

      // int型のリストを生成する
      List nums = new List();

      // 0~100までの整数値を格納する
      for (int i = 0; i < 100; i++)
      {
        nums.Add(i);
      }

      // FindAllメソッドを呼び出し、引数としてfindデリゲート変数を引き渡す
      // FindEvensメソッド内部では、値が偶数であればtrueを返す
      // つまり戻り値のresultには、すべての偶数値が格納されている
      List result = nums.FindAll(find);

      // デリゲート変数「print」を生成し、同型のPrintメソッドを代入する
      // ちなみに「Action」はC#標準のデリゲート型で、後述のForEachメソッドの引数型でもある
      Action print = new Action(Print);

      // resultリストに格納されたすべての値を表示する
      result.ForEach(print);
    }

    static bool FindEvens(int num)
    {
      // 値が偶数値であれば「true」を返す
      return num % 2 == 0;
    }

    static void Print(int num)
    {
      // 画面上に値を表示する
      Console.Write(num + " ");
    }
  }
}

//実行結果

上記のプログラムはさらに省略できます。事前にデリゲート変数を生成することなく、メソッドへの引き渡し時に、直接デリゲートを指定すればOKです。先ほど解説した匿名クラスのように、宣言と代入を同時に行うということです。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // int型のリストを生成する
      List nums = new List();

      // 0~100までの整数値を格納する
      for (int i = 0; i < 100; i++)
      {
        nums.Add(i);
      }

      // FindAllメソッドを呼び出し、同時に偶数かどうかを判定するデリゲートを生成する
      List result = nums.FindAll(delegate (int num)
      {
        // 値が偶数値であれば「true」を返す
        return num % 2 == 0;
      });

      // ForEachメソッドを呼び出し、同時に値を表示するデリゲートを生成する
      result.ForEach(delegate (int num)
      {
        // 画面上に値を表示する
        Console.Write(num + " ");
      });
    }
  }
}

上記のサンプルコードでは、デリゲート変数を宣言する部分と、実際に呼び出すメソッド本体がすっかり消えています。その代わりにメソッドを呼び出すときに、直接デリゲートを生成しています。ただし、このとき生成したデリゲートには名前がないので、1回限りしか使えません。前述した匿名クラス同様に、このようなデリゲートを「匿名デリゲート」と呼びます。

ラムダ式

先ほどの匿名デリゲートを、さらに簡潔に記載できるようにしたのが「ラムダ式」です。ラムダ式はデリゲートと同様に、メソッドをオブジェクト化するための機能で、メソッドの引数にメソッドを引き渡すためにあります。ラムダ式の基本的な構文は以下のとおりです。

(引数名) => {処理内容;}

上記のとおり、ラムダ式はごく単純な構文で記載できます。先ほどの匿名デリゲートから、「delegate」の記述をなくし、代わりに「=>」を挿入するように意識すると、簡単に使えるようになるはずです。実際に、先ほどの匿名デリゲートのサンプルコードを、ラムダ式を使ったものに書き直してみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // int型のリストを生成する
      List nums = new List();

      // 0~100までの整数値を格納する
      for (int i = 0; i < 100; i++)
      {
        nums.Add(i);
      }

      // FindAllメソッドを呼び出し、同時に偶数かどうかを判定するデリゲートを生成する
      List result = nums.FindAll((int num) =>
      {
        // 値が偶数値であれば「true」を返す
        return num % 2 == 0;
      });

      // ForEachメソッドを呼び出し、同時に値を表示するデリゲートを生成する
      result.ForEach((int num) =>
      {
        // 画面上に値を表示する
        Console.Write(num + " ");
      });
    }
  }
}

//実行結果

以上がラムダ式の基本ですが、「あまり便利な感じがしない」のではないでしょうか。実はラムダ式は、さらに省略して書けます。FindAllメソッドの部分に注目し、ここをさらに簡潔化してみましょう。

List result = nums.FindAll((int num) =>
{
  return num % 2 == 0;
});

ラムダ式は引数の型名を常に省略できます。さらに、引数がひとつの場合は丸カッコ「()」も書く必要がありません。

List result = nums.FindAll(num =>
{
  return num % 2 == 0;
});

また内部のコードが1文である場合は、波カッコ「{}」も省略でき、「return」を書く必要もありません。

List result = nums.FindAll(num => num % 2 == 0);

上記がこのラムダ式を最も省略して記載したもの、いわば最終形態となります。ラムダ式はこのように、デリゲートや匿名デリゲートと比べて、飛躍的に簡潔な書き方ができることがメリットです。

このような書き方が認められるのは、冒頭でご紹介した「型推論」が行われているからです。デリゲートやラムダ式を受ける側のメソッドでは、引数や戻り値の種類があらかじめ分かっています。そのため、わざわざラムダ式で型名を指定しなくても、型推論で判定可能です。また、戻り値を返すラムダ式が1文の場合、その1文で値を返すことは明白なので、「return」も書く必要がなくなりました。

以上の知識やテクニックは、これからLINQを使うときに当たり前のように活用するので、事前によく確認しておきましょう。

LINQには「クエリ構文」と「メソッド構文」がある

LINQには「クエリ構文」と「メソッド構文」がある

LINQには「クエリ構文(クエリ式)」と「メソッド構文」があります。クエリ構文とは、「SQL」に代表されるデータベース言語に近い構文で、C#とSQLを組み合わせる際に便利です。しかし、C#学習者にとってはわかりづらいのが難点。一方でメソッド構文は、通常のC#の構文に近い形でLINQの機能が使えます。両者の違いを以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq名前空間」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 5つの整数値を格納するリストを生成する
var list = new List { 1, 3, 5, 2, 4 };

// 「クエリ構文(クエリ式)」で偶数値の3乗値を算出する
var query = from x in list
where x % 2 == 0
orderby x
select x * x * x;

// 結果を表示する
foreach (int num in query)
{
Console.WriteLine(num);
}

// 「メソッド構文(メソッド式)」で偶数値の3乗値を算出する
var method = list
.Where(x => x % 2 == 0)
.OrderBy(x => x)
.Select(x => x * x * x);

// 結果を表示する
foreach (int num in method)
{
Console.WriteLine(num);
}
}
}
}

//実行結果

上記のように、クエリ構文(クエリ式)はC#とは書き方がまったく異なるので、わかりづらいのではないでしょうか。メソッド構文のほうがわかりやすく、使える機能も多いため、本記事ではメソッド構文について解説していきます。

C#のLINQの基本的なメソッドと使い方

C#のLINQの基本的なメソッドと使い方

C#のLINQを習得するうえでとくに重要な、以下6つの基本的なメソッドについて解説します。

  • Selectメソッド|条件を指定して要素を処理する
  • Whereメソッド|条件指定で処理する要素を絞り込む
  • SelectManyメソッド|コレクションの要素を「平坦化」する
  • GroupByメソッド|コレクションの要素をグループ分類する
  • FirstOrDefaultメソッド|最初の要素を取得する
  • Distinctメソッド|重複する要素を除外する

Selectメソッド|条件を指定して要素を処理する

「Selectメソッド」は、コレクションの要素すべてに同じ処理を行い、別のオブジェクトにその結果を格納できます。詳細は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 10個の整数値を格納するリストを生成する
      var list = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

      // すべての要素に対して、5で割った余りの値を算出して、その結果を格納する
      var resultA = list.Select(x => x % 5);

      // 結果を表示する
      foreach (var num in resultA)
      {
        Console.WriteLine(num);
      }

      Console.WriteLine();

      // すべての要素に対して、偶数であるかどうかを調べて、その結果を格納する
      var resultB = list.Select(x => x % 2 == 0);

      // 結果を表示する
      foreach (var num in resultB)
      {
        Console.WriteLine(num);
      }
    }
  }
}

//実行結果

上記はSelectメソッドの引数として、2パターンのラムダ式を渡しています。Selectメソッドには、ひとつの引数とbool型の戻り値を持つラムダ式が必要です。

なお、LINQのメソッドでは、基本的に内部でforループが実行され、コレクション内のすべての要素にアクセスします。ラムダ式の引数は、forループで逐一アクセスされるリストの要素を指し、ラムダ式内部で条件判定や値の設定や変更などを行います。

Selectメソッドの場合は、ラムダ式内部では値を加工する式を指定します。たとえば「x => x % 5」であれば、「xを5で割った余り」が格納されます。

Whereメソッド|条件指定で処理する要素を絞り込む

「Whereメソッド」は、条件に合致する要素を絞り込めます。先ほどのSelectメソッドは、すべての要素に対して一律に処理を行いますが、Whereメソッドで条件を指定することで、特定の要素のみ処理できます。詳細は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 10個の整数値を格納するリストを生成する
      var list = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

      // 3より大きく7より小さい要素に対して、5で割った余りの値を算出して、その結果を格納する
      var resultA = list
                   .Where(x => x > 3 && x < 7) // 3より大きく7より小さい値に絞り込む .Select(x => x % 5); // 5で割った余りの値を算出する

      // 結果を表示する
      foreach (var num in resultA)
      {
        Console.WriteLine(num);
      }

      Console.WriteLine();

      // 3より大きく7より小さい要素に対して、偶数であるかどうかを調べて、その結果を格納する
      var resultB = list
                   .Where(x => x > 3 && x < 7) // 3より大きく7より小さい値に絞り込む .Select(x => x % 2 == 0); // 偶数であるかどうかを調べる

      // 結果を表示する
      foreach (var num in resultB)
      {
        Console.WriteLine(num);
      }
    }
  }
}

//実行結果

上記のサンプルコードでは、まずWhereメソッドで「3より大きく7より小さい値」に絞り込み、そのうえでSelectメソッドで「5で割った余りの値」を算出しています。次の項目では、Selectメソッドで「偶数であるか」を判定し、その結果を真偽値(「True」もしくは「False」)で格納しています。

SelectManyメソッド|コレクションの要素を「平坦化」する

「SelectManyメソッド」は、コレクション内の任意の要素をひとつのシーケンスに「平坦化」できます。具体的には、クラス内の複数の要素から、ひとつの項目だけを抽出して新たなリストを作ることが可能です。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  // 四半期クラス
  class Quarter
  {
    // 四半期の名称
    public string period { get; set; }

    // 四半期に含まれる月
    public List months { get; set; }
  }

  class Program
  {
    public static void Main()
    {
      // 四半期の情報を設定する
      Quarter[] quarter =
      {
        new Quarter{period = "第1四半期", months = new List{ "1月", "2月", "3月" } },
        new Quarter{period = "第2四半期", months = new List{ "4月", "5月", "6月" } },
        new Quarter{period = "第3四半期", months = new List{ "7月", "8月", "9月" } },
        new Quarter{period = "第4四半期", months = new List{ "10月", "11月", "12月" } },
      };

      // 四半期の情報から「月」だけを抽出する
      var months = quarter.SelectMany(x => x.months);

      // 結果を表示する
      foreach (var month in months)
      {
        Console.WriteLine(month);
      }
    }
  }
}

//実行結果

上記のサンプルプログラムでは、まず四半期の分類と該当する時期をクラスにまとめています。そのうえで、SelectManyメソッドで「months」の要素だけを抽出して、新たなリストを作成するという流れです。

GroupByメソッド|コレクションの要素をグループ分類する

「GroupByメソッド」は、コレクションのある要素を基準として、コレクションを再構成できます。たとえばクラス内に「生徒名」「教科」「得点」の要素がある場合、教科を基準にGroupByメソッドを実行すると、教科ごとに生徒名と得点をまとめられます。詳細は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  // 試験結果をまとめるクラス
  class Test
  {
    // 生徒名
    public string studentName { get; set; }

    // クラス名
    public string className { get; set; }

    // 教科
    public string subject { get; set; }

    // 点数
    public int score { get; set; }
  }

  class Program
  {
    public static void Main()
    {
      // 試験結果のデータを設定する
      var result = new List()
      {
        new Test { studentName = "山田 一郎", className = "A", subject = "国語", score = 50 },
        new Test { studentName = "山田 一郎", className = "A", subject = "数学", score = 95 },
        new Test { studentName = "山田 一郎", className = "A", subject = "英語", score = 75 },
        new Test { studentName = "山田 一郎", className = "A", subject = "物理", score = 60 },
        new Test { studentName = "山田 一郎", className = "A", subject = "化学", score = 80 },

        new Test { studentName = "田中 二郎", className = "A", subject = "国語", score = 90 },
        new Test { studentName = "田中 二郎", className = "A", subject = "数学", score = 30 },
        new Test { studentName = "田中 二郎", className = "A", subject = "英語", score = 50 },
        new Test { studentName = "田中 二郎", className = "A", subject = "物理", score = 80 },
        new Test { studentName = "田中 二郎", className = "A", subject = "化学", score = 95 },

        new Test { studentName = "斎藤 三郎", className = "B", subject = "国語", score = 55 },
        new Test { studentName = "斎藤 三郎", className = "B", subject = "数学", score = 75 },
        new Test { studentName = "斎藤 三郎", className = "B", subject = "英語", score = 90 },
        new Test { studentName = "斎藤 三郎", className = "B", subject = "物理", score = 70 },
        new Test { studentName = "斎藤 三郎", className = "B", subject = "化学", score = 85 },

        new Test { studentName = "吉田 四郎", className = "B", subject = "国語", score = 50 },
        new Test { studentName = "吉田 四郎", className = "B", subject = "数学", score = 95 },
        new Test { studentName = "吉田 四郎", className = "B", subject = "英語", score = 60 },
        new Test { studentName = "吉田 四郎", className = "B", subject = "物理", score = 35 },
        new Test { studentName = "吉田 四郎", className = "B", subject = "化学", score = 70 },
      };

      // 「教科」を基準にクラスを再構成する
      var subjects = result.GroupBy(x => x.subject);

      // 教科ごとにデータを表示する
      foreach (var subject in subjects)
      {
        // 教科名を表示する
        Console.WriteLine("<{0}>", subject.Key);

        // 各データをすべて表示する
        foreach (var item in subject)
        {
          Console.WriteLine("{0}({1}組):{2}点", item.studentName, item.className, item.score);
        }

        // 改行する
        Console.WriteLine();
      }
    }
  }
}

//実行結果

上記のサンプルプログラムでは、「生徒名」ごとにまとめてあるデータソースを、「教科」ごとにまとめて再構成しています。さまざまな情報をクラスに集約したあとで、「特定のデータを中心に検証したい」シーンはよくあります。通常のC#の機能でそれを実現するのは大変ですが、LINQならGroupByメソッドひとつで実現可能です。

FirstOrDefaultメソッド|最初の要素を取得する

「FirstOrDefaultメソッド」は、条件に合致するコレクションの要素群のうち、最初のものを取得できます。詳細は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  // 人物に関するデータをまとめるクラス
  class Data
  {
    // 名前
    public string name { get; set; }

    // ID
    public int ID { get; set; }

    // 身長
    public float height { get; set; }

    // 体重
    public float weight { get; set; }
  }

  class Program
  {
    public static void Main()
    {
      // 人物のデータを設定する
      var data = new List()
      {
        new Data { name = "A", ID = 1, height = 170.5f, weight = 50.5f },
        new Data { name = "B", ID = 2, height = 175.0f, weight = 60.0f },
        new Data { name = "C", ID = 3, height = 165.5f, weight = 50.5f },
        new Data { name = "D", ID = 4, height = 175.0f, weight = 65.0f },
        new Data { name = "E", ID = 5, height = 185.0f, weight = 85.0f },
      };

      // 身長175.0cmの人物を抽出し、その最初のデータのみを取得する
      var result = data
                  .Where(item => item.height == 175.0f) // 身長175.0cmの人物を絞り込む
                  .FirstOrDefault(); // 該当する最初の人物のデータのみ取得する

      // 該当する人物のデータを表示する
      Console.WriteLine("名前={0}, ID={1}, 身長={2}, 体重={3}", result.name, result.ID, result.height, result.weight);
    }
  }
}

//実行結果

上記のサンプルプログラムの場合、身長175.0cmに該当するのは「B」さんと「D」さんの2人です。しかし実際に表示されるのは、データの出現位置が早いBさんのものになります。

Distinctメソッド|重複する要素を除外する

「Distinctメソッド」は、コレクションの重複する要素を除外できます。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;
using System.Xml.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 適当な整数値をリストに格納する
      var list = new List { 1, 0, 3, 7, 110, -56, 83, 7, 565, 0 };

      // リストの値を画面上に表示する
      foreach (var x in list)
      {
        Console.Write(x + " ");
      }
      Console.WriteLine();

      // Distinctメソッドで重複要素を取り除く
      var result = list.Distinct();

      // リストの値を画面上に表示する
      foreach (var x in result)
      {
        Console.Write(x + " ");
      }
      Console.WriteLine();
    }
  }
}

//実行結果

上記のように、Distinctメソッドを使用すると、コレクション内の重複要素がすべて取り除かれます。データをどんどん格納していくと、重複も増えていくものですが、重複を避けたい場合はDistinctメソッドを活用しましょう。

集計演算子

集計演算子

LINQには、集めたデータを任意の観点から集計するための、「集計演算子」も備わっています。ここでは、LINQで使える以下5つの集計メソッドについて見ていきましょう。

  • Maxメソッド|最大値を取得する
  • Minメソッド|最小値を取得する
  • Averageメソッド|平均値を取得する
  • Sumメソッド|合計値を取得する
  • Countメソッド|要素数を取得する

Maxメソッド|最大値を取得する

「Maxメソッド」は、コレクション内の最大値を取得できます。基本的な使い方は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 適当な整数値を配列に格納する
      var nums = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

      // 配列内の値から最大値を取得して表示する
      Console.WriteLine(nums.Max());

      // 適当な文字列を配列に格納する
      var strs = new[] { "AAA", "BBB", "CCC", "DDD", "EEE" };

      // 配列内の値から最大値を取得して表示する
      Console.WriteLine(strs.Max());
    }
  }
}

//実行結果

コレクションに含まれるメソッド「Max」を呼び出すだけで、最大値を取得できます。なお文字列型の場合は、辞書式で最後に登場するものが最大値です。

Minメソッド|最小値を取得する

「Mixメソッド」は、コレクション内の最小値を取得できます。基本的な使い方は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 適当な整数値を配列に格納する
      var nums = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

      // 配列内の値から最小値を取得して表示する
      Console.WriteLine(nums.Min());

      // 適当な文字列を配列に格納する
      var strs = new[] { "AAA", "BBB", "CCC", "DDD", "EEE" };

      // 配列内の値から最小値を取得して表示する
      Console.WriteLine(strs.Min());
    }
  }
}

//実行結果

Maxメソッドと同じように、Minメソッドを呼び出すだけでOKです。文字列の場合は、辞書式で最後に登場するものが最小値となります。

Averageメソッド|平均値を取得する

「Averageメソッド」は、コレクション内のすべての値の平均値を取得できます。基本的な使い方は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 適当な浮動小数点値を配列に格納する
      var nums = new[] { 111,1f, 50.3f, 203.9f, 68.7f, 193.2f };

      // 配列内の値から平均値を取得して表示する
      Console.WriteLine(nums.Average());
    }
  }
}

//実行結果

コレクションの「Averageメソッド」を呼び出せばいいでしょう。ただし、文字列を含むコレクションに対しては、Averageメソッドは使えないので注意が必要です。

Sumメソッド|合計値を取得する

「Sumメソッド」は、コレクション内のすべての値の合計値を取得できます。基本的な使い方は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な浮動小数点値を配列に格納する
var nums = new[] { 111,1f, 50.3f, 203.9f, 68.7f, 193.2f };

// 配列内の値の合計値を算出して表示する
Console.WriteLine(nums.Sum());
}
}
}

//実行結果

これまでと同じように、コレクションの「Sumメソッド」を呼び出すだけで使えます。ただし、文字列を含むコレクションにはSumメソッドは使えません。

Countメソッド|要素数を取得する

「Countメソッド」は、コレクション内の最大値を取得できます。基本的な使い方は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な整数値を配列に格納する
var nums = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// 配列内の要素数を表示する
Console.WriteLine(nums.Count());
}
}
}

//実行結果

Countメソッドを呼び出せば、コレクションの要素数がわかります。ただし、ほとんどの場合は配列なら「Lengthプロパティ」、リストなどのコレクションなら「Countプロパティ」が使えるので、LINQのCountメソッドを単体で使用することは少ないでしょう。

LINQの集計メソッドの応用

LINQの集計メソッドの応用

先ほどの得点データのサンプルプログラムで、実際にさまざまな集計を行ってみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
// 試験結果をまとめるクラス
class Test
{
// 生徒名
public string studentName { get; set; }

// クラス名
public string className { get; set; }

// 教科
public string subject { get; set; }

// 点数
public int score { get; set; }
}

class Program
{
public static void Main()
{
// 試験結果のデータを設定する
var result = new List()
{
new Test { studentName = "山田 一郎", className = "A", subject = "国語", score = 50 },
new Test { studentName = "山田 一郎", className = "A", subject = "数学", score = 95 },
new Test { studentName = "山田 一郎", className = "A", subject = "英語", score = 75 },
new Test { studentName = "山田 一郎", className = "A", subject = "物理", score = 60 },
new Test { studentName = "山田 一郎", className = "A", subject = "化学", score = 80 },

new Test { studentName = "田中 二郎", className = "A", subject = "国語", score = 90 },
new Test { studentName = "田中 二郎", className = "A", subject = "数学", score = 30 },
new Test { studentName = "田中 二郎", className = "A", subject = "英語", score = 50 },
new Test { studentName = "田中 二郎", className = "A", subject = "物理", score = 80 },
new Test { studentName = "田中 二郎", className = "A", subject = "化学", score = 95 },

new Test { studentName = "斎藤 三郎", className = "B", subject = "国語", score = 55 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "数学", score = 75 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "英語", score = 90 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "物理", score = 70 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "化学", score = 85 },

new Test { studentName = "吉田 四郎", className = "B", subject = "国語", score = 50 },
new Test { studentName = "吉田 四郎", className = "B", subject = "数学", score = 95 },
new Test { studentName = "吉田 四郎", className = "B", subject = "英語", score = 60 },
new Test { studentName = "吉田 四郎", className = "B", subject = "物理", score = 35 },
new Test { studentName = "吉田 四郎", className = "B", subject = "化学", score = 70 },
};

// 「教科」を基準としてグループ化したうえで、教科名と各種集計結果をまとめる
var subjects = result
.GroupBy(x => x.subject) // 「教科」を基準にグループ化する
.Select(x => new { subject = x.Key, // 教科名
max = x.Max(y => y.score), // 最高得点
min = x.Min(y => y.score), // 最低得点
average = x.Average(y => y.score), // 平均得点
sum = x.Sum(y => y.score) }); // 合計得点

// 最高得点の情報を表示する
foreach (var data in subjects)
{
Console.WriteLine("<{0}>", data.subject);
Console.WriteLine("最高得点:{0}", data.max);
Console.WriteLine("最低得点:{0}", data.min);
Console.WriteLine("平均得点:{0}", data.average);
Console.WriteLine("合計得点:{0}", data.sum);
}
}
}
}

//実行結果

「GroupByメソッド」を使用し、教科を基準にグループ化するのはこれまでと同じです。重要なポイントは、Selectメソッドを使って教科ごとの最高・最低・平均・合計得点をまとめている部分です。冒頭でご紹介した「匿名クラス」を活用し、新たなクラスとして集計結果をまとめています。

LINQでコレクションの要素が条件を満たすかチェック

LINQでコレクションの要素が条件を満たすかチェック

C#のLINQでは、コレクションの各要素が「任意の条件を満たしているか」も簡単にチェックできます。ここでは、以下4つのメソッドやテクニックについて解説します。

  • Anyメソッド|条件を満たす要素がひとつでもあるか調べる
  • Allメソッド|すべての要素が条件を満たすか調べる
  • Containsメソッド|指定した要素を含んでいるか調べる
  • SequenceEqualメソッド|2つのシーケンスが等しいか調べる

Anyメソッド|条件を満たす要素がひとつでもあるか調べる

「Anyメソッド」は、コレクションの要素のうち、条件を満たす要素がひとつ以上あるかを調べられます。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 適当な整数値をリストに格納する
      var list = new List { 1, 3, 7, 110, -56, 83, 565, 0 };

      // 画面上にリストの数値を表示する
      list.ForEach(x => Console.Write(x + " "));
      Console.WriteLine();

      // リストのいずれかの要素が100未満かどうか
      Console.WriteLine("いずれかの要素が100未満:" + list.Any(x => x < 100));

      // リストのいずれかの要素が1000以上かどうか
      Console.WriteLine("いずれかの要素が1000以上:" + list.Any(x => x >= 1000));

      // リストのいずれかの要素が3の倍数かどうか
      Console.WriteLine("いずれかの要素が3の倍数:" + list.Any(x => x % 3 == 0));
    }
  }
}

//実行結果

上記のサンプルプログラムでは、適当な整数値を格納したコレクションに、特定の条件を満たすものが含まれているか調べています。Anyメソッドの引数に、条件を指定するラムダ式を渡すだけでOKです。ラムダ式はbool値を返す必要があるので、内部には条件式だけ記載することがポイントです。Anyメソッドの戻り値もbool型なので、結果はTrueもしくはFalseで表示されます。

Allメソッド|すべての要素が条件を満たすか調べる

「Allメソッド」は、コレクションの要素すべてが条件を満たすかを調べられます。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
  class Program
  {
    public static void Main()
    {
      // 適当な整数値をリストに格納する
      var list = new List { 1, 3, 7, 110, -56, 83, 565, 0 };

      // 画面上にリストの数値を表示する
      list.ForEach(x => Console.Write(x + " "));
      Console.WriteLine();

      // リストのすべての要素が100未満かどうか
      Console.WriteLine("すべての要素が100未満:" + list.All(x => x < 100));

      // リストのすべての要素が-100以上かどうか
      Console.WriteLine("すべての要素が-100以上:" + list.All(x => x >= -100));

      // リストのすべての要素が3の倍数かどうか
      Console.WriteLine("すべての要素が3の倍数:" + list.All(x => x % 3 == 0));
    }
  }
}

//実行結果

基本的な使い方はAnyメソッドと同じです。Allメソッドに渡すラムダ式の内部で、判定したい条件を指定すれば、その答えがBool値で格納されます。

Containsメソッド|指定した要素を含んでいるか調べる

「Containsメソッド」は、指定した要素がコレクションに含まれるかを調べられます。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な整数値を配列に格納する
var nums = new[] { 1, 3, 7, 110, -56, 83, 565, 0 };

// 画面上に配列の数値を表示する
foreach (var i in nums)
{
Console.Write(i + " ");
}
Console.WriteLine();

// 配列の要素に「110」があるか
Console.WriteLine("リストの要素に「110」があるか:" + nums.Contains(110));

// 配列の要素に「-55」があるか
Console.WriteLine("リストの要素に「-55」があるか:" + nums.Contains(-55));

// 配列の要素に「0」があるか
Console.WriteLine("リストの要素に「0」があるか:" + nums.Contains(0));
}
}
}

//実行結果

Containsメソッドの引数として、「検索したい要素の値」をダイレクトに指定することが、AnyメソッドやAllメソッドとの違いです。

SequenceEqualメソッド|2つのシーケンスが等しいか調べる

「SequenceEqualメソッド」は、2つのシーケンスの要素が完全に一致するか調べられます。詳細を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な整数値を配列に格納する
var numsA = new[] { 1, 3, 7, 110, -56, 83, 565, 0 };
var numsB = new[] { 1, 3, 7, 110, -56, 83, 565, 0 };

// 画面上に配列の数値を表示する
foreach (var i in numsA)
{
Console.Write(i + " ");
}
Console.WriteLine();

foreach (var i in numsB)
{
Console.Write(i + " ");
}
Console.WriteLine();

// 2つのシーケンスが一致するか
Console.WriteLine("2つのシーケンスが一致するか:" + numsA.SequenceEqual(numsB));

// 適当な文字列を配列に格納する
var strsA = new[] { "AAA", "BBB", "CCC" };
var strsB = new[] { "AaA", "BbB", "CcC" };

// 画面上に配列の文字列を表示する
foreach (var s in strsA)
{
Console.Write(s + " ");
}
Console.WriteLine();

foreach (var s in strsB)
{
Console.Write(s + " ");
}
Console.WriteLine();

// 2つのシーケンスが一致するか
Console.WriteLine("2つのシーケンスが一致するか:" + strsA.SequenceEqual(strsB));
}
}
}

//実行結果

上記のように、コレクション内の要素が文字列でも、SequenceEqualメソッドで簡単に判定できます。ひとつの要素でも異なる場合は、一致判定とはなりません。

LINQの判定メソッド(Any・All・Contains)の応用

LINQの判定メソッド(Any・All・Contains)の応用

先ほどの得点データのサンプルプログラムで、さまざまなパターンの条件判定を行ってみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
// 試験結果をまとめるクラス
class Test
{
// 生徒名
public string studentName { get; set; }

// クラス名
public string className { get; set; }

// 教科
public string subject { get; set; }

// 点数
public int score { get; set; }
}

class Program
{
public static void Main()
{
// 試験結果のデータを設定する
var result = new List()
{
new Test { studentName = "山田 一郎", className = "A", subject = "国語", score = 50 },
new Test { studentName = "山田 一郎", className = "A", subject = "数学", score = 95 },
new Test { studentName = "山田 一郎", className = "A", subject = "英語", score = 75 },
new Test { studentName = "山田 一郎", className = "A", subject = "物理", score = 60 },
new Test { studentName = "山田 一郎", className = "A", subject = "化学", score = 80 },

new Test { studentName = "田中 二郎", className = "A", subject = "国語", score = 90 },
new Test { studentName = "田中 二郎", className = "A", subject = "数学", score = 30 },
new Test { studentName = "田中 二郎", className = "A", subject = "英語", score = 50 },
new Test { studentName = "田中 二郎", className = "A", subject = "物理", score = 80 },
new Test { studentName = "田中 二郎", className = "A", subject = "化学", score = 95 },

new Test { studentName = "斎藤 三郎", className = "B", subject = "国語", score = 55 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "数学", score = 75 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "英語", score = 100 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "物理", score = 70 },
new Test { studentName = "斎藤 三郎", className = "B", subject = "化学", score = 85 },

new Test { studentName = "吉田 四郎", className = "B", subject = "国語", score = 50 },
new Test { studentName = "吉田 四郎", className = "B", subject = "数学", score = 100 },
new Test { studentName = "吉田 四郎", className = "B", subject = "英語", score = 60 },
new Test { studentName = "吉田 四郎", className = "B", subject = "物理", score = 35 },
new Test { studentName = "吉田 四郎", className = "B", subject = "化学", score = 70 },
};

// 「教科」を基準としてグループ化したうえで、教科名と各種集計結果をまとめる
var subjects = result
.GroupBy(x => x.subject) // 「教科」を基準にグループ化する
.Select(x => new { subject = x.Key, // 教科名
anyPerfect = x.Any(y => y.score == 100), // 満点はいるか
anyFailed = x.Any(y => y.score < 30), // 赤点(30点未満)はいるか allPass = x.All(y => y.score >= 50) }); // 全員及第点(50点以上)か

// 集計結果の情報を表示する
foreach (var data in subjects)
{
Console.WriteLine("<{0}>", data.subject);
Console.WriteLine("満点はいるか:{0}", data.anyPerfect);
Console.WriteLine("赤点(30点未満)はいるか:{0}", data.anyFailed);
Console.WriteLine("全員及第点(50点以上)か:{0}", data.allPass);
Console.WriteLine();
}
}
}
}

//実行結果

基本的にはこれまでご紹介した内容と変わりません。Selectメソッドを用いて、コレクション内の全要素に対して、AnyメソッドやAllメソッドなどで条件を指定すると、細かな観点からデータソースを整理できます。

LINQでコレクション同士の「集合」を判定する

LINQでコレクション同士の「集合」を判定する

LINQの重要な機能のひとつが、コレクション同士の「集合」を判定し、条件を満たす要素を整理できることです。ここでは、LINQでとくに利用することが多い、以下3つのメソッドについて解説します。

  • Unionメソッド|和集合
  • Exceptメソッド|差集合
  • Intersectメソッド|積集合

Unionメソッド|和集合

「Unionメソッド」は、「和集合」つまり2つのコレクションに含まれる「すべての要素」を集めるものです。たとえば以下のような2つの配列がある場合、arrayAとarrayBの和集合は「1, 3, 5, 2, 4, 6」となります。

var arrayA = new List { 1, 3, 5 };
var arrayB = new List { 2, 4, 6 };

2つの配列のすべての要素をまとめたものが和集合となるので、すべての要素を漏らすことなく網羅できます。ただし重複する要素がある場合は、すべての重複が取り除かれた状態になるので注意が必要です。たとえば「 1, 3, 3, 5」と「2, 3, 5, 7」の和集合を取ると、「1, 3, 5, 2, 7」となります。実際に以下のサンプルコードで和集合を活用してみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 2つの配列に整数値を格納する
var arrayA = new List { 1, 3, 7, 11, 13, 19 };
var arrayB = new List { 2, 3, 5, 7, 12, 19 };

// 2つの配列の値を画面上に表示する
foreach (var x in arrayA)
{
Console.Write(x + " ");
}
Console.WriteLine();

foreach (var x in arrayB)
{
Console.Write(x + " ");
}
Console.WriteLine();

// 2つの配列の和集合を取得する
var union = arrayA.Union(arrayB);

// 和集合の結果を画面上に表示する
Console.WriteLine("2つの配列の和集合");
foreach (var x in union)
{
Console.Write(x + " ");
}
}
}
}

//実行結果

上記のように、Unionメソッドを活用すると、2つの配列の要素を重複なしでまとめられます。

Exceptメソッド|差集合
「Exceptメソッド」は、「差集合」つまりひとつのコレクションから、もうひとつのコレクションの要素を取り除くためのものです。たとえば以下のような2つの配列がある場合、arrayAとarrayBの差集合は「2, 4」となります。

var arrayA = new List { 1, 3, 5 };
var arrayB = new List { 2, 3, 4 };

arrayAからarrayBの要素を取り除いたものが、arrayAとarrayBの差集合なので、「1, 3, 5」から「2, 3, 4」を除いたものが答えになります。

ただし、arrayBとarrayAの差集合を取る場合は、「2, 3, 4」から「1, 3, 5」を除くことになるため、答えは「2, 4」となることに注意が必要です。実際に以下のサンプルコードで差集合を活用してみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 2つの配列に整数値を格納する
var arrayA = new List { 1, 3, 7, 11, 13, 19 };
var arrayB = new List { 2, 3, 5, 7, 12, 19 };

// 2つの配列の値を画面上に表示する
foreach (var x in arrayA)
{
Console.Write(x + " ");
}
Console.WriteLine();

foreach (var x in arrayB)
{
Console.Write(x + " ");
}
Console.WriteLine();

// 2つの配列の差集合を取得する
var union = arrayA.Except(arrayB);

// 和集合の結果を画面上に表示する
Console.WriteLine("2つの配列の差集合");
foreach (var x in union)
{
Console.Write(x + " ");
}
}
}
}

//実行結果

上記のサンプルプログラムでは、「1, 3, 7, 11, 13, 19」から「2, 3, 5, 7, 12, 19」を取り除くので、差集合は「1, 11, 19」となります。メソッドの左辺と右辺を逆にすると、まったく異なる結果になるので注意が必要です。

Intersectメソッド|積集合

「Intersectメソッド」は、「積集合」つまり2つのコレクションの「どちらにも含まれる」要素を抽出するためのものです。たとえば以下のような2つの配列がある場合、arrayAとarrayBの差集合は「3」となります。

var arrayA = new List { 1, 3, 5 };
var arrayB = new List { 2, 3, 4 };

arrayA「1, 3, 5」とarrayB「2, 3, 4」のどちらにも含まれるのは「3」のみです。なお、両者のどちらにも含まれる要素がない場合は、Intersectメソッドで求める積集合の中身は空となります。実際に以下のサンプルコードで積集合を活用してみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 2つの配列に整数値を格納する
var arrayA = new List { 1, 3, 7, 11, 13, 19 };
var arrayB = new List { 2, 3, 5, 7, 12, 19 };

// 2つの配列の値を画面上に表示する
foreach (var x in arrayA)
{
Console.Write(x + " ");
}
Console.WriteLine();

foreach (var x in arrayB)
{
Console.Write(x + " ");
}
Console.WriteLine();

// 2つの配列の積集合を取得する
var union = arrayA.Intersect(arrayB);

// 積集合の結果を画面上に表示する
Console.WriteLine("2つの配列の積集合");
foreach (var x in union)
{
Console.Write(x + " ");
}
}
}
}

//実行結果

上記のサンプルプログラムでは、「1, 3, 7, 11, 13, 19」と「2, 3, 5, 7, 12, 19」のどちらにも含まれる要素、つまり積集合は「3, 7, 19」となります。

LINQの「OrderByメソッド」でコレクションをソート

LINQの「OrderByメソッド」でコレクションをソート

LINQでとくに便利な機能が、さまざまなコレクションを簡単に並び替えることができる「ソート」機能です。ここでは、以下3つのソートテクニックを解説します。

  • 昇順ソート
  • 降順ソート
  • 逆順に並べ替える

昇順ソート

コレクションをLINQで「昇順(小さい順)」にソートしたい場合は、「OrderByメソッド」を使います。OrderByメソッドの構文は以下のとおりです。

var ソート済みコレクション名 = ソート元コレクション名.OrderBy(x => x.ソートするキー);

OrderByメソッドの引数には、これまで同様にラムダ式を引き渡しますが、そこで「ソートするキー」を指定します。たとえば、クラスの「price」の値でソートするのであれば、「OrderBy(x => x.price);」とします。OrderByメソッドの使い方を以下のサンプルコードで確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 果物の名前と価格を設定する(匿名クラスを活用)
var fruits = new[]
{
new { name = "りんご", price = 300 },
new { name = "ぶどう", price = 500 },
new { name = "いちご", price = 700 },
new { name = "バナナ", price = 200 },
new { name = "メロン", price = 2000 },
new { name = "もも", price = 1000 },
new { name = "パイナップル", price = 800 },
};

// 「price」の値を基準として、fruitsの要素を昇順(小さい順)にソートする
var sorted = fruits.OrderBy(x => x.price);

// ソート後のデータを表示する
foreach (var x in sorted)
{
Console.WriteLine(x);
}
}
}
}

//実行結果

上記のサンプルプログラムのように、OrderByメソッドを使えば自作クラスでも、任意のキーで簡単に昇順ソートができます。

降順ソート

コレクションをLINQで「降順(大きい順)」にソートしたい場合は、「OrderByDescendingメソッド」を使います。OrderByDescendingメソッドの構文は以下のとおりです。

var ソート済みコレクション名 = ソート元コレクション名.OrderByDescending(x => x.ソートするキー);

OrderByDescendingメソッドの引数は、先ほどのOrderByと同様の「キー」を指定するラムダ式です。これだけで以下のサンプルコードのように、コレクションを降順ソートできます。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 果物の名前と価格を設定する(匿名クラスを活用)
var fruits = new[]
{
new { name = "りんご", price = 300 },
new { name = "ぶどう", price = 500 },
new { name = "いちご", price = 700 },
new { name = "バナナ", price = 200 },
new { name = "メロン", price = 2000 },
new { name = "もも", price = 1000 },
new { name = "パイナップル", price = 800 },
};

// 「price」の値を基準として、fruitsの要素を降順(大きい順)にソートする
var sorted = fruits.OrderByDescending(x => x.price);

// ソート後のデータを表示する
foreach (var x in sorted)
{
Console.WriteLine(x);
}
}
}
}

//実行結果

上記のサンプルプログラムのように、並び替えのキーさえ正しく指定すれば、さまざまな要素を含む自作クラスも簡単にソートできます。

逆順に並べ替える

コレクションをLINQで逆順に並び替えたいときは、「Reverseメソッド」を使います。Reverseメソッドは引数を取らないので、以下のサンプルコードのようにそのまま呼び出すだけでOKです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 果物の名前と価格を設定する(匿名クラスを活用)
var fruits = new[]
{
new { name = "りんご", price = 300 },
new { name = "ぶどう", price = 500 },
new { name = "いちご", price = 700 },
new { name = "バナナ", price = 200 },
new { name = "メロン", price = 2000 },
new { name = "もも", price = 1000 },
new { name = "パイナップル", price = 800 },
};

// 「price」の値を基準として、fruitsの要素を逆順に並べ替える
var reverse = fruits.Reverse();

// 並び替え後のデータを表示する
foreach (var x in reverse)
{
Console.WriteLine(x);
}
}
}
}

//実行結果

上記のサンプルプログラムのように、Reverseメソッドを使用すると、クラスのメンバ数に関係なく簡単に逆順へと並び替えられます。たとえば、まず降順にソートしてからデータを使用し、そのあと別の用途で昇順のデータが必要な場合に、Reverseメソッドを使用すると便利です。

LINQ to XMLでHTMLやXMLドキュメントを解析する

LINQ to XMLでHTMLやXMLドキュメントを解析する

C#では「LINQ to XML」という機能を使用し、HTMLやXMLドキュメントなどを簡単に解析できます。以下のサンプルコードで詳細を確認しましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

// 「System.Xml.Linq」を使用する
using System.Xml.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// XMLドキュメントを作成する
string xml = @"

山田 一郎
田中 二郎
斎藤 三郎
吉田 四郎
";

// XMLドキュメントを解析する
var doc = XElement.Parse(xml);

// XMLネームスペースを指定する
XNamespace name = "https://www.test.com";

// XMLドキュメントを解析し、class属性が「B」で始まるnameタグの内容を取得する
var query = from n in doc.Descendants(name + "name") // nameタグの場所まで進める
where n.Attribute("class").Value.StartsWith("B") // class属性が「B」の部分を指定する
select n; // 該当する部分のデータを抽出する

// XMLドキュメントから抽出したデータを表示する
foreach (var group in query)
{
Console.WriteLine(group.Value);
}
}
}
}

//実行結果

HTMLやXMLを使用する場合は、上記のサンプルプログラムのように「LINQ to XML」を活用すると、ドキュメントを効率的に操作できます。

LINQは「メソッドチェーン」で各メソッドを連結できる

LINQは「メソッドチェーン」で各メソッドを連結できる

すでにこれまでのサンプルコードでも使用していますが、LINQは「メソッドチェーン」で各メソッドを並べて記述できます。これは、LINQのメソッドが基本的に「IEnumerable型」つまりシーケンスを返すため、その戻り値を参照して引き続き別のメソッドを呼び出せることが理由です。詳細は以下のサンプルコードのとおりです。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;
using System.Xml.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な整数値をリストに格納する
var list = new List { 1, 0, 3, 6, 110, -56, 83, 68, 565, 0 };

// リストの値を画面上に表示する
foreach (var x in list)
{
Console.Write(x + " ");
}
Console.WriteLine();

// LINQメソッドをメソッドチェーンで連結して使う
var resultA = list
.Distinct() // 重複要素を削除する
.OrderBy(x => x); // 昇順ソートする

// リストの値を画面上に表示する
foreach (var x in resultA)
{
Console.Write(x + " ");
}
Console.WriteLine();

// LINQ以外のメソッド(FindAllとConvertAll)とも組み合わせてみる
var resultB = list
.FindAll(x => x % 2 == 0) // 偶数のみ抽出する
.OrderBy(x => x) // 昇順ソートする
.ToList() // ConvertAllメソッドを使用するためリストに変換する
.ConvertAll(x => x * x * x); // 抽出した要素を3乗値に変換する

// リストの値を画面上に表示する
foreach (var x in resultB)
{
Console.Write(x + " ");
}
Console.WriteLine();
}
}
}

//実行結果

なおリストの「FindAllメソッド」はLINQの「Where」に、リストの「ConvertAllメソッド」はLINQの「Select」に相当します。

LINQの大きな特徴は3つ

LINQの大きな特徴は3つ

C#のLINQの大きな特徴は以下3つです。

  • 処理内容をメソッドで指定できる
  • IEnumerable型に実装されていて戻り値も同型
  • LINQの処理は「遅延実行」される

処理内容をメソッドで指定できる

繰り返し解説しているように、ほとんどのLINQメソッドは引数に「ラムダ式」を取ります。そのため、ほとんどのLINQでは引数に処理用のメソッドを取るので、以下のサンプルコードのようにさまざまな処理が行えます。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 0~20までの整数値をリストに格納する
var nums = Enumerable.Range(0, 21).ToList(); // Enumerable.Rangeメソッドで整数値の範囲を指定する

// 整数値のリストを文字列に加工する
var converted = nums.Select(i =>
{
// 空文字列を用意する
String str = "";

// 3の倍数であれば「Fizz」を表示する
if (i % 3 == 0)
{
str += "Fizz";
}

// 5の倍数であれば「Buzz」を表示する
if (i % 5 == 0)
{
str += "Buzz";
}

// 空文字列ならそのまま整数値を文字列に変換する
return !string.IsNullOrEmpty(str) ? str : i.ToString();
});

// 変換後のリストを表示する
foreach (string output in converted)
{
Console.WriteLine(output);
}
}
}
}

//実行結果

上記のサンプルプログラムは、0~20の文字列を含むリストを用意し、3の倍数であれば「Fizz」を、5の倍数であれば「Buzz」を表示するというものです。つまり15の倍数には「FizzBuzz」が表示されます。ラムダ式内部にはメソッドを記載できるので、LINQではこのような長い処理でも簡単に実現できます。

IEnumerable型に実装されていて戻り値も同型

今回ご紹介した「Select」や「Where」などのように、データを返すメソッドは必ずIEnumerableクラスに実装されており、なおかつ戻り値は「IEnumerableクラス」となっています。そのため前述したように、メソッドチェーンを使って簡潔なサンプルコードが書けます。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な整数値をリストに格納する
var nums = new List { 1, 5, 2, 3, 7, 4 };

// メソッドチェーンで「偶数」に絞り込み、さらに2乗値に加工する
var result = nums
.Where(item => item % 2 == 0) // 偶数値を絞り込む
.Select(item => item * item); // 2乗値に加工する

// 加工した結果を表示する
foreach (var x in result)
{
Console.WriteLine(x.ToString());
}
}
}
}

//実行結果

LINQの処理は「遅延実行」される

LINQの処理は「遅延実行」されることも大きな特徴です。LINQのメソッドを使用したとき、その式が実際に実行されるのはforeachなどで要素にアクセスするときとなります。いわば、あらかじめ処理内容を予約しておいて、処理後のデータを使う場面で初めて実行されるというイメージです。以下のサンプルコードで検証してみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 適当な整数値をリストに格納する
var nums = new List { 1, -5, 2, -3, 7, 4 };

// 絶対値が小さい順にソートする
var result = nums.OrderBy(item => Math.Abs(item));

// あとから「-6」という要素を追加する
nums.Add(-6);

// 加工した結果を表示する
foreach (var x in result)
{
Console.WriteLine(x.ToString());
}
}
}
}

//実行結果

上記のサンプルプログラムでは、LINQのメソッドでソート処理を行ったあとに「-6」を追加するコードを記載しています。しかし実際には、「-6」を追加したあとにソートされています。これはLINQの遅延処理により、foreachでデータにアクセスするときに初めてLINQのメソッドが実行されるためです。ここを理解しておかないと、意図しない動作になることがあるので注意が必要です。

C#でLINQを使うメリットとデメリット

C#でLINQを使うメリットとデメリット

C#でLINQを使うことで得られる3つのメリットと、注意すべき2つのデメリットについて解説します。

  • メリット1:ソースコードが読みやすくなる
  • メリット2:予期せぬバグやエラーが発生しにくい
  • メリット3:幅広い型に対応しているので便利
  • デメリット1:LINQ特有の構文に馴染みにくい
  • デメリット2:パフォーマンスが低下しやすい

メリット1:ソースコードが読みやすくなる

LINQの最も大きなメリットは、ソースコードが読みやすくなることです。ひとつの処理がひとつのメソッドにまとまっているので、プログラマが何を意図しているかソースコードで明確に表現できます。foreach文で書いたコードと、LINQで書いたコードを比較してみましょう。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// 4文字以上の文字列を探して字数のリストを作る
var source = new List() { "Cat", "Tiger", "Lion", "Bird", "Dog", "Wolf", "Horse" };

// foreachの場合
var resultA = new List();
foreach (var x in source)
{
if (x.Length >= 4)
{
resultA.Add(x.Length);
}
}

// 加工した結果を表示する
foreach (var x in resultA)
{
Console.Write(x.ToString() + " ");
}
Console.WriteLine();

// LINQの場合
var resultB = source
.Select(x => x.Length) // Lengthの要素を抽出する
.Where(x => x >= 4); // Lengthが4以上の要素を絞り込む

// 加工した結果を表示する
foreach (var x in resultB)
{
Console.Write(x.ToString() + " ");
}
}
}
}

//実行結果

foreachの場合と比べて、LINQを使用したソースコードは簡潔です。さらに、「どこで何をしているか」わかりやすいので、複数人での開発現場においてプログラマ同士の意思疎通が図りやすくなります。

メリット2:予期せぬバグやエラーが発生しにくい

プログラミングでは、何か処理を記述するたびにバグやエラーのリスクが発生します。しかしLINQには、絞り込み・検索・並び替えなどの処理が標準機能として搭載されているため、自分で新たなロジックを書くことが減ります。結果的にバグやエラーが発生しにくくなり、プログラムの開発効率を向上させることが可能です。

メリット3:幅広い型に対応しているので便利

LINQのメソッドが実装されている「IEnumerableクラス」は、List以外のDictionaryやSetなどにも対応しているので、多様な場面で活用できます。たとえば以下のサンプルコードのように、Dictionaryの特定のキーを持つものを取り出して加工するまでループ処理を書かずして実現できます。

//サンプルプログラム

// 「System名前空間」を使用する
using System;

// 「System.Collections.Generic名前空間」を使用する
using System.Collections.Generic;

// 「System.Linq」を使用する
using System.Linq;

namespace Main
{
class Program
{
public static void Main()
{
// プロジェクトの役割をDictionaryクラスで設定する
var project = new Dictionary<string, string>
{
{ "山田 一郎", "ディレクター" },
{ "田中 二郎", "システムエンジニア" },
{ "斎藤 三郎", "プログラマー" },
{ "吉田 四郎", "プログラマー" },
{ "佐藤 五郎", "プログラマー" },
{ "鈴木 六郎", "デザイナー" },
};

// プログラマー担当者を抽出して名前を加工する
var result = project
.Where(x => x.Value == "プログラマー") // プログラマー担当者を抽出する
.Select(x => x.Key.Replace(" ", "")); // 苗字と名前の間のスペースを除去する

// 加工した結果を表示する
foreach (var x in result)
{
Console.WriteLine(x);
}
}
}
}

//実行結果

上記のサンプルプログラムでは、Dictionaryの特定値を持つ要素を取り出し、苗字と名前の間の空白スペースを除去しています。ループ処理をいっさい書かずに、こうした処理がさまざまなコレクションで実行できるのは便利です。

デメリット1:LINQ特有の構文に馴染みにくい

LINQの構文は独特な部分があるので、慣れるまでは「使いづらさ」を感じることもあります。しかし冒頭でご紹介したように、「クエリ構文」ではなく「メソッド構文」を使えば、C#の構文に似ているため気軽に使えます。また、LINQのメソッドを使うとソースコードが簡潔になることは、それだけで学習する価値がある大きなメリットだといえるでしょう。

デメリット2:パフォーマンスが低下しやすい

C#のLINQメソッドは、呼び出すたびにコレクション内のすべての要素にアクセスしているため、パフォーマンス低下の原因になることがあります。近年のCPUは高速なので、パフォーマンスが問題になる可能性は低いですが、プログラムを実行する環境によっては注意が必要です。

C#のLINQを活用してデータの集計や加工を行おう!

C#のLINQを活用してデータの集計や加工を行おう!

今回はC#のLINQの使い方や応用テクニックについて解説しました。LINQにはさまざまな便利メソッドがあるため、これまで意識せずに使っていたメソッドがあった人も多いはずです。とくにソートのメソッドは非常に便利なので、配列やリストなどのコレクションを扱うときに活用できます。

さらに、通常は細かな処理を自分で書かないといけないデータの集計や加工も、LINQならメソッドをひとつ呼び出すだけで実現できます。ただし、LINQを活用するためには、ラムダ式のようなC#の応用知識が必要なので、少しずつ使い方に慣れていきましょう。この機会にぜひC#のLINQを活用し、データ処理にチャレンジしてみてください。

アクセスランキング 人気のある記事をピックアップ!

    コードカキタイがオススメする記事!

    1. 子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介

      2024.01.26

      子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介

      #プログラミングスクール

    2. 【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説

      2022.01.06

      【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説

      #プログラミングスクール

    3. 【未経験でも転職可】30代におすすめプログラミングスクール8選!

      2024.01.26

      【未経験でも転職可】30代におすすめプログラミングスクール8選!

      #プログラミングスクール

    4. 初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説

      2024.01.26

      初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説

      #JAVA

    5. 忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説

      2024.01.26

      忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説

      #プログラミングスクール

    1. 【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介

      2022.01.06

      【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介

      #プログラミングスクール

    2. 【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説

      2024.01.26

      【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説

      #プログラミングスクール

    3. 【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説

      2024.01.26

      【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説

      #プログラミングスクール

    4. 【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説

      2024.01.26

      【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説

      #プログラミングスクール

    5. 【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説

      2024.01.26

      【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説

      #プログラミングスクール