C#のLINQにチャレンジしてみよう!

C#のLINQにチャレンジしてみよう!のアイキャッチイメージ

C#のLINQとは?

C#のLINQとはLanguage-Integrated Queryの略で、配列変数やコレクションなどのデータ群から、条件文を指定してそれに合うデータを取り出す構文を意味します。データベースで使うSQLに似ていますが、違いもあります。

この記事ではSQLを知らない人でも理解できるようにわかりやすくご説明しますので、是非読んで役立ててください。

LINQの基本

まずは簡単な例から始めましょう。なおC#のLINQを使用するには、ソースコードの先頭にネームスペース指定の”using System.Linq;”を記述してください。

int[] ary = { 8, 4, 2, 9, 5, 0, 3, 1, 7, 6 };

// LINQクエリ構文
var result
 = from val in ary
  where val <= 5
  select val;

// resultの中身を表示する
foreach (var num in result)
{
 Console.Write(num + " ");
}

これを実行すると、コンソールに以下のように表示されます。ary配列の5以下の値が抽出されていますね。

4 2 5 0 3 1

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

上のサンプルプログラムのLINQクエリ構文の説明は以下になります。

var result
(結果を入れるresultコレクション)
= from val in ary
(配列aryの値を1つvalに入れる)
 where val <= 5
(valが5以下であれば)
 select val;
(valをresultに入れる)

C#のLINQはSQLに似ていると言いましたが、selectを最後にするのがSQLと違います。なおサンプルのクエリ構文は以下のようなメソッド構文にすることも可能です。=>はC#特有のラムダ式の表記法ですね。

var result = ary
 .Where(val => val <= 5)
 .Select(val => val);

この記事では、ラムダ式を使用しないシンプルなクエリ構文で説明します。

匿名型でLINQを使う

C#の匿名型に対してLINQを使うと分かりやすいコードを記述できます。以下が例です。

// 匿名型の配列
var human_table = new[]
{
 new {FirstName = "太郎", SecondName = "山田", Age = 23, Sei = "male" },
 new {FirstName = "花子", SecondName = "鈴木", Age = 18, Sei = "female" },
 new {FirstName = "一郎", SecondName = "佐藤", Age = 41, Sei = "male" },
 new {FirstName = "信介", SecondName = "阿部", Age = 32, Sei = "male" },
};

var result
 = from row in human_table
  where row.Sei == "male" // 男性のみ対象
  orderby row.Age // 年齢で昇順にソート
  select row.SecondName; // セカンドネームを選択

foreach (var name in result)
{
 Console.WriteLine(name);
}

実行すると以下のように表示されます。

山田
阿部
佐藤

orderbyで昇順・降順のソートが可能

上のサンプルプログラムは名簿のうち男性だけをリストアップしてセカンドネームを取り出し、年齢の若い順(昇順)に並べて表示するという処理を行います。クエリ構文のorderbyで昇順にソートされます。

では降順にソートしたい場合はどうすれば良いのでしょうか。その場合は、

orderby row.Age descending

とすれば年齢の高い順(降順)に表示されます。なお昇順の場合は以下のようにしてもOKです。ascendingは省略できるということですね。

orderby row.Age ascending

intoでselect文を続ける

C#のクエリ構文はselectで終わりますが、その後にintoを追加するとさらに続けることができます。上のサンプルのクエリ文は以下のように記述しても同じ結果になります。

var result
 = from row in human_table
  where row.Sei == "male"
  select row into row2 // intoでさらに続ける
  orderby row2.Age
  select row2.SecondName;

「男性をリストアップして、そのリストをさらに年齢の昇順でソートする」ということですね。この例では元のクエリより複雑になっていますが、クエリの条件が多い場合は役立つでしょう。

letでクエリの中で変数の代入が可能

letを使うとクエリ内で変数の計算と代入ができます。サンプルのクエリを以下のように修正してみてください。

var result
 = from row in human_table
  where row.Sei == "male"
  orderby row.Age
  let fullname = row.SecondName + row.FirstName // 名字と下の名前を結合
  select fullname; // resultにフルネームを入れる

結果は以下になります。

山田太郎
阿部伸介
佐藤一郎

クエリの中でも匿名クラスを生成できる

クエリの中でnewをして匿名クラスを生成することができます。サンプルのクエリ以降を以下のように修正してみてください。

var result
  = from row in human_table 
  where row.Sei == "male"
  orderby row.Age
  select new { row.SecondName, row.FirstName }; // 名字と下の名前を返す

foreach (var name_set in result)
{
 Console.WriteLine(name_set.SecondName+name_set.FirstName); // 結合して表示
}

これでletを使った時と同じ結果が表示されます。letを使うより、このようにクエリの外で計算をした方がやりやすい場合もあるでしょう。

Count、Max、Averageなどの使い方

C#のLINQのクエリは数値の項目に対して計算をすることもできます。以下はサンプルのhuman_tableの項目について様々な計算を行います。

// データの項目の数
var cnt
 = (from row in human_table
  select row.SecondName).Count();
Console.WriteLine("人数は " + cnt + "人");
// 項目の最大値
var max
 = (from row in human_table
  select row.Age).Max();
Console.WriteLine("最高年齢は " + max + "才");
// 項目の平均値
var avg
 = (from row in human_table
  select row.Age).Average();
Console.WriteLine("平均年齢は " + avg + "才");

結果は以下になります。他にも最小値を出すMin()や合計値を出すSum()もあるので試してみてください。

人数は 4人
最高年齢は 41才
平均年齢は 28.5才

AnyとAllの使い方

取得したデータが条件を満たすかどうかチェックするにはAnyとAllを使います。以下はサンプルのhuman_tableの項目について判定を行います。判定文の=>はラムダ式ですね。

// Anyの条件を1つでも満たせばTrue
bool result1
 = (from row in human_table
  select row.Age).Any(age => age < 20);
Console.WriteLine("20歳未満がいるかどうか " + result1);

// Allの条件を全て満たせばTrue
bool result2
 = (from row in human_table
  select row.Age).All(age => age >= 20);
Console.WriteLine("全員が20歳以上かどうか " + result2);

結果は以下になります。human_tableの鈴木花子さんが18歳なのでAnyの条件はTrue、Allの条件はFalseになるということですね。

20歳未満がいるかどうか True
全員が20歳以上かどうか False

SelectManyで配列を取得できる

匿名型の配列にさらに配列が入っていた場合、それを取得するには以下のようにSelectManyを使います。なおLINQのこの機能はクエリ型でなくメソッド型で使用します。

var syumi_table = new[]
{
 new {SecondName = "山田", Syumi = new[]{ "サッカー","ゲーム" } },
 new {SecondName = "鈴木", Syumi = new[]{ "ピアノ","読書" } },
 new {SecondName = "佐藤", Syumi = new[]{ "ゴルフ","カラオケ" } },
 new {SecondName = "阿部", Syumi = new[]{ "バンド","競馬" } },
};

var result = syumi_table
  .Where(val=> val.SecondName != "鈴木")
  .SelectMany(val=> val.Syumi); // Syumiを全て取得

foreach (var list in result)
{
 Console.WriteLine(list);
}

このサンプルを実行すると以下のように表示されます。配列の中身がIEnumerableを実装しているコレクションとして取得されています。

サッカー
ゲーム
ゴルフ
カラオケ
バンド
競馬

joinで2つのデータを結合

joinを使うと2つのデータの共通点がある項目を結合することができます。ここまでのサンプルでhuman_tableとsyumi_tableという2つの匿名型の配列データが出てきましたが、これを結合してみましょう。join~in~on~equals~で可能です。以下がその例です。

var result
 = from row in human_table
  join row2 in syumi_table on row.SecondName equals row2.SecondName
  select new[] { row.FirstName, row2.Syumi[0] };

foreach (var name_syumi in result)
{
 Console.WriteLine(name_syumi[0]+" "+name_syumi[1]);
}

human_tableとsyumi_tableのSecondNameは共通しているので、それが等しい箇所を結合します。実行すると以下のように表示されます。

太郎 サッカー
花子 ピアノ
一郎 ゴルフ
信介 バンド

group byで重複する箇所のあるデータをまとめる

例えばサンプルのsyumi_tableが以下のようになっていたら、SecondNameが同じ個所をまとめたいと思いますよね。

var syumi_table = new[]
{
 new {SecondName = "山田", Syumi = "サッカー" },
 new {SecondName = "山田", Syumi = "ゲーム" },
 new {SecondName = "鈴木", Syumi = "ピアノ" },
 new {SecondName = "鈴木", Syumi = "読書" },
};

group byを使えば同じ箇所があるデータをまとめることができます。以下が例です。

var result
 = from row in syumi_table
  group row.Syumi by row.SecondName; // SecondNameでSyumiをまとめる

foreach (var name_syumi in result)
{
 Console.Write(name_syumi.Key+" ");
 foreach (var s in name_syumi)
 {
  Console.Write(s+" ");
 }
 Console.WriteLine(""); // 改行
}

結果は以下になります。resultはIGroupingを実装したコレクションで、中身の要素のKeyにgroup byでまとめたSecondNameが入っています。

山田 サッカー ゲーム
鈴木 ピアノ 読書

さいごに

C#のLINQについて理解できましたでしょうか。今回のサンプルでは匿名クラスの配列データが対象でしたが、LINQは他にもXMLドキュメントやSQLデータベースを対象にすることもできます。C#でデータ検索のプロフェッショナルを目指すなら、是非LINQをマスターしましょう。