LINQを理解しよう!

[C#] LINQを理解しよう!

※ 当サイトは広告を含みます。

ここではC#のLINQを学習します。

LINQとは

LINQとは、コレクションに対してSQLっぽい処理を記述できる手法です。
正式名称は統合言語クエリ(Language-Integrated Query)となり、略してLINQと呼ばれます。

なお、公式の説明は超難しいです。

統合言語クエリ(LINQ)は、C#言語への直接的なクエリ機能の統合に基づくテクノロジのセットの名前です。これまでは、データに対するクエリは、コンパイル時の型チェックやIntelliSenseのサポートがない単純な文字列として表現されてきました。さらに、SQLデータベース、XMLドキュメント、さまざまなWebサービスなど、各種データソースの異なるクエリ言語を学習する必要があります。LINQでは、クエリは、クラス、メソッド、イベントと同様に、ファーストクラスの言語コンストラクトです。

Retrieved 22:59, April 13, 2025, from https://learn.microsoft.com/ja-jp/dotnet/csharp/linq/
管理人
管理人

なるほどねぇ。

りさ
りさ

これどういう意味なの?

管理人
管理人

とりあえず、意味が分からないことが分かった。

りさ
りさ

そうですか...

ちなみにSQLっぽく書ける手段があるだけでSQLは一切関係ありません。
あくまでコレクションに対しての処理を記述する1つの仕組みです。

TipsSQLとはデータベースに対する操作を記述するプログラミング言語です。

LINQを利用するための前知識

LINQを利用するための前知識として匿名型ラムダ式の知識が必要です。
後、知らなくても問題ありませんが、各種LINQは拡張メソッドとして定義されてます。

名前空間は不要

拡張メソッドを利用するためには名前空間の記述が必要です。
LINQの場合は次の名前空間を指定しますが、現在は内部的に自動宣言されてるので不要です。


using System.Linq;
管理人
管理人

古いシステムだと任意の宣言が必要です。

りさ
りさ

いつから不要なの?

管理人
管理人

知らん。書いて駄目なら宣言して。

LINQを使ってみる

先に伝えた通り、LINQはコレクションに対する処理を記述する手段です。
まず、次のコレクションがあるとしましょう。単なるint型の配列です。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new int[] { 1, 2, 3, 4, 5 };

      foreach (var d in collection)
        Console.WriteLine(d);
    }
  }
}

ここから特定の数値だけを抜き出して表示したいと思います。
仮に3以上の数値だけを表示するとします。こんな感じですかね。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new int[] { 1, 2, 3, 4, 5 };

      foreach (var d in collection)
      {
        if (d >= 3)
          Console.WriteLine(d);
      }
    }
  }
}

でっ、これをLINQに書き換えてみます。結論としてこうなります。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new int[] { 1, 2, 3, 4, 5 };

      // LINQ利用
      var query = collection.Where(x => x >= 3);

      foreach (var d in query)
        Console.WriteLine(d);
    }
  }
}

コレクションに対してWhereで記述してるのがLINQです。WhereはLINQの中の1機能です。
条件をラムダ式で記述します。ちなみにLINQで書かれる式をクエリと呼びます。


// LINQ利用
var query = collection.Where(x => x >= 3);

これは対象のコレクションから条件を満たす値を抜き出す式です。
まぁ、式という表現が正しいかは分かりませんが、そういうものです。

他にもSelectとかAnyとかGroupByとかとか、色々と実装されてます。
SQLっぽいのは使われてる名前が似てるからですかね。

2つの記述方法

LINQには2つの記述方法が存在します。それをメソッド構文クエリ構文と言います。
出来ることに違いは無いと思います。ただ、僕の経験だとメソッド構文しか使いません。

メソッド構文

さっきのパターンです。こんな感じにメソッドっぽく記述するやつ。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new int[] { 1, 2, 3, 4, 5 };

      // LINQ利用(メソッド構文)
      var query = collection.Where(x => x >= 3);

      foreach (var d in query)
        Console.WriteLine(d);
    }
  }
}
管理人
管理人

実質的にメソッド構文の1択。

りさ
りさ

そうなの?

管理人
管理人

いや、知らねぇけど。

りさ
りさ

...

クエリ構文

こっちはSQLっぽく記述するパターンです。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new int[] { 1, 2, 3, 4, 5 };

      // LINQ利用(クエリ構文)
      var query = from x in collection where x >= 3 select x;

      foreach (var d in query)
        Console.WriteLine(d);
    }
  }
}
管理人
管理人

人生で初めて書いた。間違ってたらすまんな(適当)。

りさ
りさ

雑すぎる...

補足このサイトではクエリ構文は一切扱いません。

LINQを使う理由

LINQを使うのは処理をシンプルに記述できるからです。
ここでは2つのパターンの使用例を紹介します。

LINQは連続して記述できる

1つ目のパターンです。

LINQは複数の処理を連続(結合)して記述できます。
これはSkipWhereMaxを繋げてみました。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new int[] { 8, 1, 15, 6, 19 };

      var query = collection.Skip(1).Where(x => x < 10).Max();

      Console.WriteLine(query);
    }
  }
}

Skipは指定した個数分をスキップ、Whereは条件指定、Maxは最大値を求めます。
つまり、コレクションに対して1個分をスキップし、10より小さい値を集め、その中から最大値を探しました。

これを1行で書けるのがLINQです。最強ですね。
なお、人によっては改行します。この辺は処理の長さとか好み。


var query = collection
  .Skip(1)
  .Where(x => x < 10)
  .Max();

var query = (
  collection
    .Skip(1)
    .Where(x => x < 10)
    .Max()
);
管理人
管理人

こうやってコーディング論争が起きるのであった。

りさ
りさ

嫌な業界だね。

条件に処理を記述できる

2つ目のパターンです。

LINQは条件をラムダ式で記述しますが、そこに自由な式を記述できます。
まぁ、単なるメソッドなので当たり前ですね。とりあえず、こんなコードがあったとします。


namespace Sample
{
  internal class Data
  {
    public string Name { get; set; }
    public int Score { get; set; }

    public void Display()
    {
      Console.WriteLine($"Name={this.Name}, Score={this.Score}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new Data[]
      {
        new Data() { Name = "A", Score = 90, },
        new Data() { Name = "B", Score = 50, },
        new Data() { Name = "C", Score = 30, },
      };

      foreach (var d in collection)
        d.Display();
    }
  }
}

ここからScoreが最も大きい人物を探したいと思います。
最大値なので先ほど使ったMaxが活用できそうです。


namespace Sample
{
  internal class Data
  {
    public string Name { get; set; }
    public int Score { get; set; }

    public void Display()
    {
      Console.WriteLine($"Name={this.Name}, Score={this.Score}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new Data[]
      {
        new Data() { Name = "A", Score = 90, },
        new Data() { Name = "B", Score = 50, },
        new Data() { Name = "C", Score = 30, },
      };

      var query = collection.Max();

      query.Display();
    }
  }
}

単純にこうなります。と言いたいとこですが、これは動きません。
実行すると例外が発生すると思います。理由はMaxが対象とするのはData型だからです。

つまり、対象をData型の中に存在するScoreプロパティにする必要があります。
これをラムダ式は簡単に記述できます。こんな感じです。


namespace Sample
{
  internal class Data
  {
    public string Name { get; set; }
    public int Score { get; set; }

    public void Display()
    {
      Console.WriteLine($"Name={this.Name}, Score={this.Score}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new Data[]
      {
        new Data() { Name = "A", Score = 90, },
        new Data() { Name = "B", Score = 50, },
        new Data() { Name = "C", Score = 30, },
      };

      var query = collection.Max(x => x.Score);

      Console.WriteLine(query);
    }
  }
}

注意として、Maxで求めることができるのは最大値であり、最大値を持つオブジェクトではありません。
つまり、オブジェクトが所有するDisplayメソッドは利用できません。

ここで大切なのはLINQが求める型と返す型を理解することです。
LINQが難しいと感じる人は型への理解が甘いです。LINQを使う以前の問題です。

後、各LINQの仕様は公式ドキュメントをどうぞ。
https://learn.microsoft.com/en-us/dotnet/api/system.linq
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable

遅延実行と即時実行

LINQには利用時の注意があります。それが遅延実行即時実行です。
まず、LINQには記述した時点では評価(演算)されないメソッドが存在します。

それを遅延実行または即時実行で表現します。両方ともそのままの意味です。
ちなみに遅延実行がDeferred execution、即時実行がImmediate executionらしい。

分類は公式ドキュメントに書いてあります。
https://learn.microsoft.com/en-us/dotnet/csharp/linq/get-started/introduction-to-linq-queries#classification-table

管理人
管理人

値を求めるようなやつは即時実行で並び替えみたいなやつが遅延実行って感じです。

即時実行は書いてあるとおりなので説明不要と思います。
LINQを書いた時点で処理されます。注意が必要なのは遅延実行ですね。

この遅延実行を大雑把に説明すると、LINQが使われる時点まで処理されません。
次の例を見てください。これはリストの中身(3番目)を途中で消してます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new List<int>() { 1, 2, 3, 4, 5 };

      var query = collection.Where(x => x == 3);

      collection.RemoveAt(2);

      foreach (var d in query)
        Console.WriteLine(d);
    }
  }
}

LINQを記述したのはリストの中身を削除する前です。にも関わらず、最後の表示では対象が消えてます。
つまり、実際の処理が行われたのはforeachを呼び出した瞬間となります。これが遅延実行です。

即時実行の強制

コーディングしてると遅延実行を気にするよりも、任意の時点で実行したい場面のほうが多いです。
これを解決するのが即時実行の強制です。呼び方があってるかは分かりませんが、そのままの意味です。

記述は遅延実行のクエリに続けて即時実行のクエリを書きます。
普通はTo〇〇となってるメソッドを使います。いわゆる変換用のメソッドですね。

これはToArrayなら配列に変換、ToListならリストに変換してくれます。
例えるならクエリ式を配列やリスト等の実オブジェクトに変換したと言えます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var collection = new List<int>() { 1, 2, 3, 4, 5 };

      // 即時実行の強制
      var query = collection.Where(x => x == 3).ToList();

      collection.RemoveAt(2);

      foreach (var d in query)
        Console.WriteLine(d);
    }
  }
}

POINTコレクションとして使い回したい場合も即時実行が便利です。

あとがき

LINQは超便利です。と言うか、何故LINQで書かないってソースが普通にあるある。
if文だと何行も必要な処理がLINQなら1行で書けます。後はグループ化も便利ですね。

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。