この記事は C# で開発をしているのに、まだ LINQ(リンク)が使えていない人向けの入門記事です。
LINQ が使えていない理由は何でしょうか?
「難しくて理解できない」
「SQL っぽくて読みづらい」
「とっつきにくくて試せていない」
「メリットが分からない」
「必要性を感じない」
そういった人にも LINQ を理解してもらえるように、できるだけ簡単に説明していきます。
LINQ とは?
LINQ とは何でしょうか?
公式(MSDN)には、以下のように説明されています。
統合言語クエリ (LINQ: Language-Integrated Query) は、クエリ機能を C# 言語 (および Visual Basic や場合によってその他の .NET 言語) に直接統合する一連の技術の名前です。 LINQ を使用すると、クエリは、クラス、メソッド、イベントなどと同じように、高度な機能を備えた言語構成要素になります。
クエリを記述する開発者の場合、LINQ で最も違いを認識できる "統合言語" 部分はクエリ式です。 クエリ式は、C# 3.0 で導入された宣言クエリ構文で記述します。 クエリ構文を使用すると、データ ソースに対する複雑なフィルター処理、順序付け、およびグループ化の操作を最小限のコードで実行できます。 同じ基本的なクエリ式のパターンを使用して、SQL データベース、ADO.NET データセット、XML ドキュメントとストリーム、および .NET コレクション内のデータを照会および変換します。
この説明で理解できた人がいれば、その人は天才ではないでしょうか。
ここではあまり難しく考えず、「LINQ は foreach のパワーアップ版」と覚えてください。
LINQ の誤解
LINQ はよく誤解されることがありますので、その誤解を解いておきます。
・SQL と何か関係がある
LINQ は SQL とは一切関係ありません。
LINQ の典型的な例として、以下のようなサンプルコードが提示されることがあります。
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
var query = from x in list
where x % 2 == 0
orderby x
select x * 3;
「from」「where」「orderby」「select」と SQL でおなじみのキーワードが出てきますので、勘違いしてしまう人が多いのではないかと思います。
この誤解を避けるため、この記事では極力これらのキーワードを使わずに説明していきます。
・C# 言語固有の機能である
C# 以外の言語でも利用できます。
詳しくは別記事「LINQライブラリまとめ」を参照してください。
・LINQ は処理が速い(または遅い)
foreach と比較した場合、処理速度はほとんど変わりません。
・LINQ は新しい機能である
LINQ は 2007 年 11 月にリリースされた C# 3.0 で導入されました。
人の感覚にもよると思いますが、もうかれこれ 10 年以上前の技術なので、十分枯れていると言ってよいでしょう。
LINQ をはじめる前に
・クエリ構文とメソッド構文
LINQ の書き方には「クエリ構文」と「メソッド構文」の 2 種類があります。
どちらで書いても構わないのですが、この記事では C# の文法として馴染みのある「メソッド構文」を採用します。
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// クエリ構文
var query = from x in list
where x % 2 == 0
orderby x
select x * 3;
// メソッド構文(この記事ではこちらを記法を採用します)
var query = list
.Where(x => x % 2 == 0)
.OrderBy(x => x)
.Select(x => x * 3);
・LINQ の前に覚えておきたい C# の文法
LINQ の前に覚えておきたい C# の文法が 3 つあります。
「型推論」「匿名クラス」「ラムダ式」です。
※すでに知っている人は読み飛ばしてもらって構いません。
・型推論
型推論は変数宣言に「var」キーワードを使用します。
右辺の式を評価して、自動的に変数の型を決めてくれます。
using System;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
//List<int> list = new List<int> { 1, 84, 95, 95, 40, 6 }; // C# 2.0 以前の書き方
foreach (var x in list)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
1
84
95
95
40
6
・匿名クラス
匿名クラスは「class」キーワードでクラスを定義することなく、名前無しのクラスを使用することができます。
using System;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
// Fruit クラスは定義せず、匿名クラスを使用する
var fruitList = new[] {
new {Name = "りんご", Price = 300},
new {Name = "バナナ", Price = 200},
new {Name = "パイナップル", Price = 1000},
new {Name = "いちご", Price = 500},
};
foreach (var fruit in fruitList)
{
Console.WriteLine(fruit);
}
}
}
}
[コンソール出力結果]
{ Name = りんご, Price = 300 }
{ Name = バナナ, Price = 200 }
{ Name = パイナップル, Price = 1000 }
{ Name = いちご, Price = 500 }
・ラムダ式
ラムダ式はメソッドをオブジェクト化する機能です。
あまり深く考えず、デリケートの省略表記だと思っておきましょう。
// デリゲート(C# 2.0 以前の書き方)
delegate (int x) { return x * 3; }
// ラムダ式
x => x * 3
using System;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// list の要素から偶数を取り出す
var findAllList = list.FindAll(x => x % 2 == 0);
Console.WriteLine("=== findAllList ===");
foreach (var x in findAllList)
{
Console.WriteLine(x);
}
// list の要素をそれぞれ 3 倍にする
var convertAllList = list.ConvertAll(x => x * 3);
Console.WriteLine("=== convertAllList ===");
foreach (var x in convertAllList)
{
Console.WriteLine(x);
}
// list の要素をそれぞれ 3 倍にする(C# 2.0 以前の書き方)
var convertAllList2 = list.ConvertAll(delegate (int x) { return x * 3; });
Console.WriteLine("=== convertAllList2 ===");
foreach (var x in convertAllList2)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== findAllList ===
84
40
6
=== convertAllList ===
3
252
285
285
120
18
=== convertAllList2 ===
3
252
285
285
120
18
LINQ の使い方
それでは LINQ メソッドの使い方を見ていきましょう。
・要素の取得
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// list の最初の要素を取得する
Console.WriteLine("First: " + list.First());
// list の最後の要素を取得する
Console.WriteLine("Last: " + list.Last());
}
}
}
[コンソール出力結果]
First: 1
Last: 6
・集計
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// list の最大値を取得
Console.WriteLine("Max: " + list.Max());
// list の最小値を取得
Console.WriteLine("Min: " + list.Min());
// list の平均値を取得
Console.WriteLine("Average: " + list.Average());
// list の合計値を取得
Console.WriteLine("Sum: " + list.Sum());
// list の要素値を取得
Console.WriteLine("Count: " + list.Count());
}
}
}
[コンソール出力結果]
Max: 95
Min: 1
Average: 53.5
Sum: 321
Count: 6
・変換
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// list を配列に変換
int[] array = list.ToArray();
Console.WriteLine("=== array ===");
foreach (var x in array)
{
Console.WriteLine(x);
}
// list を object 型の List に変換
List<object> objectList = list.Cast<object>().ToList();
Console.WriteLine("=== objectList ===");
foreach (var x in objectList)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== array ===
1
84
95
95
40
6
=== objectList ===
1
84
95
95
40
6
・複数要素の取得
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// list から重複を除いた要素を取得する
var distinctList = list.Distinct();
Console.WriteLine("=== distinctList ===");
foreach (var x in distinctList)
{
Console.WriteLine(x);
}
// list の先頭から指定された数の要素をスキップして残りの要素を取得する
var skipList = list.Skip(3);
Console.WriteLine("=== skipList ===");
foreach (var x in skipList)
{
Console.WriteLine(x);
}
// list の先頭から指定された数の要素を取得する
var takeList = list.Take(3);
Console.WriteLine("=== takeList ===");
foreach (var x in takeList)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== distinctList ===
1
84
95
40
6
=== skipList ===
95
40
6
=== takeList ===
1
84
95
・判定
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// list のすべての要素が 100 未満かどうか
Console.WriteLine("All: " + list.All(x => x < 100));
// list のいずれかの要素が 0 未満かどうか
Console.WriteLine("Any: " + list.Any(x => x < 0));
// list に値が 40 の要素が含まれてるかどうか
Console.WriteLine("Contains: " + list.Contains(40));
}
}
}
[コンソール出力結果]
All: True
Any: False
Contains: True
・集合
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list1 = new List<int> { 1, 84, 95, 95, 40, 6 };
var list2 = new List<int> { 1, 16, 39, 33, 7, 84 };
// 和集合を取得する
var unionList = list1.Union(list2);
Console.WriteLine("=== unionList ===");
foreach (var x in unionList)
{
Console.WriteLine(x);
}
// 差集合を取得する
var exceptList = list1.Except(list2);
Console.WriteLine("=== exceptList ===");
foreach (var x in exceptList)
{
Console.WriteLine(x);
}
// 積集合を取得する
var intersectList = list1.Intersect(list2);
Console.WriteLine("=== intersectList ===");
foreach (var x in intersectList)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== unionList ===
1
84
95
40
6
16
39
33
7
=== exceptList ===
95
40
6
=== intersectList ===
1
84
・ソート
using System;
using System.Linq;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var fruitList = new[] {
new {Name = "りんご", Price = 300},
new {Name = "バナナ", Price = 200},
new {Name = "パイナップル", Price = 1000},
new {Name = "いちご", Price = 500},
};
// fruitList を Price が昇順になるように並び替える
var orderByList = fruitList.OrderBy(x => x.Price);
Console.WriteLine("=== orderByList ===");
foreach (var x in orderByList)
{
Console.WriteLine(x);
}
// fruitList を Price が降順になるように並び替える
var orderByDescendingList = fruitList.OrderByDescending(x => x.Price);
Console.WriteLine("=== orderByDescendingList ===");
foreach (var x in orderByDescendingList)
{
Console.WriteLine(x);
}
// fruitList を逆順に並び替える
var reverseList = fruitList.Reverse();
Console.WriteLine("=== reverseList ===");
foreach (var x in reverseList)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== orderByList ===
{ Name = バナナ, Price = 200 }
{ Name = りんご, Price = 300 }
{ Name = いちご, Price = 500 }
{ Name = パイナップル, Price = 1000 }
=== orderByDescendingList ===
{ Name = パイナップル, Price = 1000 }
{ Name = いちご, Price = 500 }
{ Name = りんご, Price = 300 }
{ Name = バナナ, Price = 200 }
=== reverseList ===
{ Name = いちご, Price = 500 }
{ Name = パイナップル, Price = 1000 }
{ Name = バナナ, Price = 200 }
{ Name = りんご, Price = 300 }
・LINQ メソッドの組み合わせ
最後に LINQ メソッドの組み合わせについて紹介します。
LINQ メソッドの戻り値は IEnumerable 型になっているため、メソッドチェーンとして繋げて使うことができます。
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// LINQ メソッドを組み合わせて使う
var resultList1 = list
.Distinct() // 重複要素を削除
.Skip(2) // 先頭から指定された数の要素をスキップ
.OrderBy(x => x); // 昇順に並び替え
Console.WriteLine("=== resultList1 ===");
foreach (var x in resultList1)
{
Console.WriteLine(x);
}
// LINQ 以外のメソッド(FindAll、ConvertAll)も組み合わせて使う
var resultList2 = list
.FindAll(x => x % 2 == 0)
.OrderBy(x => x)
.ToList() // ConvertAll メソッドを使用するため一旦 List に変換する
.ConvertAll(x => x * 3);
Console.WriteLine("=== resultList2 ===");
foreach (var x in resultList2)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== resultList1 ===
6
40
95
=== resultList2 ===
18
120
252
Select と Where
以上で LINQ メソッドの説明は終わりになりますが、いかがだったでしょうか。
想像していた LINQ とは違って、案外「普通の C#」として習得できたのではないでしょうか。
もしかしたら今まで LINQ と知らずに使っていたメソッドもあったかもしれません。
最後にこれまで説明を避けてきた Select と Where も紹介しておきます。
実は「LINQ メソッドの組み合わせ」のところで紹介した FindAll メソッド、ConvertAll メソッドを以下のように置き換えることができます。
Collections.Generic | LINQ |
---|---|
FindAll | Where |
ConvertAll | Select |
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// LINQ 以外メソッド(FindAll、ConvertAll)も組み合わせて使う
var resultList1 = list
.FindAll(x => x % 2 == 0)
.OrderBy(x => x)
.ToList() // ConvertAll メソッドを使用するため一旦 List に変換する
.ConvertAll(x => x * 3);
Console.WriteLine("=== resultList1 ===");
foreach (var x in resultList1)
{
Console.WriteLine(x);
}
// FindAll メソッドを Where メソッドに、ConvertAll メソッドを Select メソッドに書き換え(処理は同じ)
var resultList2 = list
.Where(x => x % 2 == 0)
.OrderBy(x => x)
.Select(x => x * 3);
Console.WriteLine("=== resultList2 ===");
foreach (var x in resultList2)
{
Console.WriteLine(x);
}
// クエリ構文で書き換え(処理は同じ)
// (実は最初に紹介した「LINQ の典型的な例」と同じ)
var resultList3 =
from x in list
where x % 2 == 0
orderby x
select x * 3;
Console.WriteLine("=== resultList3 ===");
foreach (var x in resultList3)
{
Console.WriteLine(x);
}
}
}
}
[コンソール出力結果]
=== resultList1 ===
18
120
252
=== resultList2 ===
18
120
252
=== resultList3 ===
18
120
252
LINQ のメリット
実は LINQ でできることは foreach や for を使っても実現することができます。
それでは LINQ を使うとどんなメリットがあるのか見ていきましょう。
・少ない行数、ネスト数で簡潔なコードが書ける
少ない行数、ネスト数で簡潔なコードを書くことができます。
forearch と for を使った場合と LINQ を使った場合を比較してみます。
forearch と for を使ったコードは 15 行ありますが、LINQ を使ったコードは僅か 4 行しかありません。(コンソール出力のコードの行数は除いています)
また、LINQ を使ったコードは複雑になっても {} のネスト数が増えません。
using System;
using System.Linq;
using System.Collections.Generic;
namespace LinqTest
{
class MainClass
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 84, 95, 95, 40, 6 };
// LINQ を使って実装
// ========================================
var resultList1 = list
.Where(x => x % 2 == 0)
.OrderBy(x => x)
.Select(x => x * 3);
Console.WriteLine("=== resultList1 ===");
foreach (var x in resultList1)
{
Console.WriteLine(x);
}
// ========================================
// LINQ は使わず、foreach と for を使って実装
// ========================================
var resultList2 = new List<int>();
foreach (var x in list)
{
if (x % 2 == 0)
{
resultList2.Add(x);
}
}
resultList2.Sort((x, y) => x.CompareTo(y));
for (int i = 0; i < resultList2.Count; i++)
{
resultList2[i] *= 3;
}
Console.WriteLine("=== resultList2 ===");
foreach (var x in resultList2)
{
Console.WriteLine(x);
}
// ========================================
}
}
}
[コンソール出力結果]
=== resultList1 ===
18
120
252
=== resultList2 ===
18
120
252
・意図が伝わるコードが書ける
foreach や for で書いたコードはループ処理の内容が {} の中に記述されているので、中の処理を追って読んでいかないと、そのループ処理が何をやっているのか理解できません。
LINQ では処理の内容ごとに決まったメソッドが用意されているので、書いたコードが何をやっているのかが明確になります。
機能的に十分なのであれば LINQ を優先して使うべきでしょう。
文法 | 自由度 | 意図の伝わりやすさ |
---|---|---|
goto | ◎ | ☓ |
for | ○ | △ |
while | ○ | △ |
foreach | △ | ○ |
LINQ | ☓ | ◎ |
・メソッドチェーンによる処理の組み合わせができる
Where や OrderBy はそれぞれ処理が独立しており、自由に組み合わせることができます。
foreach や for で書いた場合と比較して、実装や修正が簡単に行えます。
まとめ
今回の記事は入門者向けということもあってかなり端折ってますので、もっと LINQ のこと知りたいという人は他の解説サイトや書籍などで勉強して頂ければと思います。
公式(MSDN)
https://msdn.microsoft.com/ja-jp/library/bb397676.aspx
@IT>LINQ基礎編
http://www.atmarkit.co.jp/fdotnet/csharp30/csharp30_06/csharp30_06_01.html
追記:2017/03/28
続きの記事「続・はじめての LINQ」を書きました。