[C#] 29. Linqクエリ式を使い方


Study / C#    作成日付 : 2019/07/17 20:57:00   修正日付 : 2021/09/14 20:56:04

こんにちは。明月です。


この投稿はC#のLinqクエリ式を使い方に関する説明です。


以前の投稿でLinq式に関して簡単に説明したことがあります。

link - [C#] 28. リスト(List)とディクショナリ(Dictionary)、そしてLinq式を使い方


また、簡単に説明するとLinqとはプログラム上で設定されたオブジェクトの集合、つまりリスト(List)やディクショナリ(Dictionary)で設定されたデータを効果的に分類及び検索するためのC#のプログラム文法です。

Linqを作成する方法はクエリ式と関数式がありますが、その中でクエリ式はデータベースで使うSQLクエリ式と似ている方法です。


基本的に使う方法はfrom in where selectです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 例クラス
  class Node
  {
    // 値をコンストラクタで格納する。
    public Node(int data)
    {
      // プロパティのDataに値を格納
      this.Data = data;
    }
    // Dataプロパティ
    public int Data
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // リスト宣言(リストのオブジェクトはNodeクラス)
      var list = new List<Node>();
      // iが0から9まで繰り返し
      for (int i = 0; i < 10; i++)
      {
        // リストにデータを挿入
        list.Add(new Node(i));
      }
      // NodeクラスのData値が6以上のインスタンスをList<int>タイプで分類する。
      var filterList = from node in list where node.Data > 5 select node.Data;
      // フィルターされたリストを繰り返しで抽出
      foreach (var node in filterList)
      {
        // コンソール出力
        Console.WriteLine(node);
      }

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


まず、from inに関して説明するとinの後の変数は検索しようと思うリストの変数です。

そしてforeachの繰り返し文みたいに一つのオブジェクトに置換することがfromの後の変数です。

つまり、foreach(Node node in list)がfrom node in listと同じ意味になります。


whereは繰り返し文の中で条件を作ることでnode.Data > 5はif(node.Data > 5)と同じ意味です。

selectはreturnされた結果により最終のfilterListのデータタイプが決められます。select nodeをしたらIEnumerable<Node>のタイプになりますが、上の例ではnode.DataをしたのでIEnumerable<int>タイプで生成されます。


ここで参考にIEnumerableのインタフェースはListの親インタフェースです。


つまり、Listみたいに使えるインタフェースですが、正確にはイテレータパターン(iterator pattern)のインタフェースです。

つまり、Listと似てますが、AddやRemoveみたいにデータを追加、削除をできなく、foreachでデータを取得することしかできないです。


参考でLinqで抽出した結果にデータを追加、削除するためにはToList()関数でListタイプにキャストして使ったら良いです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 例クラス
  class Node
  {
    // 値をコンストラクタで格納する。
    public Node(int data)
    {
      // プロパティのDataに値を格納
      this.Data = data;
    }
    // Dataプロパティ
    public int Data
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // リスト宣言(リストのオブジェクトはNodeクラス)
      var list = new List<Node>();
      // iが0から9まで繰り返し
      for (int i = 0; i < 10; i++)
      {
        // リストにデータを挿入
        list.Add(new Node(i));
      }
      // NodeクラスのData値が6以上のインスタンスをList<int>タイプで分類する。
      var filterList = from node in list where node.Data > 5 select node.Data;
      // 上のクエリ式の結果IEnumerableタイプからListタイプにキャスト
      var newList = filterList.ToList();
      // select node.DataによりList<int>タイプで設定したので、Nodeインスタンスを入れることではない。
      newList.Add(1);
      // 新しいリストを繰り返し文で抽出
      foreach (var node in newList)
      {
        // コンソール出力
        Console.WriteLine(node);
      }

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


また、クエリ式に戻って、実務プログラムを開発するとfrom where selectが一番よく使います。

その以外にorderbyがあるし、join、letをよく使います。

orderbyはデータの整列する式だし、joinは二つのリストを合併する式です。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 例クラス
  class Node
  {
    // 値をコンストラクタで格納する。
    public Node(int key, string value)
    {
      // プロパティに値を格納
      this.Key = key;
      this.Value = value;
    }
    // Keyプロパティ
    public int Key
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
    // Valueプロパティ
    public string Value
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // リスト宣言(リストのオブジェクトはNodeクラス)
      var list1 = new List<Node>();
      var list2 = new List<Node>();
      // iが0から9まで繰り返し
      for (int i = 0; i < 10; i++)
      {
        // リストにデータを挿入
        list1.Add(new Node(i, "List1:" + i));
        list2.Add(new Node(i, "List2:" + i));
      }
      // list1の基本リストとlist2の6以上の結果値のnode2で置換してKeyでマッピングして結果をnode3に生成する。
      // node1のキーで降順で整列して
      // 結果をまた、Nodeインスタンスを生成してIEnumerableでリターンする。
      // valueの値はnode3でマッピングしない値、つまり0から5までの値は空のstringを格納してその以外はnode3のvalue値を格納する。
      var filterList = from node1 in list1
               join node2 in (from sub in list2 where sub.Key > 5 select sub) on node1.Key equals node2.Key into node3
               orderby node1.Key descending
               select new Node(node1.Key, node3.ToArray().Length > 0 ? node3.ToArray()[0].Value : "");
      // フィルターされたリストを繰り返しで抽出
      foreach (var item in filterList)
      {
        // コンソール出力
        Console.WriteLine("Key - " + item.Key + " Value - " + item.Value);
      }

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


Linq式のJoinはデータベースのouter joinと似てます。

つまり、最初のlist1を基準でlist2をjoinして結果をintoのキーワードでnode3に作成します。joinになるデータで値がなければ0個のIEnumerableデータで、あれば1個のIEnumerableデータでnode3に置換します。

そして順番をorderbyに通ってlist1基準の降順に再整列します。

その結果をselectで新しいNodeインスタンスで再生成します。


コンソール結果をみれば降順で出力するし、0から5までのデータはマッピングする値がないので空白stringで出力します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example
{
  // 例クラス
  class Node
  {
    // 値をコンストラクタで格納する。
    public Node(int key, string value)
    {
      // プロパティに値を格納
      this.Key = key;
      this.Value = value;
    }
    // Keyプロパティ
    public int Key
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
    // Valueプロパティ
    public string Value
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
    // クラスのStringをリターン
    public override string ToString()
    {
      return "Key - " + Key + " Value - " + Value;
    }
  }
  // Groupされたクラス
  class Group
  {
    // 値をコンストラクタで格納する。
    public Group(int key, IEnumerable<Node> value)
    {
      // プロパティに値を格納
      this.Key = key;
      this.Value = value;
    }
    // Keyプロパティ
    public int Key
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
    // Valueプロパティ
    public IEnumerable<Node> Value
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
    // クラスのStringをリターン
    public override string ToString()
    {
      // リストになっているvalueデータのString値を作成
      var sb = new StringBuilder();
      foreach (var item in Value)
      {
        sb.AppendLine(item.ToString());
      }
      return sb.ToString();
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // リスト宣言(リストのオブジェクトはNodeクラス)
      var list = new List<Node>();
      // iが0から9まで繰り返し
      for (int i = 0; i < 10; i++)
      {
        // リストにデータを挿入
        list.Add(new Node(i, "List:" + i));
      }

      // 偶数、奇数別でグループを分けてそのキーでNodeを再整列する。
      var filerList = from node in list group node by node.Key % 2 into g select new Group(g.Key, g);
      // filerListのキー順番とおりに繰り返し
      foreach (var item in filerList)
      {
        // コンソール出力
        Console.WriteLine("Group Key - " + item.Key);
        Console.WriteLine(item.ToString());
      }

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例はリストをグループ化しました。

まず、groupはグループする対象を設定してbyによりキーを設定します。node.Key % 2ですべてリストに出られる場合の数は、0と1です。

つまり、0と1のリストを作って0になる値のlistと1になる値のリストを作りました。その結果をGroupというクラスのインスタンスを生成してまた、0と1のIEnumerableタイプで生成します。

結果をみれば0と1でグループになることを確認できます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example
{
  // 例クラス
  class Node
  {
    // 値をコンストラクタで格納する。
    public Node(int data)
    {
      // プロパティのDataに値を格納
      this.Data = data;
    }
    // Dataプロパティ
    public int Data
    {
      // 入力はコンストラクタから受け取る。
      get; private set;
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // リスト宣言(リストのオブジェクトはNodeクラス)
      var list = new List<Node>();
      // iが0から9まで繰り返し
      for (int i = 0; i < 10; i++)
      {
        // リストにデータを挿入
        list.Add(new Node(i));
      }

      // letは変数の再定義
      var filerList = from node in list let data = node.Data * 100 select new Node(data);
      // filerListのキー順番とおりに繰り返し
      foreach (var item in filerList)
      {
        // コンソール出力
        Console.WriteLine(item.Data);
      }

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上のクエリ式ではletを使いました。

letのキーワードはデータを再定義する式です。つまり、from node in listで再定義したnodeのNodeクラスのインスタンスをletによりnode.Dataの値に100を掛けてint値で再置換します。

それをまた、selectに通ってNodeクラスのインスタンスを生成してリストで作ります。


結果はnode.Dataが100に掛けて、Nodeのインスタンスリストで結果が出ることを確認できます。


Linqのクエリ式はMSの.net framework Docで説明しているので、参照したら良いです。

link - https://docs.microsoft.com


Linqクエリ式をよく使ったら、プログラムを作成する時、ソースのステップを減らすこともできるし、検証されたクエリ式なのでユーザが作成したアルゴリズムより良い性能で使えます。

もちろん、それがクエリをしっかり使う場合です。不必要なjoinとfrom、letによりデータ合併、置換、そして間違いwhereが逆に性能が悪くなります。

そしてまた、クエリ式は普通のプログラムコード式ではないので、近すぎなら、ソース読みにくい(可読性低下)し、リストのフィルター、Join,置換状態のデバッグを確認できないので、開発するところで不便なことが多いです。

そして、逆に実務開発環境ではLinqのクエリ式より関数式をよく使います。


ここまでC#のLinqクエリ式を使い方に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿