【2/17】今年は「濃厚」技術トーク!@ITメールセミナー スラッシュドット    はてなブックマーク  Yahoo!ブックマークに登録  印刷
 

連載:[完全版]究極のC#プログラミング

Chapter7 ラムダ式(後編)

川俣 晶
2009/11/02


 本記事は、(株)技術評論社が発行する書籍『[完全版]究極のC#プログラミング ― 新スタイルによる実践的コーディング』から、許可を得て転載しています。
 同書籍は、もともと本フォーラムにて連載していた『C# 2.0入門』、『C# 3.0入門』の記事を整理統合し、加筆、修正されたものです。

  手元でまとめて読みたい方は、ぜひ書店などにてお買い求めください。

 【注意】本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

7.1 ラムダ式は何をもたらすか?

 実際にラムダ式を使うことで体験した出来事を、まずは紹介しよう。

 以下、具体的なコードで説明していくが、実際に書いたコードそのままではなく、C# 1.xの知識で読み取れるように全面的に書き直したコードであることをお断りしておく(以下のコードを“鈍くさい”と思う読者もいると思うが、実際のコードはもっと簡潔で、かつC# 3.0の機能を活用している)。

 さて、少し前に実際に筆者が書いていたコードの中に、メニューとして選択可能な項目のリストがある。メニュー項目は次のリスト7.1のように定義されていた。

public delegate bool SimpleMenuAction();

public class メニュー項目ItemA
{
  public readonly string Name; // 名前
  public readonly SimpleMenuAction Action; // 実行内容

  public メニュー項目ItemA(string name, SimpleMenuAction action)
  {
    Name = name;
    Action = action;
  }
}
リスト7.1 メニュー項目の定義

 これに対して、次ページのリスト7.2のようなメニュー項目のテーブルがあった。

private static メニュー項目ItemA[] メニューItems1 =
  {
    new メニュー項目ItemA("選択項目1", 実行メソッド),
    new メニュー項目ItemA("選択項目2", 実行メソッド),
    new メニュー項目ItemA("選択項目3", 実行メソッド),
  };
リスト7.2 メニュー項目のテーブル

 さて、当初はこれで十分と思われていたが、途中で「19時以降にのみ見せるメニューを追加したい」という要求が出てきた。それが1つなら、if文で例外条件を判定して特別処理を挟んでもよいのだが、要求は2つであり、しかも、増える可能性もあった。そこで、このテーブル中に条件も含めて記述できるようにしたいと考えた。

 最もシンプルな解決策は、メニュー項目クラスに、「何時以降有効にする」という「時」の整数を保存可能にすることだろう。

 まず、メニュー項目ItemAのクラスに、その整数を保持するフィールドFromHourを追加する(リスト7.3参照)。

public class メニュー項目ItemB
{
  public readonly string Name;
  public readonly SimpleMenuAction Action;
  public readonly int FromHour;

  public メニュー項目ItemB(string name, SimpleMenuAction action, int fromHour)
  {
    Name = name;
    Action = action;
    FromHour = fromHour;
  }
}
リスト7.3 FromHourフィールドを追加したメニュー項目の定義

 テーブルは、次のリスト7.4のように書き直す。

private static メニュー項目ItemB[] メニューItems2 =
  {
    new メニュー項目ItemB("選択項目1", 実行メソッド, 0),
    new メニュー項目ItemB("選択項目2", 実行メソッド, 0),
    new メニュー項目ItemB("選択項目3", 実行メソッド, 0),
    new メニュー項目ItemB("選択項目4", 実行メソッド, 19),
  };
リスト7.4 リスト7.3用のメニュー項目のテーブル

 これで、必要な情報をテーブルに埋め込むことができた。

 メニューを構築するメソッドは、選択されたメニューオブジェクトのFromHourを調べることで、現在の「時」が与えられた数値以上であれば表示することができる。

 このコードは「YAGNI*2」の原則からいえばこれで十分であり、これ以上凝った仕掛けを入れる意味はない。そういう意味で、これは良いコードである。

 しかし、このコードは仕様変更の要求に対して、あまりにももろい。たとえば、条件が19時から19時30分になったらもう対応できない。そのほかにも、終了時刻が指定された場合や、時間帯が2つのケース、あるいは曜日によって時間が変動するなど、いくらでも込み入った要求が想定できる。

 そのような要求を想定し、条件をデリゲートで指定するようにコードを修正することができる(リスト7.5参照)。

public delegate bool SimpleMenuAvailability();

public class メニュー項目ItemC
{
  public readonly string Name;
  public readonly SimpleMenuAction Action;

  // 現在有効なメニューか?
  public readonly SimpleMenuAvailability IsAvailable;

  public メニュー項目ItemC(string name, SimpleMenuAction action,
    SimpleMenuAvailability isAvailable)
  {
    Name = name;
    Action = action;
    IsAvailable = isAvailable;
  }
}
リスト7.5 条件をデリゲートで指定するメニュー項目の定義

 しかし、この構造はC# 2.0の時代であれば採用しなかっただろう。もし、ラムダ式が使用できないC# 2.0で、匿名メソッドを使って実現するとすれば、テーブルは次ページのリスト7.6のように書き直すことになる。

private static メニュー項目ItemC[] メニューItems3 =
  {
    new メニュー項目ItemC(
      "選択項目1", 実行メソッド, delegate() { return true; }),
    new メニュー項目ItemC(
      "選択項目2", 実行メソッド, delegate() { return true; }),
    new メニュー項目ItemC(
      "選択項目3", 実行メソッド, delegate() { return true; }),
    new メニュー項目ItemC(
      "選択項目4", 実行メソッド,
        delegate() { return DateTime.Now.Hour >= 19; } ),
  };
リスト7.6 リスト7.5用のメニュー項目のテーブル

 このコードは、将来必要になるか否かも定かではない変更に備えるにしては、あまりにもコードが肥大化している。本質的にほとんど意味を持たないdelegateキーワードやreturnキーワードが目立ちすぎ、パッと見て意図も読み取りにくい。これは、明らかにYAGNIの原則によって戒められるべき、悪いコードの典型例だろうと思う。

 それゆえに、もしC# 2.0を使っていれば、このコードは採用しなかっただろう。いくら、筆者が匿名メソッドを湯水のように使うタイプだとしても、このケースではメリットに対してデメリットが大きすぎる。

 だが、これを匿名メソッドではなく、はるかに少ない文字数で記述できるラムダ式で書いたらどうなるだろうか? 次のリスト7.7では、「()=>true」や「()=>DateTime.Now.Hour >= 19」がラムダ式に当たる。

private static メニュー項目ItemC[] メニューItems4 =
  {
    new メニュー項目ItemC("選択項目1", 実行メソッド, ()=>true),
    new メニュー項目ItemC("選択項目2", 実行メソッド, ()=>true),
    new メニュー項目ItemC("選択項目3", 実行メソッド, ()=>true),
    new メニュー項目ItemC("選択項目4", 実行メソッド,
                                       ()=>DateTime.Now.Hour >= 19),
  };
リスト7.7 リスト7.6にラムダ式を用いたメニュー項目のテーブル

 正直、この程度なら許してよいと思った。YAGNIの原則には反するが、コードのわかりやすさを決定的に損なわない範囲で、未知の修正に対する保険をかけることができている。

 事実、この保険は役立った。すぐに、メニューの有効期間が「19時以降」から「19時以降22時未満」へと変更されたのだ。それに伴い、テーブルは次のリスト7.8のように修正された。

private static メニュー項目ItemC[] メニューItems5 =
  {
    new メニュー項目ItemC("選択項目1", 実行メソッド, ()=>true),
    new メニュー項目ItemC("選択項目2", 実行メソッド, ()=>true),
    new メニュー項目ItemC("選択項目3", 実行メソッド, ()=>true),
    new メニュー項目ItemC("選択項目4", 実行メソッド,
            ()=>DateTime.Now.Hour >= 19 && DateTime.Now.Hour < 22),
  };
リスト7.8 リスト7.7の選択項目4の有効期間を変更

 この変更は、たった1つのラムダ式を書き換える局所的な変更で収まったので、一瞬で完了した。しかし、もしも最初のリスト7.4のコードを採用していたら、メニュー項目クラスに終了時刻の情報を追加したり、メニューを構築するメソッドに終了時刻の判定を追加したりと、手間のかかる修正が要求されたことだろう。だが、それにもかかわらず、ラムダ式ではなく匿名メソッドを使うという前提であったとしたら、その手間のかかるコードのほうを採用していたかもしれない。つまり、匿名メソッドとラムダ式の長さの差がコードの質に影響を与えたのである。

*2 YAGNIとは「You Aren't Going to Need It.」の略で、もしかしたら必要とされるかもしれない機能は実際には必要とされない可能性が非常に高いことを意味する。つまり、未知の未来に備えるためのコードをあらかじめ書く行為は、たいていの場合無駄になるという教訓である。

 

 INDEX
  [完全版]究極のC#プログラミング
  Chapter7 ラムダ式(後編)
  1.7.1 ラムダ式は何をもたらすか?
    2.7.2 ラムダ式と匿名メソッドの違い
    3.7.3 ステートメント型のラムダ
    4.7.4 式形式のラムダの可能性
    5.7.5 型指定を省略できる場合、できない場合
    6.7.6 何もしないラムダ式
    7.7.7 ラムダ式の使用例/【C#olumn】「=>」は不等号?
    8.7.8 ラムダ式のさまざまなバリエーション
    9.7.9 ジェネリックメソッドと型推論
    10.7.10 オーバーロードの解決/値型と参照型の相違は何か?/練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」

ホワイトペーパーTechTargetジャパン

Insider.NET フォーラム 新着記事

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

スキルアップ/キャリアアップ(JOB@IT)

- PR -
- PR -

お勧め求人情報

キャリアアップ 〜JOB@IT
@IT Special -PR-
  企業の仮想化に足りない“発想”とは?
仮想化運用管理のキモは意外なところに!

New!
  操作もマニュアルも分かりやすい!
ユーザー視点で開発されたPC管理ツール

New!
  仮想化すればコストは削減できるか?
仮想化に必要な「3つの視点」を解説する

  セキュリティを知り尽くす上野氏が登壇!
@ITメールソリューションLive! in Tokyo

  運用管理の課題を“2つの観点”から分析
ユーザー満足度の高い「仮想環境」とは?

  世界に通用するストレージの作り方とは?
製品に込めた思いを富士通の開発者に聞く

  OSSで手間も時間も、障害も減った――
「マピオンの事例」オープンソース活用法

  「ノートPCの持ち出し禁止」で大丈夫?
情報漏えいを防ぐ管理手法とインフラは?

  1日の処理を1秒に――MySQLの達人が語る
「コスト削減」できるチューニング

  ドキュメント作成を自動化して、SEの作業
効率を大幅アップ! Visio 2007の魅力

  急速に広がるHyper-Vでのサーバ仮想化
そのベストプラクティスをデルが解説

  @IT主催セミナーで語られた、「担当者に
求められるセキュリティ対策」をレポート

  @IT「Windows 7」 特設サイトオープン!
最新情報・移行ノウハウを公開しています