[C#] LINQとはなんぞや

LINQとはなんぞや。

私的なざっくり理解だと、LINQはデータベース以外のデータも、SQLクエリっぽい感じで操作できる仕組みかな。

私の感覚ですが、LINQを知るには、まずSQLServerやMySQLのような関係データベースといわれるものを、普通に触れるレベルになってからの方がいいと思う。
SQLの知識0だと、用語とか、メリットが分かりにくい気がする。

また、ラムダ式も覚えておいた方が良い。
前に書いたので、参考までに。 イベントハンドラとラムダ式

あと、欲を言うならジェネリックもなんとなく知っといたほうがいいかも。 ジェネリックとはなんぞや

さて、上記を踏まえたうえで、あらためて説明すると、LINQとは、データベースだけでなく、XMLやオブジェクトの入った配列とか、いわゆる「データソース」として存在するものに、SQLクエリ的な方法で同じようにアクセスすることを目的として実装された。らしい。

なので、アクセスするデータソースごとに、名前がいくつか分かれている。
(LINQ to SQL, LINQ to XML, LINQ to Entity, LINQ to Object …)
色々分かれてこそいるが、「同じようにアクセス」ことが目的なので、どれか覚えれば似たような感じで他も使えるっぽい。
XML と Objectしかつかったことないけど、確かに「検索」という点に関してはほぼ同じかなと思った。

LINQ to Object をざっくり知る

ここからは、どんな環境でも使えるし使うだろうLINQ to Objectについてざっくり説明する。
あくまで、「ざっくり」なので「分かってない」感はあるけど、まぁ、いいじゃないですかエンジンの構造をしらなくても車が運転できれば。

まず、LINQには二通りの書き方がある。
from Hoge in Moge select foo みたいなSQL式といういうのと
Moge.Select(hoge => hoge.foo) というメソッド形式がある。

多くのサイトや公式ではSQL式を前提としているが、私はなんとなくメソッド形式つかってます。
なので、ここで出てくる例は全部メソッド形式になります。

まず、すごい簡単な例を。

//  データソースを用意
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

//偶数だけゲットするクエリ式
var numQuery = numbers.Where(num => (num % 2) == 0);

//foreachでぐるぐる回して表示
foreach (int num in numQuery)
{
  Console.WriteLine(num);
}

配列から2で割って0になる偶数を表示していく例です。

ちなみに、従来だと以下のように書いていたと思います。

//従来だったら、以下のように書く
foreach (var num in numbers)
{
	if ((num % 2) == 0)
	{
		Console.WriteLine(num);
	}
}

これだけだと、便利どころか無駄にさえ感じる。しかもWhere(num => ( num % 2 ) == 0) なんて見慣れない式もでてきて、ラムダ式もSQLクエリもよく分かってないころに見たときは、「いやいい、俺は当分従来でいい。」と諦めた。

しかし、LINQの便利さは、クラスだったりを使う時に驚異的に便利になることが分かった。
ここからは、無理して覚えずに、「へぇ、そんなんできるだ。」で流し読みしてほしい。

まず、studentというクラスがあったとする。
氏名と性別と点数リストなクラス。

class Student
{
	public string First { get; set; }
	public string Last { get; set; }
	public byte Sex { get; set; }

	public List<int> Scores;
}

そして、適当にデータを入れる。

//適当にデータいれる
var studentList = new List<Student>();

var taro = new Student()
{
	First = "Taro",
	Last = "Yamada",
	Sex = 0,
	Scores = new List<int>() { 80, 75, 77 }
};
studentList.Add(taro);

var hanako = new Student()
{
	First = "Hanako",
	Last = "Yamada",
	Sex = 1,
	Scores = new List<int>() { 50, 65, 30 }
};
studentList.Add(hanako);

var ichiro = new Student()
{
	First = "Ichiro",
	Last = "Suzuki",
	Sex = 0,
	Scores = new List<int>() { 90, 99, 85 }
};
studentList.Add(ichiro);

んで、以下のように操作できる。

名字がYamadaさんだけ取得

var yamadas = studentList.Where(student => student.Last == "Yamada");

foreach (var m in yamadas)
{
	Console.WriteLine(m.Last + " " + m.First);
}
//Yamada Tro
//Yamada Hanako

Scoreの平均が80以上の人を取得

var av80over = studentList.Where(student => student.Scores.Average() > 80);
foreach (var m in av80over)
{
	Console.WriteLine(m.Last + " " + m.First);
}
//Suzuki Ichiro

過去に80点を一度も取ったことがない女性を取得

var foolHanako = studentList.Where(student => student.Scores.Where(point => point > 80).Count() == 0 && student.Sex == 1);
foreach (var m in foolHanako)
{
	Console.WriteLine(m.Last + " " + m.First);
}
//Yamada Hanako

最初の名前で絞り込むだけなら従来のやりかたでいいけど、条件が複数になったり、クラスの中にListを持ち、そのListの中身だったりが絡むと、foreachの中にforeachとループ分のネストになりかねないが、LINQであれば、SQLの条件で絞るような感覚で書けていると思う。

データベースじゃないのに、データベースのようにメモリ内のオブジェクトをいじれる。
もちろん並び替えもできる。(OrderByメソッドがある)
「過去に80以上を取ったことがある、ユーザーを平均点順に並べて」と言われたら、SQLなら想像がつくが、プログラムで書くとなると「え~っと」って考え込んていた。

SQLをある程度知っていると、プログラムでやるよりデータを取得するときにSQLで書いて取得しなおしたほうが、楽だし手っ取り早いと思ってたが、これを覚えた後だとどうだろう。

プログラマはifとforとwhileがあれば事足りると思っていたが、複雑なデータを扱う場合LINQは使うべきメリットがあると思う。
forやforeachが3つほどネストしたらLINQのほうが楽かも?

Whereだけ覚えとく

通常のSQLでもそうだけど、よく使うのは Where な感じです。
なので、まずWhereだけ覚えておけば、最低限の絞込は使える気がする。

まずは、何かしらデータ群となるものを、ベースに、Whereメソッドを呼び出す。
DictionaryやListなどとにかくIEnumerableを継承していれば、データソースとして使える。
IEnumer..なんだって?って思う人もいるかもだが、まぁ、「ふーん、よく分からんけどIEnumerableってやつを使っている系だったら使えるっぽいな。」でとりあえずよいかと。

では、先ほどの例のstudentListで考える。

studentListのWhereメソッドを呼び出す。(ListやDictionary等のIEnumerable継承系ならばWhereメソッドを持っているはず)
引数を適当に決める。関数の引数名なのでなんでもよい。(仮にmogeとする)
んで、bool値を返す条件式を入れる。(mogeの苗字がhogeならば真)
studentList.Where( moge => moge.Last == “hoge”)

といった感じで使う。

Whereの中にはジェネリックなデリゲートのFunc<T , Tresult> が使われており、StudentのListでWhereだと中身は bool Func<Student, bool>(Student arg) となり、Student型を引数に持ち、boolを返すデリゲートとなり、さらに型推論されているので、型名は不要でラムダ式で書いた結果~・・・・。

とにかく、Whereはコンテナの中身を引数にとって、bool値を返す条件式を入れればよい

極端だけど、studentList.Where( f => true ); こんなんでもよい。

また、前のラムダの記事で書いたように、1行だから省略されているけど、省略する前に戻せば、下記のような感じにもかける。

var girls = studentList.Where( 
	student => {
		bool result = false;
		if( student.Sex == 1)
		{
			result = true;
		}
		return result;
	}
);

これで、Whereはとりあえず使えると思います。

遅延実行

LINQ使う上で一応知っておかないといけない事がある。

LINQは遅延実行となる

LINQのクエリは、特定のメソッドを呼び出すか、foreachで呼ばれないと、みんなが想像する普通のオブジェクトにはならない。

例えば、さきほどのYamadaだけを取り出す関数をみる。

var testYamda = studentList.Where(student => student.Last == "Yamada");

型推論 var は、List<Student>を取得しているわけでなく、IEnumerable<Student>を取得する。

//こんなイメージを想像するがこれはエラー
List<Student> result = studentList.Where(student => student.Last == "Yamada");

//実際はIEnumerableが返っている
IEnumerable<Student> result = studentList.Where(student => student.Last == "Yamada");

何が違うかというと、IEnumerableは結果を求めるための計算式だけが入っており、計算が実行されないと、オブジェクトにならない。

何を言っているか意味がわからないかもしれいないし、俺も何を言っているか分からないんだが、まぁ、そういう物だとここは理解しようじゃないか。

なぜこのようなことをするのか、
理由は色々あるみたいだけど、主に省メモリ、省アクセス、省計算とか?
はやりのMVCで言うなら、実際にユーザーに表示させるViewになるまで、余計なメモリ消費をなくすことで、軽くなるとか?
大量のメモリを使えるクライアントサイドのアプリならともかく、みんなで共用するサーバーのメモリを節約することは確かに大事かも。

ちなみに、実際に計算されるトリガーは、ToArray()とToList()とToDictionary()とFirst()などなど。
先ほどの絞り込んだtestYamadaを実際のオブジェクトにするには以下のように書く。

Student[] realarr = testYamda.ToArray();

List<Student> reallist = testYamda.ToList();

Student firstYamada = testYamda.First();

//foreachで取り出した場合
foreach (Student s in testYamda)
{
    Console.WriteLine(s.Last + " " + s.First);
}

つまり、せっかくの遅延実行もこのトリガーで、即時実行させすぎると意味がない。
可能な限りIEnumerableのまま持ち回すのがいいようだ。

ざっくりだけど以上。

Selectとか他にもあるんだけど、とりあえずWhereで遊べば、あとはSQL的な感覚で覚えれる気がする。

 

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>