BETA

【C#】ラムダ式とLINQ入門

投稿日:2020-06-07
最終更新:2020-06-22

はじめに

C#において、登竜門でも名乗っていそうなラムダ式とLINQ。
あの奇妙な見た目。「なんもわからん」といった声をよく耳にします。
そこで私も記事を書くことにしました。周辺知識についても見ていきます。
まずはラムダ式から見ていきましょう。

そもそもラムダ式とは

ラムダ式とは、ラムダ計算で使う式のことです。ラムダ計算とは計算モデルの一つです。
式の先頭にギリシャ記号の"λ(ラムダ)"を使うのが名前の由来です。λを採用した由来は知りません。
このラムダ計算というモデルは計算機科学で一般的に使われていて、
特に関数型プログラミングにおいて重要になってきます。
本記事の趣旨から脱線するのでここでは解説しません。
(興味があれば「ラムダ計算」で検索してみましょう)

ラムダ式の使い方

C#に限らず、いろいろな言語にラムダ式と呼ばれる機能があります。
といってもラムダ式はただの式です。ラムダ計算を理解しなくても使えます。
ラムダ式は無名関数の定義に多用されます。その名の通り名前のない関数を定義できます
実際に使い方を見てみましょう。

ラムダ式は高校で習った関数とは違った表記をします。
引数を二乗する関数を考えます。

高校数学での関数の表記は以下の通りです:

f(x)=x*x

fが関数の名前で、xが引数、=の後ろが関数の式です。

ラムダ式では以下のように表現します:

λx.x*x

先頭のλがラムダ式を表していて、続くxが引数、.の後ろが関数の式です。
重要なこととして、この関数には名前をつけていません。名前をつけたいなら変数に代入します。

f=λx.x*x

C#では以下のように表現します。

x=>x*x

λが消えて.=>になりました。これだけです。
引数が増えたら()で括ります。関数の式が複数行になったら{}で括って
最後の文にreturnをつけます。

(x, y) =>  
{  
    int sum = x + y;  
    int prod = x * y;  
    return sum * prod;  
}  

無名関数の何がいいのか?その真価はLINQで発揮されます。

そもそもLINQとは

LINQ(統合言語クエリ;Language-Integrated Query)とは、
いろいろな種類のデータに統一された方法でアクセスできる機能です。

IT界にはいろいろなデータ形式(SQL Server, XMLなど)があります。
以前はそれぞれの形式に対応した言語を学ぶ必要がありました。
LINQを使えばその必要はありません。標準実装されているので、まさに救済措置と言えるでしょう。
特にコレクション(C#における配列、Listなどの総称)に対して使われることが多いです。

LINQの使い方

データにアクセスするための命令をクエリ(問い合わせ)といいます。
クエリの種類はこのページの通りにたくさんあります。
一度に覚えないでください。適宜参照しましょう。
ちなみに自前のクエリも作れます。(「c# linq カスタムメソッド」で検索)
ここではよく使うWhere()Select()を解説します。

以下の配列を考えます:

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

Where()は条件に合う要素だけを取り出します。

// 偶数を取り出すクエリ  
IEnumerable<int> even = array.Where(x => x % 2 == 0);  

ここでラムダ式が出てきました。つまり引数に関数を渡しています。これを高階関数といいます。
以前はこのような一時的な関数も、名前をつけてクラスに定義する必要がありました。
ラムダ式を使えばそんなものは不要です。

それからIEnumerable<int>なるものがでてきました。
詳しくは省略しますが、LINQはデータをIEnumerable<T>として扱います。
IEnumerable<T>は「全ての要素を一つずつ返す」しか能が無いので、
いろいろ弄りたいときは、後ろにToArray()ToList()をつけて配列やリストに変換します。

// 偶数を取り出して、リストに変換する  
List<int> even = array.Where(x => x % 2 == 0).ToList();  
// 結果:{2, 4, 6, 8}  

ToArray()ToList()をつけない場合、evenが必要になったときにクエリが実行されます。つまりevenは使うたびに新しい結果を返します。これを遅延実行といいます。
(「LINQ 遅延実行」で検索)

// クエリを定義しただけで、実行はされない。  
// まだevenに中身はない。  
List<int> even = array.Where(x => x % 2 == 0);  
array[0] = 100;  
// ここで初めてクエリが実行される。  
// よって結果に100が加わる。  
foreach (int n in even)  
{  
    Console.WriteLine(n.ToString());  
}  
// 結果:{100, 2, 4, 6, 8}  

Select()は各要素に処理を実行します。数学で言う「写像」です。

// 各要素を二乗するクエリ  
IEnumerable<int> square = array.Select(x => x * x);  
// 結果:{1, 4, 9, 16, 25, 36, 49, 64, 81}  

処理によっては配列の型が変化することに注意してください。
Unityを例に取ると:

IEnumerable<float> distance =  
    // 以下の時点ではGameObject[]  
    GameObject.FindObjectsWithTag("Enemy")  
    // 以下の時点ではIEnumerable<float>  
    .Select(x => Vector3.Distance(x.transform.position, targetPos))  

ここまでIEnumerable<int>だのList<int>だの長ったらしく書いてきましたが、
varを使えば全て省略できます。LINQに慣れてきたら使うようにしましょう。
(「c# 型推論」で検索)

// これが  
IEnumerable<int> square = array.Select(x => x * x);  
// こうなる  
var square = array.Select(x => x * x);  

大抵のクエリは繋げて書けます。AtCoder Beginner Contest 162 のB - FizzBuzz Sumを例にとると、
「1からnまでの整数のうち、3でも5でも割り切れない数の合計」は以下のように書けます:

Enumerable.Range(1, n)  
    .Select(x => x % 3 != 0)  
    .Select(x => x % 5 != 0)  
    .Sum();  

このように連続してクエリを実行することができます。これをメソッドチェーンといいます。
メソッドチェーンを使うことで、クエリを直感的に追うことができます。
LINQ以外でもやろうと思えばやれます。(「c# メソッドチェーン 自作」で検索)

クエリの名前の由来

C#に限らず、いろいろな言語にLINQに類似した機能があります。
中にはC#より直感的に思える命名もあります。C#とPythonを例に取ります。
C#のWhere()はPythonではfilter()です。
条件に合うものだけを取り出すので直感的な名前です。
C#Select()はPythonではmap()です。
数学で言う「写像(map)」なのでこれも直感的です。
C#がこのような名前を採用したのは、
SQLという言語と同じ感覚で使えるようにするためだと思われます。
実際にSQLと構文が似ています。SQLを使っていた人からすると理解しやすいんだと想います。

まとめ

長くなりましたが、この記事で解説した用語は以下のとおりです:

  • 無名関数
  • クエリ
  • 高階関数
  • メソッドチェーン

記事を書いているうちに、なぜラムダ式とLINQが難しいのかわかった気がします。
関数型プログラミングそのものだからです。
ラムダ計算、無名関数、高階関数、メソッドチェーンあたりは
関数型プログラミングそのもので、オブジェクト指向とは異なる思想だからです。
つまり、関数型プログラミングを学べば強固なプログラミングが可能になるのです。
関数型プログラミングの境地へ行きましょう。

普通のやつらの上を行け ---Beating the Averages---
Copyright 2001 by Paul Graham
原文: http://www.paulgraham.com/avg.html
日本語訳:Shiro Kawai (shiro @ acm.org)

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

ハシブトクロウ(@JuncleGrow)の技術ブログです。

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう