Meiryo’s blog

やってみて詰まったことを備忘録として残すブログ

【C#】LINQに慣れる1~LINQとは~

独習C#を読んでいてついにLINQの章まで来ました。
これもラムダ式同様、できる人が使う不気味な物体でしたが
慣れて行こうと思います。

LINQとは

従来は Listなどのコレクションを扱うときはforeachなどを使い、
データベースを扱うときはSQLを使い、
XMLを扱う時はDOMなどを使う
・・・と複数の手段を使い分ける必要がありました。
これらを統一の手法で操作できるようにするのがLINQです。

どうやらいい感じにデータを扱うことができるものらしいですね。

準備

データを扱うとのことなので
なにかしらデータが無いと試すことが出来ません。
こういうときに使うのは楽しいものが良いので
なにかいいものがないか探してみたところ、
下記のサイトを見つけました。
ゲーム機毎のタイトル一覧表 FC(NES) SFC(SMC) MD(SMD) GB N64 NDS PCE MSX
ここからお借りしたデータを加工してLINQをいじっていこうと思います。

加工したデータやデータを格納しておくクラスは
こちらにまとめたので
もしすぐLINQをいじりたいという人は使ってみてください。

LINQの構文

LINQにはクエリ構文メソッド構文の2つがあります。
クエリ構文はシンプルに書けるがLINQのすべての機能を使えず
メソッド構文は少し複雑になるけどLINQのすべての機能を使うことができるようです。
それぞれ使ってみます。

扱う対象のデータはこのようになっています。
f:id:meimaru:20200410192525p:plain
スーパーファミコンのソフトのデータですね。
約1400行あります。

クエリ構文

クエリ構文はSQLのような構文です。
使ってみたプログラムがこちら。

using System;
using System.Linq;

namespace Test.LINQ
{
    class SandBox
    {
        public static void Main(string[] args)
        {
            var sfcSoftTable = new SfcSoftTable();
            var softTitles = from val in sfcSoftTable.GetSoftTable()
                             where val.Title.StartsWith("ドラゴンクエスト")
                             orderby val.Price descending
                             select val.Title +"   価格:"+val.Price;

            foreach (var title in softTitles)
            {
                Console.WriteLine(title);
            }
        }

    }
}

from句で「どこのデータを使う」か、
where句で「どういう条件で絞り込む」か、
orderbyで「何を対象に並べ替える」か、
select句で「結果をどう出力する」か、
をそれぞれ指定しています。
このプログラムでは
「タイトルが「ドラゴンクエスト」で始まるものを
価格が高い順に並べ替えてタイトルと価格を出力する」
と指定しました。
実行結果がこちらになります。
f:id:meimaru:20200410195659p:plain

指定した通りに結果が出力されたことが確認できました。

ちなみにfrom句の後に出てきた謎の変数「val」ですが
これは範囲変数と言います
取り出したデータを一時的に格納する変数です。
名前はvalでなければいけないわけでなく
tempでもvでもなんでも良いです。

メソッド構文

メソッド構文とクエリ構文の2つがあると書きましたが
この2つは独立したものではなく、
メソッド構文を簡単にしたのがクエリ構文だそうです。
そのため、クエリ構文で表現できたらメソッド構文でも表現できます。

先程クエリ構文で書いたプログラムをメソッド構文で書いたのが
下記になります

using System;
using System.Linq;

namespace Test.LINQ
{
    class SandBox
    {
        public static void Main(string[] args)
        {
            var sfcSoftTable = new SfcSoftTable();
            //クエリ構文
            //var softTitles = from val in sfcSoftTable.SoftTable
            //                 where val.Title.StartsWith("ドラゴンクエスト")
            //                 orderby val.Price descending
            //                 select val.Title +"   価格:"+val.Price;

            //メソッド構文
            var softTitles = sfcSoftTable.SoftTable.Where(val => val.Title.StartsWith("ドラゴンクエスト"))
                                                   .OrderByDescending(val => val.Price)
                                                   .Select(val => val.Title + "   価格:" + val.Price);
            foreach (var title in softTitles)
            {
                Console.WriteLine(title);
            }
        }

    }
}

あんまり書き方は大きく変わらないようですね。
ラムダ式になっただけという感覚です。

遅延実行と即時実行

これはLINQを使う上で知っておかないといけないことのようです。
遅延実行ってなんなのかを確認するコードを書いてみました。

using System;
using System.Linq;

namespace Test.LINQ
{
    class SandBox
    {
        public static void Main(string[] args)
        {
            var sfcSoftTable = new SfcSoftTable();

            var softTitles = sfcSoftTable.SoftTable.Where(val => val.Title.StartsWith("ドラゴンクエスト"))
                                                   .OrderByDescending(val => val.Price)
                                                   .Select(val => val.Title + "   価格:" + val.Price);

            var dq3 = sfcSoftTable.SoftTable.Single(val => val.Title.StartsWith("ドラゴンクエストⅢ"));
            dq3.Title = "ドラゴンクエストⅢ そして伝説へ…(更新済み)";

            foreach (var title in softTitles)
            {
                Console.WriteLine(title);
            }

        }
    }
}

書いたこととしては
①これまでと同様に「ドラゴンクエスト」から始まる
タイトルのソフトを見つけてsoftTitlesに入れる
②sfcSoftTableからドラゴンクエストⅢを探し、タイトルを変更
③softTitlesの内容を出力
です。
感覚としてはsfcSoftTableに変更が入っただけで
softTitlesには影響がなさそうに見えますが・・・
実行結果はこうなります。
f:id:meimaru:20200523112440p:plain

softTitlesに変更が適用されています。
これが遅延実行です。
最初にデータソース(sfcSoftTable)にアクセスして
データを取ってきてると思いきや
データが必要になった時に(foreach文)取ってきてるという動きをしています。

遅延せずに値を取得したい場合は即時実行の性質をもつメソッドを使います。
この性質をもつメソッドはCountやSum,Maxなど色々ありますが
その中で今回はToList()を使ってみました。

using System;
using System.Linq;

namespace Test.LINQ
{
    class SandBox
    {
        public static void Main(string[] args)
        {
            var sfcSoftTable = new SfcSoftTable();

            var softTitles = sfcSoftTable.SoftTable.Where(val => val.Title.StartsWith("ドラゴンクエスト"))
                                                   .OrderByDescending(val => val.Price)
                                                   .Select(val => val.Title + "   価格:" + val.Price);

            var immediateExecution = sfcSoftTable.SoftTable.Where(val => val.Title.StartsWith("ドラゴンクエスト"))
                                                   .OrderByDescending(val => val.Price)
                                                   .Select(val => val.Title + "   価格:" + val.Price)
                                                   .ToList();

            var dq3 = sfcSoftTable.SoftTable.Single(val => val.Title.StartsWith("ドラゴンクエストⅢ"));
            dq3.Title = "ドラゴンクエストⅢ そして伝説へ…(更新済み)";

            Console.WriteLine("遅延実行");
            foreach (var title in softTitles)
            {
                Console.WriteLine(title);
            }

            Console.WriteLine("-------------------------------------");
            
            Console.WriteLine("即時実行");
            foreach (var title in immediateExecution)
            {
                Console.WriteLine(title);
            }
        }
    }
}

f:id:meimaru:20200523123345p:plain
即時実行のメソッドを使った方では変更の影響を受けていないことが
確認できました。

LINQの基本がわかったので
次回はLINQのいろんなメソッドを使ってみます。

次回はこちらになります。