LINQ to Objectsの備忘録です。LINQの特徴であるDeferred Query Evaluation(遅延クエリ評価)ですが、テクノロジ的なことについて詳細は記載していません。また、拡張メソッドのオーバーライド版を使用すると、比較、抽出処理などをカスタマイズすることができますが、今回の記事には記載していません。拡張メソッドのより詳細な使い方は書籍、他サイト、MSDNをご参考ください。

確認環境

  • Windows Vista Enterprise
  • 開発環境: Visual Studio 2008 Professional
  • .NET 3.5

LINQ to Objects備忘録のリンクを掲載しておきます。

今回取り扱っているクエリ式,拡張メソッド

クエリ式,拡張メソッド 簡単な説明
Where句,Where拡張メソッド 選択を行います。
Select句,Select,SelectMany拡張メソッド 射影演算を行います。
Order句,OrderBy,OrderByDeescending,
ThenBy,ThenByDescending拡張メソッド
ソートを行います。
Reverse拡張メソッド 要素の集合を反転した要素を作成します。
let句 クエリ式のエイリアスを定義します。

0.データ準備

クエリ式を実行するためのクラスとデータを作成するメソッドを定義します。

サンプルで使用するクラスCategory,Articleの定義です。CategoryにArticleが紐づくようにしてあります。

public enum ArticleType
    {
        Code = 0,
        Memo = 1
    }
    public class Category
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public IList<Article> Articles { get; set; }
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(string.Format("Category:ID={0}, Name={1}\n", ID, Name));
            foreach (var article in Articles)
            {
                builder.Append("  " + article.ToString() + "\n");
            }
            return builder.ToString();
        }
    }
    public class Article
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public DateTime ReleaseDate { get; set; }
        public ArticleType ArticleType { get; set; }

        public override string ToString()
        {
            return string.Format("Article:ID={0}, Title={1}, ReleaseDate={2:d}, ArticleType={3}",
                                   ID, Title, ReleaseDate, ArticleType);
        }
    }

 データを初期化するために制的メソッドが用意されているとします。

public static IList<Category> InitializeData()
        {
            List<Category> list = new List<Category>();
            //こんな風に追加もできます。
            //list.Add(new Category() { ID = 1, Name = "CS", Articles = new List<Article> { new Article { ID = 1 } } });
            Category c1 = new Category { ID = 1, Name = "CS", Articles = new List<Article>() };
            c1.Articles.Add(new Article { ID = 1, Title = "CSArticle1", ReleaseDate = new DateTime(2004, 1, 1), ArticleType = ArticleType.Code });
            c1.Articles.Add(new Article { ID = 2, Title = "CSArticle2", ReleaseDate = new DateTime(2004, 2, 1), ArticleType = ArticleType.Memo });
            list.Add(c1);
            Category c2 = new Category { ID = 2, Name = "ASP", Articles = new List<Article>() };
            c2.Articles.Add(new Article { ID = 3, Title = "CSArticle1", ReleaseDate = new DateTime(2004, 1, 1), ArticleType = ArticleType.Code });
            c2.Articles.Add(new Article { ID = 4, Title = "AspArticle1", ReleaseDate = new DateTime(2002, 12, 21), ArticleType = ArticleType.Memo });
            list.Add(c2);

            Category c3 = new Category { ID = 3, Name = "SQL", Articles = new List<Article>() };
            c3.Articles.Add(new Article { ID = 5, Title = "SqlArticle1", ReleaseDate = new DateTime(2006, 2, 3), ArticleType = ArticleType.Code });
            list.Add(c3);

            Category c4 = new Category { ID = 4, Name = "CS", Articles = new List<Article>() };
            c4.Articles.Add(new Article { ID = 6, Title = "SqlArticle1", ReleaseDate = new DateTime(2008, 10, 3), ArticleType = ArticleType.Memo });
            list.Add(c4);
            return list;
        }

 

1. LINQクエリメモ

1.1 LINQのクエリ式と拡張メソッド

クエリ式は拡張メソッドに変換されることを理解しているとLINQのクエリ式を理解しやすくなると思います。

例えば次のクエリ式

IList<Category> categories = InitializeData();
var categoriesNamedCS = from c in categories
                        where c.Name == "CS"
                        select c.Name;
foreach (var c in categoriesNamedCS)
{
    Console.WriteLine(c.ToString());
}

は次のような拡張メソッドに変換されます。

var categoriesNamedCS2 = categories.Where(c => c.Name == "CS").Select(c => c.Name);
foreach (var c in categoriesNamedCS2)
{
     Console.WriteLine(c.ToString());
}

1.2 Where演算子

抽出を行うwhere演算子のサンプルです。whereに該当する拡張メソッドはWhereです。複数の抽出条件がある場合はAND演算子&&,OR演算子||を使用できます。

var categories = InitializeData();
var query =
    from c in categories
    where c.ID < 3 || c.Name == "CS"
    select c;
foreach (var item in query)
{
    Console.WriteLine(item);
}

実行すると次のように出力されます。

Category:ID=1, Name=CS
  Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo

Category:ID=2, Name=ASP
  Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo

Category:ID=4, Name=CS
  Article:ID=6, Title=SqlArticle1, ReleaseDate=2008/10/03, ArticleType=Memo

1.3 射影演算Select,SelectMany拡張メソッド

selectを使うサンプルです。selectに該当する拡張メソッドはSelectです。名前のみのリストを作成しています。

var categories = InitializeData();
var query =
    from c in categories
    where c.ID < 3
    select c.Name;

foreach (var item in query)
{
   Console.WriteLine(item);
}

処理結果は次のようになります。

CS
ASP

射影演算でc.ArticlesとするとIList<Article>を要素とするIEnumerable<IList<Article>>がクエリ式の結果となります。各要素コレクションではなく、コレクションを展開した形式(1次元のリスト)にしたい場合は、拡張メソッドのSelectManyを使用します。また、同じことをfromを2度使うことで実現できます。下のサンプルが例です。

var categories = InitializeData();

Console.WriteLine("射影演算でコレクションを要素とした場合");
var query =
    from c in categories
    where c.ID < 3
    select c.Articles;

foreach (var item in query)
{
    Console.WriteLine(item);
}
Console.WriteLine("LINQクエリでリスト要素を展開する場合");
var query2 =
    from c in categories
    where c.ID < 3
       from a in c.Articles
       select a;

foreach (var item in query2)
{
    Console.WriteLine(item);
}
Console.WriteLine("SelectManyを使用する場合");
var query3 = categories.Where(c => c.ID < 3).SelectMany(c => c.Articles);
foreach (var item in query3)
{
    Console.WriteLine(item);
}

実行結果は次のようになります。

射影演算でコレクションを要素とした場合
System.Collections.Generic.List`1[Linq2Object.Article]
System.Collections.Generic.List`1[Linq2Object.Article]
LINQクエリでリスト要素を展開する場合
Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo
Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo
SelectManyを使用する場合
Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo
Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo

1.4 Order演算子

ソートを行う場合に、order byの後に、ソートに使用する値を列挙します。昇順(デフォルト)、降順を制御する場合は、ascending,descendingをそれぞれ指定します。対応する拡張メソッドはOrderBy,OrderByDeescending,ThenBy,ThenByDescendingになります。ThenByメソッドは2つ以上のソート条件がある場合にOrderBy().ThenBy()の順序で使用します。

var categories = InitializeData();
var query =
    from c in categories
    where c.ID < 3
    orderby c.Name, c.ID descending
    select c;

foreach (var item in query)
{
    Console.WriteLine(item);
}

処理結果は次のようになります。

Category:ID=2, Name=ASP
  Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo

Category:ID=1, Name=CS
  Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo

1.5 Reverse拡張メソッド

処理結果のリストの順番を反転させる場合に使用します。以下がサンプルです。

var categories = InitializeData();
var query =
    (from c in categories
    where c.ID < 3
    orderby c.Name, c.ID
    select c).Reverse();

foreach (var item in query)
{
    Console.WriteLine(item);
}

処理結果は次のようになります。1.4と表示順が反転していることがわかります。

Category:ID=1, Name=CS
  Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo

Category:ID=2, Name=ASP
  Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo

1.6 let句

letを使用すると繰り返し使用する煩雑なクエリ式のエイリアスを定義できます。

XElement products = XElement.Load(@"D:\temp\products.xml");
var categories = new string[] { "book", "novel" };
var query = from p in products.Elements("product")
            let xCategory = (string) p.Attribute("category")
            join c in categories
              on xCategory equals c
          orderby xCategory
            select new
            {
                Name = (string)p.Element("name"),
                Price = (decimal)p.Element("price"),
                Category = xCategory
            };

上記の例はLINQ to XMLを使用した例です。

今回は以上です。

選択where,射影select,ソートorder演算について記載しました。次回は結合演算(Join),集合演算(和Union,差Except,積Intersect),集約のサンプルを掲載します。