【C#入門】LINQ(統合言語クエリ)をわかりやすく解説
公開日:2021.10.18
スキルアップC#のLINQ(統合言語クエリ)とは、ループ処理を簡潔に記載でき、操作メソッドを数多く提供するライブラリです。C#のプログラミングでは利用する機会が多いため、「何となく使っている」という方も多いのではないでしょうか。
本記事では、C#のLINQについて、C#の仕様も振り返りつつ、基本の使い方を紹介します。サンプルコードも多く示しますので、実際にプログラミングの学習を進める際の参考にしてください。
LINQとは
C#のLINQ(統合言語クエリ)とは、コレクション(配列やList、Dictionaryなど)の要素を処理するメソッドを集めたライブラリです。コレクションは、データベースやXMLを操作する際にもよく使うため、C#でプログラミングをする場合、LINQの利用は避けて通れません。
LINQは、forやforeachの高機能版ともいえ、ループ処理を簡潔に記載できる点が大きなメリットです。LINQは、ソースの冒頭でSystem.Linqを参照することによって使えるようになります。
以下は、foreachを使った処理とforeachからLINQに置き換えた処理のコード断片です。
【foreachを使った処理】
List<int> myList = new List<int>();
foreach(string s in source) {
int length = s.Length;
if (length >= 4){
myList.Add(length);
}
}
foreach (int result in myList){
Debug.Log(result.ToString());
}
【foreachからLINQに置き換えた処理】
IEnumerable<int> myList= source.Select(s => s.Length)
.Where(l => l >= 4);
foreach(int result in myList){
Debug.Log(result.ToString());
}
LINQに置き換えたループ処理は、foreachに比べて簡潔に記載できていることをご確認ください。
LINQの特徴
C#のLINQには、以下の大きな特徴があります。
- 処理をメソッドで指定できる
- IEnumerable型に実装されており、IEnumerable型を返す
- LINQの処理は遅延実行される
LINQは、以下のように処理をメソッドで指定でき、IEnumerable型を返します。
IEnumerable<int> linqResult = source.Select(s => s.Length)
.Where(l => l >= 4);
また、LINQの処理は、メソッド実行時に処理予約するだけのため、処理は実行されません。以下は、LINQの遅延実行を理解しやすいサンプルコードです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class MySample : MonoBehaviour {
void Start(){
List<int> list = new List<int> { 6, -8, 1 };
// 絶対値が小さい順に並び替えるOrderByメソッドを実行(この時点では実行していない
IEnumerable<int> result = list.OrderBy(item => Mathf.Abs(item));
// リストに要素を追加、この時点では { 6, -8, 1, 5 }
list.Add(5);
// 絶対値が小さい順に並び替え
// 1, 5, 6, -8の順に出力される(メソッドの後に追加した要素も反映される)
foreach(int item in result) {
Debug.Log(item.ToString());
}
}
}
上記のコードでは、OrderByメソッド実行の後にAddメソッドでListに要素を追加。その後に、ループ処理で要素を取り出して出力しています。しかし、要素にアクセスするこのタイミングで、初めてOrderByメソッドを実行し、追加した要素も含めて並べかえができていることを確認することが可能です。
LINQを使うメリット
LINQを使用するメリットは、以下の3点です。
- コードの可読性が上がる
- バグが発生しにくい
- データの集まりを表現する幅広い型に対応
LINQを使用すると、実行したいこと1つにつき1つのメソッドを呼び出すので、コードの可読性が上がり、誰が見ても分かりやすいコードを記載できます。また、データの集まりを操作するのに必要なメソッドを網羅しているため、自作でロジックを作る必要がありません。そのため、バグの作りこみを抑えることができます。
さらに、LINQのメソッドを提供するIEnumerable型は、List以外にもDictionaryやSetなども実装されているため、処理を汎用的に作ることも可能です。
LINQを使う前に知っておきたいC#の基礎知識
LINQの使い方を説明する前に、C#の基礎知識を整理しておきましょう。これらの概念は、LINQを理解するうえで欠かせません。これまで、「LINQを理解しにくい」と感じていた場合は、特にこれらの概念を再確認して「理解しにくい部分はどこか」を把握し、C#の知識を深めましょう。
クエリ構文とメソッド構文
LINQには、クエリ構文とメソッド構文の2種類あります。クエリ構文はSQLに似た構文のことです。SQLになじみのある人なら、クエリ構文のほうが直感的に理解しやすいといわれています。以下は、クエリ構文のサンプルコードです。
var list = new List<int> { 1, 3, 8, 27, 4, 6 };
// クエリ構文:SQLの構文に似ている呼び出し方法
var query = from x in list
where x % 2 == 0
orderby x
select x * 2;
一方、メソッド構文は、以下の通りです。
var list = new List<int> { 1, 3, 8, 27, 4, 6 };
// メソッド構文
var query = list
.Where(x => x % 2 == 0)
.OrderBy(x => x)
.Select(x => x * 2);
本記事においては、クエリ構文で行えないこともあるため、メソッド式構文を採用しています。
型推論
型推論とは、変数を宣言する際varキーワードを使用し、右辺の式から自動的に変数のほうを決定する仕組みのことです。
//varキーワードを使った型推論
var list = new List<int> { 1, 3, 8, 27, 4, 6 };
C# 2.0 以前は、以下のように型を明確にして記述する必要がありました。
List<int> list = new List<int> { 1, 3, 8, 27, 4, 6 };
LINQでも型推論を多用します。
匿名クラス
匿名クラスとは、クラスを定義せずに使える名前のないクラスです。以下は、匿名クラスのサンプルコードです。
// NameとAgeフィールドを持つ匿名クラスを使用する
var sampleList = new[] {
new {Name = "鈴木", Age = 30},
new {Name = "佐藤", Age = 29},
new {Name = "高橋", Age = 12},
new {Name = "田中", Age = 55},
};
「フィールド名 = 値」のセットをカンマで区切って初期値を定義します。フィールドのデータ型は、コンパイル時にコンパイラが値を確認して決定する仕組みです。ただし、メンバーはパブリック定義のみ。全フィールドの初期化は必須で、メソッド定義はできないなどのデメリットもあるため、使い方はよく検討する必要があります。
ラムダ式
ラムダ式は、その場で定義して使える匿名関数です。ラムダ式で生成した関数については、デリゲート(関数を代入できる変数)に代入して使うことが必須となります。通常の関数定義は、以下の通り関数名を付けて処理内容を記載します。
// 2つのint型を入力して、1つのint型を返す
private int Add(int x, int y){
return x + y;
}
上記と同じ動きの関数をラムダ式で書くと、このような記法となります。
(x, y) => x + y;
実際には、上記のようにラムダ式だけを単体で書くことはなく、ラムダ式をデリゲートに代入して利用し、LINQも引数にデリゲートを用いています。例えば、よく使うSelectメソッドの引数は「Func<T, TResult>」(引数あり・返り値ありの関数が登録できるデリゲート)と定義されています。
LINQの基本的な使い方
C#の基礎知識についておさらいをしたところで、LINQの基本的な使い方を見ていきましょう。サンプルコードを交えながら説明を進めていきます。
Selectメソッドによるデータの選択
LINQのSelectメソッドは、Listなどコレクションの全要素を、指定の処理を実行したうえで別のオブジェクトに渡すメソッドです。また、Whereオペレータを使うと、指定した条件に合致した要素のみをSelectメソッドで処理します。どちらもラムダ式を利用し、右辺に処理を記述することが必要です。
では、サンプルコードで確認しましょう。
using System;
using System.Linq;
namespace MySample{
class MySample {
static void Main(){
int[] src = {0, 1, 2, 3, 4, 5};
//要素を3で割った余りをqueryオブジェクトに格納
var query1 = src
.Select(x => x % 3);
Console.WriteLine("[{0}]", string.Join(", ", query1));
//1よりも大きい要素のみを3で割った余りをqueryオブジェクトに格納
var query2 = src
.Where(x => x > 1)
.Select(x => x % 3);
Console.WriteLine("[{0}]", string.Join(", ", query2));
Console.ReadKey();
}
}
}
上記の実行結果は、以下の通りです。
[0, 1, 2, 0, 1, 2]
[2, 0, 1, 2]
GroupByメソッドによるデータのグルーピング
GroupByメソッドは、データをグルーピングするメソッドです。以下は、GroupByメソッドのサンプルコードです。
using System;
using System.Collections.Generic;
using System.Linq;
namespace GroupingSample {
class GroupingTest {
public string GoodsKind { get; set; }
public int GoodsNum { get; set; }
public string GoodsName { get; set; }
}
class GroupingSample {
static void Main() {
var result = new List<Test>() {
new GroupingTest {GoodsKind = "筆記具", GoodsNum = 15, GoodsName = "ボールペン"},
new GroupingTest {GoodsKind = "紙製品", GoodsNum = 36, GoodsName = "ノート"},
new GroupingTest {GoodsKind = "画材類", GoodsNum = 10, GoodsName = "水彩絵具"},
new GroupingTest{GoodsKind = "筆記具", GoodsNum = 22, GoodsName = "万年筆"},
new GroupingTest {GoodsKind = "紙製品", GoodsNum = 41, GoodsName = "封筒"},
new GroupingTest {GoodsKind = "画材類", GoodsNum = 12, GoodsName = "日本画絵具"},
new GroupingTest {GoodsKind = "筆記具", GoodsNum = 34, GoodsName = "鉛筆"},
new GroupingTest {GoodsKind = "紙製品", GoodsNum = 50, GoodsName = "アルバム"},
new GroupingTest {GoodsKind = "画材類", GoodsNum = 45, GoodsName = "絵筆"},
};
var query = result.GroupBy(x => x.GoodsKind);
foreach(var group in query) {
Console.WriteLine("{0}の在庫数量", group.Key);
foreach(var item in group) {
Console.WriteLine("{0}:{1}", item.GoodsName, item.GoodsNum);
}
}
Console.ReadKey();
}
}
}
上記のコードを実行すると、GoodsKind単位でデータがグルーピングされ、以下のような結果となります。
筆記具の在庫数量
ボールペン:15
万年筆:22
鉛筆:34
紙製品の在庫数量
ノート:36
封筒:41
アルバム:50
画材類の在庫数量
水彩絵具:10
日本画絵具:12
絵筆:45
foreach文を使った書き換え
「Selectメソッドによるデータの選択」で記載したサンプルコードのうち、SelectメソッドとWhereオペレータを使ったLINQをforeachに書き換えると以下のようになります。
using System;
namespace MySample{
class MySample {
static void Main() {
int[] src = {0, 1, 2, 3, 4, 5};
//var query2 = src
// .Where(x => x > 1)
// .Select(x => x % 3);
//上記のLINQをforeachに書き換え
foreach(int num in src) {
if(num > 1) {
int mod = num % 3;
Console.WriteLine("{0}", mod);
}
}
Console.ReadKey();
}
}
}
SelectManyメソッドによる平坦化
Selectメソッドと似たメソッドに、SelectManyメソッドがあります。SelectManyメソッドは、コレクション内に含まれる全要素を同じコレクションの要素に格納するメソッドです。このような処理を「平坦化」と呼びます。以下は、SelectManyメソッドによる平坦化の動きを確認するためのサンプルコードです。
using System;
using System.Collections.Generic;
using System.Linq;
namespace MySample{
class MyQuarter {
public string Period { get; set; }
public List<String> Months { get; set; }
}
class MySample {
static void Main(){
MyQuarter[] myq = {
new MyQuarter{Period = "第1四半期", Months = new List<string>{"4月", "5月", "8月"}},
new MyQuarter{Period = "第2四半期", Months = new List<string>{"7月, "8月", "9月"}},
new MyQuarter{Period = "第3四半期", Months = new List<string>{"10月", "11月", "12月"}},
new MyQuarter{Period = "第4四半期", Months = new List<string>{"1月", "2月", "3月"}}
};
var query = myq.SelectMany(x => x.Months);
Console.WriteLine("[{0}]", string.Join(", ", query));
Console.ReadKey();
}
}
}
上記の実行結果は、以下の通りとなります。
[4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月, 1月, 2月, 3月]
C#のLINQは慣れれば便利!
普段は、何となく使っているC#のLINQですが、本格的に理解しようとすると、少し難しく感じるかもしれません。それでも、理解できない部分を復習しながら、実際に何度もプログラミングで使って動きを確認していくと、自然と慣れてきて、LINQの便利さを理解できます。
AKKODiSでは、エンジニアの方向けにさまざまなサポートをしています。独学でC#を学ぶなかで行き詰まりを感じた場合には、AKKODiS(派遣・紹介予定派遣)が提供するAKKODiS AcademyのC#講座の利用もご検討ください。C#の学びを進め、エンジニア系の仕事を獲得し、業務経験も積んでいきましょう。
(2021年10月現在)