ワテの場合、昔はラムダ式とかLINQとかの記述方法は馴染めなかった。
例えばC#のLINQはこんなやつだ。
List<int> intLst = new List<int>() { 1, 2, 3, 6, 4, 2, 7, 3 }; // ごく普通のリスト var intLst_result = intLst.Where(item => (item >= 3)).ToList(); // これがLINQ for (int i = 0; i < intLst_result.Count; i++) // LINQの実行結果を表示 { Console.WriteLine("intLst_result[{0,-2}] = {1,3}", i, intLst_result[i]); }
その実行結果は以下の通り。
intLst_result[0 ] = 3 intLst_result[1 ] = 6 intLst_result[2 ] = 4 intLst_result[3 ] = 7 intLst_result[4 ] = 3 続行するには何かキーを押してください . . .
コードの説明
ごく普通のC#の整数型のリストを定義する。
この例では初期値として8個の整数を与えている。
List<int> intLst = new List<int>() { 1, 2, 3, 6, 4, 2, 7, 3 }; // ごく普通のリスト
次の行がLINQ式だ。
var intLst_result = intLst.Where(item => (item >= 3)).ToList(); // これがLINQ
最初に定義したintLstと言うリストに対して、
Where ToList
などのメソッドをズラズラと連結して元データ(intLst)を加工して行く。
この例では、リストの要素のうち 3以上の値を持つ物のみを新しいリストにする。
その加工結果を左辺の intLst_result に代入して完了する。
LINQを使わない場合には、forループで数行のコードを書けば出来る事は皆さんもお分かりだろう。
さて、そんなLINQをワテの場合は最近では使いまくっている。
昔はLINQ嫌いだったのだが、今は大好き!
その辺りの理由も説明しながら、今回はワテの備忘録も兼ねて幾つかのサンプルLINQ式を紹介しよう。
では、本題に入ろう。
LINQは式
先ほど紹介したLINQ式は以下の通り。
var intLst_result = intLst.Where(item => (item >= 3)).ToList(); // これがLINQ
ここで注意すべき点は、右辺は一つの式なのだ。
右辺の intLst. まで入力するとVisual Studio 2017 Community のインテリセンスが働いて、利用出来るメソッドの候補が沢山出て来る。
Add AddRange Aggregate All<> Any<> AsEnumerable AsParallel AsParallel<> AsQueryable ... Select ... toList ... Where ...
それらを連結して一つのLINQ式を作る。
さて、そんなLINQ式が苦手な人も多いだろう。
その辺りの理由を見てみよう。
LINQ式のデメリット
ネットの掲示板などを見ていると、LINQを嫌いな人が言う最も大きな理由は、
「LINQの右辺が複雑で何をやっているのか分からない。」
と言うものが多いと思う。
或いは、「他人が書いたLINQはサッパリ分からない。」
なども嫌いな理由にあげられる。
まあ、ワテの場合も昔はLINQ嫌いだったのだが、その一番の理由は大勢の皆さんと同じく、LINQが分りにくいと言う点であった。
特にLINQを覚え立ての頃は、自力では書けないので、ネット上のサンプルコードを組み合わせて必死で目的のLINQ処理をしていた。
無事にLINQ式を書く事が出来ても、一、二週間後にそのコードを見た時に、何をやっているのか分からない。
自分で書いたコードなのに分からない。
数十分掛けて解読して思い出してようやく理解出来た。
そんな経験が良く有った。そう言う経験が何度か有ったので、自然とLINQ嫌いになってしまった。
ところが今は、LINQ大好き。
書けるところは全部LINQで書くみたいな感じで、LINQを使いまくっている。
LINQで書けないと負けた気分になる事さえある。
何故か?
その理由を説明しよう。
LINQ式のメリット
例えば先ほどのLINQ式を従来式のforループで書くとこんな感じか。
var intLst_result2 = new List<int>(); for (int i = 0; i < intLst.Count; i++) { var item_i = intLst[i]; if (item_i >= 3) { intLst_result2.Add(item_i); } }
forループではなくてforeachループを使うのを好む人も多いがワテの場合はインデックス式のforループが好きだ。
まあ、それは今の議論とは無関係なので本題に戻ると、ワテがこのようなforループでデータを加工するのではなくて、LINQ式を使うのが好きな理由は何かと言うと、要するにLINQは一つの式なのだ。
つまり、LINQ式でメソッドを連結して徐々にデータを加工して行き、目的の結果が得られたら、その式の中に間違い(バグ)が入る可能性は少ない。なぜならLINQ式で使えるメソッドは限られているので、そのメソッドでデータを加工して行けばヘンテコなロジックを書いてしまう可能性が少ないからだ(拡張メソッドを使えばメソッドは増やせるが、その場合でもメソッドを連結してデータを加工して行くと言うLINQの手法が変わる事は無いし)。
一方、forループで書く場合は数行あるいは数十行のコードの組み合わせとなる。
上で紹介したような短いforループ処理程度であれば、間違いが入り込む可能性は少ないが、数十行くらいに渡ってズラズラとコードを書いていると、ウッカリミスなどで本来予定しているロジックでは無くなり、得られる結果データが予想外の結果になるなどもある(と思う)。
と言う訳で、ワテの場合には、多少ややこしいLINQ式であっても、一旦正しく動く事がデバッグなどで確認出来れば、その部分はほぼ間違いなく正しく動いていると言う安心感みたいなのを持てるようになった。
そう言う点で、最近のワテはLINQが好きなのだ。
LINQも見慣れると違和感が無くなる
上のデメリットの項目で、LINQの欠点として、あとで見直した時に何の処理をやっているのか分り辛いと言うのを挙げた。
ところが、LINQをバリバリ書き捲っていると、意外と慣れて来る。
ネット検索して各種のLINQのサンプルを見ても拒絶反応みたいなのが無くなり、逆に、こんなやり方が有ったのか!みたいな感動もある。
つまりまあ、何事も食わず嫌いは良くないと言う事か。
据え膳食わぬは男の恥
LINQ使わぬはプログラマーの恥
そんな感じか?
ちょっと違うか?
ちなみに、類義語で、こんな諺もあるらしい。
据え膳と河豚汁を食わぬは男の内ではない
(すえぜんとふぐじるをくわぬはおとこのうちではない)
そんな諺が有るなんて知らなんだ。
一度も見た事も聞いた事も無いぞ?
ワテの場合、理系なので文系の話題には弱い。
まあ、そんな話題はどうでも良いので、次に進もう。
List<List<string>>をDictionary<string, string> に変えてみる
まあ、あまり使いそうに無い例であるが、ワテの場合、こんなリストをディクショナリーに変換したい場面に遭遇した。
List<List<string>> strLstLst = new List<List<string>> { new List<string>(){"BANANA","1"}, new List<string>(){"ORANGE","48"}, new List<string>(){"KIWI","5"}, };
そこで即席で、変換関数をLINQで作ってみた。
static Dictionary<string, string> ConvertListList_to_Dictionary(List<List<string>> strLstLst) { Dictionary<string, string> dic = strLstLst.ToDictionary(x => x.ElementAt(0), x => x.ElementAt(1)); return dic; }
実行してみよう。
Dictionary<string, string> dic = ConvertListList_to_Dictionary(strLstLst); foreach (var kvp in dic) { Console.WriteLine("Key = ->{0,-9}<-, Value = ->{1,9}<-", kvp.Key, kvp.Value); }
一行目でList<List<string>>からDictionary<string,string>への変換関数をコール。
そして、結果を表示しているだけの簡単なコードだ。
その実行結果は以下の通り。
Key = ->BANANA <-, Value = -> 1<- Key = ->ORANGE <-, Value = -> 48<- Key = ->KIWI <-, Value = -> 5<-
まあ、こんな感じでDictionary<string, string> に変換出来た。
この場合、入力のリストには重複するキーは無いと言う前提なので、変換関数の中でもし重複キーが有った場合の対策などは施していない。
では、もう少し実用性の有りそうなLINQをやってみよう。
List<T>をGroupBy()で集約してDictionary化する
例えば衆議院議員の名簿から適当に選んだ議員さんのお名前と選挙区をリストにしてみた。
ID番号には深い意味は無い。適当にワテが振った整数値だ。
class Class2 { public class DietMember // 国会議員クラス { public int ID; public string Name; public string Party; public string Prefecture; } public void Test2() { // 衆議院議員一覧 var MemberList = new List<DietMember>() { new DietMember() {ID=11, Name="大西宏幸", Party="自由民主党", Prefecture="大阪" }, new DietMember() {ID=12, Name="原田憲治", Party="自由民主党", Prefecture="大阪" }, new DietMember() {ID=13, Name="岸本周平", Party="希望の党" , Prefecture="和歌山"}, new DietMember() {ID=14, Name="若宮健嗣", Party="自由民主党", Prefecture="東京" }, new DietMember() {ID=15, Name="平沢勝栄", Party="自由民主党", Prefecture="東京" }, new DietMember() {ID=16, Name="石田真敏", Party="自由民主党", Prefecture="和歌山"}, new DietMember() {ID=17, Name="長島昭久", Party="希望の党" , Prefecture="東京" }, }; //(1a) 都道府県別に政党を分類する Dictionary<string, List<string>> linqLambda_Dic1 = MemberList .GroupBy(x => x.Prefecture, x => x.Party) .ToDictionary(x => x.Key, x => x.ToList()); //(2a) 都道府県別にリストを分類する Dictionary<string, List<DietMember>> linqLambda_Dic2 = MemberList .GroupBy(x => x.Prefecture) .ToDictionary(x => x.Key, x => x.ToList()); //(3a) 都道府県別と政党別にリストを分類する var linqLambda_Dic3 = MemberList .GroupBy(x => new { x.Prefecture, x.Party }) .ToDictionary(x => x.Key, x => x.ToList()); } }
このように定義した国会議員リストに対して、三種類のLINQ式を作ってみた。
以下では、そのLINQ式を一つずつ見て行こう。
やっている内容としては、GroupByを使って元の国会議員リストを集計して別のデータに加工する処理だ。
都道府県別(Prefecture)にGroupByして政党名(Party)リストのDictionaryを作成
(1a) 都道府県別に政党を分類するのLINQコードは以下の通り。
//(1a) 都道府県別に政党を分類する Dictionary<string, List<string>> linqLambda_Dic1 = MemberList .GroupBy(x => x.Prefecture, x => x.Party) .ToDictionary(x => x.Key, x => x.ToList());
GroupByの詳しい説明は、マイクロソフト社のこの辺りのページを見ると良いだろう。
Enumerable.GroupBy メソッド
Enumerable.GroupBy メソッド (System.Linq)シーケンスの要素をグループ化します。
ワテ流に説明するなら、
GroupBy(x=> このメンバでグループ化, x=> このメンバを取り出す)
と言う感じか。
なので、国会議員リストを県別にグループ化して、政党名を取り出す。
このLINQ式で生成されるDictionary
Dictionary<string, List<string>> linqQuery_Dic1
の中身は以下の通り。
linqLambda_Dic1 Count = 3 [0] Key "大阪" string Value Count = 2 [0] "自由民主党" string [1] "自由民主党" string [1] Key "和歌山" string Value Count = 2 [0] "希望の党" string [1] "自由民主党" string [2] Key "東京" string Value Count = 3 [0] "自由民主党" string [1] "自由民主党" string [2] "希望の党" string
生成されたDictionaryはKeyが都道府県名、Valueは政党名のリスト。
あまり良い例では無かったかな。
GroupByに第二パラメータを、
.GroupBy(x => x.Prefecture, x => x.Party)
以下のように議員さんの名前にすれば、
.GroupBy(x => x.Prefecture, x => x.Name)
各都道府県をキーにして、議員さんの名前リストを値に持つディクショナリーが生成出来る。
まあ、そっちのほうが分かり易いだろう。
都道府県別(Prefecture)にGroupByして議員リストのDictionaryを作成
次の例は、先ほどのコードを再び掲載すると以下のコードを実行したものだ。
//(2a) 都道府県別にリストを分類する Dictionary<string, List<DietMember>> linqLambda_Dic2 = MemberList .GroupBy(x => x.Prefecture) .ToDictionary(x => x.Key, x => x.ToList());
このコードでは、GroupByで x.Prefectureのみをパラメータに与えている。
その実行結果は以下の通り。
linqLambda_Dic2 Count = 3 [0] Key "大阪" string Value Count = 2 [0] ID 11 int Name "大西宏幸" string Party "自由民主党" string Prefecture "大阪" string [1] ID 12 int Name "原田憲治" string Party "自由民主党" string Prefecture "大阪" string [1] Key "和歌山" string Value Count = 2 展開すると二人の議員情報が入ってる [2] Key "東京" string Value Count = 3 展開すると三人の議員情報が入ってる
この例では、先ほどの例と同じく、都道府県名でGroupByして、Keyが都道府県名、Valueが議員さん情報を持つDietMemberクラスのリストだ。
都道府県と政党別にGroupByして議員リストのDictionaryを作成
//(3a) 都道府県別と政党別にリストを分類する var linqLambda_Dic3 = MemberList .GroupBy(x => new { x.Prefecture, x.Party }) .ToDictionary(x => x.Key, x => x.ToList());
実行すると結果は以下の通り。
「大阪」の「自民党」の議員さんのリスト
「東京」の「希望の党」の議員さんのリスト
などが生成出来る。
linqLambda_Dic3 Count = 5 [0] Key { Prefecture = "大阪", Party = "自由民主党" } Value Count = 2 [0] ID 11 int Name "大西宏幸" string Party "自由民主党" string Prefecture "大阪" string [1] ID 12 int Name "原田憲治" string Party "自由民主党" string Prefecture "大阪" string [1] 展開すると何人かの議員情報が入ってる [2] 展開すると何人かの議員情報が入ってる [3] 展開すると何人かの議員情報が入ってる [4] 展開すると何人かの議員情報が入ってる
GroupByの使い方の例としては分かり易いと思う。
さて、今まで見て来た例は、LINQのメソッド(SelectとかWhereなど)をドットで連結する手法であった。
ところが、LINQにはもう一つ別の種類の記述方法がある。
LINQでのクエリ構文とメソッド構文
今まで見て来たような、LINQのメソッドをドットで連結する記述方法は、メソッド構文と言うらしい。
参考サイト
このメソッド構文のLINQは、ワテの理解ではラムダ式LINQと呼んでも良いのかな?
一方、データベースを操作する時のSQL文のように記述する方式のLINQは上のマイクロソフトのサイトによるとクエリ構文と言うらしい。
ワテも今日初めて知った。
先ほど紹介した三つのメソッド構文LINQをクエリ構文LINQで書いてみた。
クエリ構文LINQで書き換えた例
//(1b) 都道府県別に政党を分類する Dictionary<string, List<string>> linqQuery_Dic1 = ( from member in MemberList group member.Party by member.Prefecture ).ToDictionary(g => g.Key, g => g.ToList()); //(2b) 都道府県別にリストを分類する Dictionary<string, List<DietMember>> linqQuery_Dic2 = ( from member in MemberList group member by member.Prefecture ).ToDictionary(g => g.Key, g => g.ToList()); //(3b) 都道府県別と政党別にリストを分類する var linqQuery_Dic3 = ( from member in MemberList group member by new { member.Prefecture, member.Party } ).ToDictionary(g => g.Key, g => g.ToList());
こんな感じか。
このクエリ構文LINQを実行すると、最初に紹介したメソッド構文LINQの方式と同じ結果になると思う。
その辺りはVisual Studio 2017 Communityのデバッガを使ってブレークポイントで停止してみて、ローカルウインドウの中で各種の変数の中身を見てみると良いだろう。
ちなみに、ワテの場合には、後者のクエリ構文LINQにはまだ余り慣れていないので、主に使うのはメソッド構文LINQだ。
まあそっちのほうが、短く書けるのでそう言う点でもワテ好みだ。
まとめ
当記事では、Visual Studio 2017のC#において、LINQを使う簡単なサンプルプログラムを紹介した。
LINQには、
- メソッド構文LINQ
- クエリ構文LINQ
の二種類がある。
メソッド構文LINQとは、ドット . でメソッドを連結して記述する方式
クエリ構文LINQとは、SQL文のようにクエリコマンドを記述する方式
だ。
まあ、どっちのLINQでも良いので、使ってみると良いだろう。
ワテの場合にも、最初はLINQには馴染めなかったのだが、使い始めるととっても便利。
今では、forループでグルグル回すなんて言う従来式の記述方法では無くて、LINQで書ける処理は兎に角LINQで書くようになった。
ワテの場合、forループで数十行にも渡るような複雑な処理をLINQ式で記述出来るとある種の快感があるwww
まさにLINQ症候群あるいはLINQ中毒と言う感じ。
LINQを使わないと禁断症状が出る事さえある。なんでやねん。
皆さんにもお勧めしたい。
本でLINQを勉強する
LINQの和文解説書は少ない。
これなんか良いかも。ただしワテは読んでいない。理由は、まだ買っていないからだ。
次に紹介する商品は、本では無くて音楽だ。
プログラミングのLINQとの関連性は未確認だ。
さて、C#を勉強するなら、ワテのお勧めはこれだ。
何と言っても著者はプログラミング関連の解説書で有名な山田 祥寛(ヤマダ ヨシヒロ)さんだ。当記事を執筆した時点で、この本は発行されたばかりの新書のようで、アマゾンのレビューは0件だった。でもまあ有名な山田さんの本だから買っても間違いは無いと思う。
あるいはVisual Studioの使い方も習いたいなら、こんな本もある。
こう言う本を一冊じっくり読んで勉強すると、効率が良い。
本を買わずにネットのサンプルプログラムを実行しようとして、Visual StudioでC#プロジェクトも作らずに、
[ファイル]
[新規ファイル追加]
でテキストファイルを追加して、そこに適当にコードを入力しても、実行出来ないんですが?
と5chの掲示板で質問をしている人を見た事がある。
まあ、それは実行出来ないわな。
プログラミングを勉強するなら、やっぱりちゃんとした教科書を一冊は読むべきだろう。
コメント