ジョイ・オブ・プログラミング:LINQ(後編)

LINQを活用した簡単XMLデータベース・アプリケーション

シグマコンサルティング 菅原 英治
2008/04/22
Page1 Page2

LINQのWhere条件を動的に変更する方法について

 最後にLINQのWhere条件を動的に変更する方法について考察してみます。SQL文ではWHERE句の文字列を動的に結合していくことで検索条件を変化させることが比較的簡単に行えます。しかし、LINQにおいてはWhere条件が式ツリーで定義されるため、多少工夫が必要です。

 わたしの考えでは、LINQにおいて動的に変更する方法としては次の2つがあるのではないかと考えています。

 (1)Where条件を重ねる方法
 (2)ラムダ式を動的に組み上げる方法

 サンプル・アプリケーションでは、前者の(1)Where条件を重ねる方法を取っています。入力された値を条件数分if文で判断し重ねるかどうかを分ける、という特に工夫がない方法を取っています。また、この方法では、それぞれ条件はAND(=条件論理式の「かつ」)で結合されることになります。

 前者において、ANDで結合されてしまう点はどうしようもないのですが、条件数分if文がある点は工夫することで、if文をなくしてきれいにコーディングする方法があります。

Where条件を重ねる方法の工夫:デコレータ・パターンの利用

 条件数分のif文がある問題を解決するために、わたしはデザインパターンの1つであるデコレータ・パターンを採用してはどうかと考えました。次のコードは、デコレータ・パターンを利用した検索ロジック(Window1.xaml.csのSearchメソッド)です。

 このコードを利用するために、こちらのファイルをサンプル・アプリケーションに追加してください。追加したら、先ほどのSearchメソッドを次のように書き換えます。

// 検索する
private void Search()
{
  // 検索結果をリストに設定
  this.listView1.ItemsSource =
    new WhereByPoint(this.textBoxPoint.Text,
      new WhereByAddress(this.textBoxAddress.Text,
        new WhereByName(this.textBoxName.Text,
          new WhereById(this.textBoxId.Text,
            new WhereAllRestaurants())))).Where();
}
リスト3 デコレータ・パターンを利用したSearchメソッド(Window1.xaml.cs)

 このコードでは、検索ロジックからif文がきれいさっぱりなくなっていることが分かるでしょう。そして代わりに「Where」で始まるクラスのコンストラクタがたくさん並んでいることにお気付きでしょう。

 このソースを実行すると、これまでと同じ結果が得られます。わたしは、このようにコードがきれいにまとまった瞬間に、何ともいえないうれしさを感じました。皆さんはいかがですか?

 さて、デコレータ・パターンを利用したソースについて簡単に解説します。デコレータ・パターンとは、既存のオブジェクトに対し、新しい機能を追加する際に有効なデザインパターンです。今回の例では、全レストランを取得するオブジェクトに対し、Idや名前などの条件で絞る機能を動的に追加できるとよいでしょう。それでは次のコードを見てみましょう。

// IEnumerable<Restaurant>を返すメソッドを表すインターフェイス
public interface IWhere
{
  IEnumerable<Restaurant> Where();
}

// 全レストラン取得用
public class WhereAllRestaurants : IWhere
{
  public IEnumerable<Restaurant> Where()
  {
    // ……中略……
    // XMLファイルをデシリアライズし、
    // List<Restaurant>オブジェクトを返す
  }
}

// Id用のWhereByIdクラス
public class WhereById : IWhere
{
  private IWhere _where;
  private string _id;

  public WhereById(string id, IWhere where)
  {
    _where = where;
    _id = id;
  }

  public IEnumerable<Restaurant> Where()
  {
    int id;

    if (!int.TryParse(_id, out id))
      return _where.Where();
    else
      return _where.Where().Where(r => r.Id == id);
  }
}

// ……省略……
// 名前/住所/得点用の「WhereByXXX」クラス
リスト4 デコレータ・パターンを実装するコード(IWhere.cs)
ソースのダウンロードはこちらから。

 それでは、このコードを解説します。まず、IWhereインターフェイスが定義されています。このインターフェイスでは、IEnumerable<Restaurant>オブジェクトを返すWhereというメソッドが定義されています。

 続いて、IWhereインターフェイスを実装するWhereAllRestaurantsクラスが定義されています。このクラスは最も基本的なオブジェクトとなるクラスです。このクラスで実装するWhereメソッドでは、全レストランの情報をXMLファイルから取得し、IEnumerable<Restaurant>オブジェクトとして返す処理を記述します。

 最後に、IWhereインターフェイスを実装するWhereByIdクラスが定義されています。このクラスは、IWhereインターフェイスを実装したオブジェクトに対し、Idで検索する機能を追加するクラスです。このクラスで実装するWhereメソッドでは、Idに設定された値が不正な場合は何も行わず、正しい場合は、Idで絞り込んでいます。

 このWhereByIdクラスと同様に、名前、住所、得点で絞り込むためのIWhereインターフェイスを実装するクラスを作成していきます。

 デコレータ・パターンの利用についての解説は以上です。このようにある情報をフィルタリングしていくような処理を実現する場合は、デコレータは非常に有効な方法といえます。

ラムダ式を動的に組み上げる方法

 ここでは、ラムダ式を動的に組み上げる方法を簡単に解説します。こちらの方法では、条件をANDの結合に限らず、OR(=条件論理式の「または」)で結合することが可能です。今回はサンプル・アプリケーションのId条件と名前条件をORで結合するラムダ式を動的に組み上げてみます。

 次のコードは、動的に組み上げたラムダ式を利用した検索ロジック(Window1.xaml.csのSearchメソッド)です。このコードを利用するために、こちらのファイルをサンプル・アプリケーションに追加してください。そして、Searchメソッドを次のように書き換えます。

// 検索する
private void Search()
{
  // 全レストランを取得する
  IEnumerable<Restaurant> result = GetRestaurants();

  // Idを取得する(未入力は0とする)
  int id = string.IsNullOrEmpty(this.textBoxId.Text)
                ? 0 : int.Parse(this.textBoxId.Text);

  //Idと名前のOR条件で検索する
  result = result.Where(Predicate.GetPredicate(id, this.textBoxName.Text));

  // 検索結果をリストに設定
  this.listView1.ItemsSource = result;
}
リスト5 動的に組み上げたラムダ式を利用したSearchメソッド(Window1.xaml.cs)

 このコードの太字で示された部分に注目してください。PredicateクラスのGetPredicateメソッドで取得したラムダ式をWhereメソッドのパラメータに利用しています。

 さて、PredicateクラスでのGetPredicateメソッドでは、下記のコードのように動的にラムダ式を生成しています。

public class Predicate
{
  // 動的にId or 名前条件用ラムダ式を生成する
  // r => r.Id == id || r => r.Name.Contains(name)という式を動的に作る
  public static Func<Restaurant, bool> GetPredicate(int id, string name)
  {  
    // パラメータの定義する:r
    ParameterExpression param =
      Expression.Parameter(typeof(Restaurant), "r");

    // Idのbodyの左を定義する:r.Id
    MemberExpression left = Expression.Property(param, "Id");

    // Idのbodyの右を定義する:id
    ConstantExpression right =
      Expression.Constant(id, typeof(int));

    // Idのbodyを定義する:r.Id == id
    BinaryExpression body = Expression.Equal(left, right);

    // Nameの属性を定義する:r.Name
    MemberExpression nameProperty = Expression.Property(param, "Name");

    // Name定数を定義する:name
    ConstantExpression nameConstant = Expression.Constant(name, typeof(string));

    // Nameのbodyを定義する:r.Name.Contains(name)
    MethodCallExpression nameBody = Expression.Call(nameProperty,
        typeof(string).GetMethod("Contains"), nameConstant);

    // 全体のbodyを定義する
    // r => r.Id == id || r => r.Name.Contains(name)
    BinaryExpression body = Expression.Or(idBody, nameBody);

    // 式ツリーを(r => r.Id == id || r => r.Name.Contains(name))を組み立て、
    // 実行コードにコンパイルする
    return Expression.Lambda<Func<Restaurant, bool>>(
      body, param).Compile();
  }
}
リスト6 ラムダ式を組み上げるPredicateクラスのGetPredicateメソッド(Predicate.cs)
ソースのダウンロードはこちらから。

 このコードでは、「r => r.Id == id || r => r.Name.Contains(name)」というラムダ式を動的に組み上げています。ここでは式ツリー(=ツリー構造で格納されたコード・データ)の詳細については解説しませんが、簡単にコードの流れを解説しましょう。

 式ツリーについての詳細を知りたい場合は、次のMSDNを参照するとよいでしょう。

 まず最初に式のパラメータとなるRestaurant型で「r」という名前をもったparam(=ラムダ式のパラメータ)を定義しています。続いて、Idが等しいかどうかのbody(=本文)「r.Id == id」を定義しています。さらに、名前を含むかどうかのbody「r.Name.Contains(name)」を定義しています。

 そして、Idと名前のbodyをOrでつないで全体のbodyである「r => r.Id == id || r => r.Name.Contains(name)」を定義します。

 最後にparamとbodyを動的に組み上げ、結果として求めるラムダ式を生成しています。

 このコードを実行すると、たしかにIdと名前の条件でOR条件の結果が得られることが分かるでしょう。

 ラムダ式を動的に組み上げる方法は以上です。

まとめ

 それではまとめです。前編では、まず、サンプル・アプリケーションの動きを確認しました。そしてレストラン検索アプリケーションの動作を確認しました。次に、サンプル・アプリケーションのソースを理解するための基礎として、LINQの基礎について解説しました。LINQの基礎では、LINQにおける射影の選択の方法、LINQの2つの記述方法、LINQ to XMLについて解説しました。特にサンプル・アプリケーションでは、LINQの2つの記述方法のうちのメソッド・ベースの方法で記述しているため、その記述方法を理解する必要がありました。

 そして後編では、サンプル・アプリケーションのソースについて解説しました。特に、XMLファイルからデシリアライズしたオブジェクトを利用して、それに対してLINQによる検索を行っていることが重要でした。また、LINQのWhere条件を動的に変更する方法について考察し、その実装方法を解説しました。

 サンプル・アプリケーションは、単純なソースで作成されています。わたし自身、このアプリケーションを思い立ってから、作成に要した時間は20分も必要ありませんでした。Visual Studio 2008を利用すると、このように非常に短時間で、それなりの機能を持ったものが簡単に作成できます。そして作成したものが意図したとおり動く様子はとても楽しいものです。

 皆さんも本稿をきっかけにプログラミングの楽しさに気付いていただけたら幸いです。さようなら。End of Article

 

 INDEX
  ジョイ・オブ・プログラミング:LINQ(前編)
  XMLを扱えるLINQ ―LINQ to XML― の基礎を学ぼう
    1.ジョイ・オブ・プログラミングの伝道師「ロブ」
    2.LINQの基礎
    3.LINQ to XMLについて
 
  ジョイ・オブ・プログラミング:LINQ(後編)
  LINQを活用した簡単XMLデータベース・アプリケーション
    1.サンプル・アプリケーションのソース解説
  2.LINQのWhere条件を動的に変更する方法

 ジョイ・オブ・プログラミング


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間