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

Chapter4 Findメソッド

川俣 晶
2009/09/14

4.3 ForEachだけではない繰り返しメソッド

 実は、System.Arrayクラスやジェネリックコレクションのクラスなどが持つ繰り返しメソッドはForEachメソッドだけではない。Find、FindIndex、FindAllのような検索系のメソッドも持っている。

 「Findメソッド」はForEachメソッドとよく似ているが、繰り返しではなく検索に特化したメソッドである。

 主な相違は2つある。

 1つは、第2引数で指定するデリゲートがAction<T>ではなくPredicate<T>になっていることである。つまり、メソッドの戻り値がvoid型ではなくbool型になっている。メソッドがtrueを返すと繰り返しを打ち切るようになっている。もう1つは、引数のメソッドがtrueを返したときの列挙値を、Findメソッドそのものの戻り値として返すことである。

 さて、ここで注目したいのは、trueを返すと繰り返しを打ち切るという効能である。これを使えば、break文がなくとも、それに相当するコードを書くことができる(リスト4.3参照)。

using System;

class Program
{
  static void Main(string[] args)
  {
    string[] 玉電駅名 = {
      "下高井戸","七軒町","六所神社前","山下","豪徳寺前","宮ノ坂"
    };

    Array.Find(玉電駅名, (駅名)=>
    {
      if (駅名.Contains("前"))
      {
        Console.WriteLine(
          "前の付く駅名としては、たとえば{0}があります。", 駅名);
        return true;
      }
      return false;
    });

   // 出力:前の付く駅名としては、たとえば六所神社前があります。
  }
}
リスト4.3 ForEachメソッドの代わりにFindメソッドを使った例

 しかし、これは読んでうれしいコードではない。return文が2つもあり、返すtrue/falseも直感的に意味がわかりにくい。

 さらにいえば、本来検索のためのFindメソッドを繰り返しのために使っていることも、コードのわかりにくさを生んでいるといえる。パッと見て、このソースが何をするものなのか、かなりわかりにくいといえるだろう。

 では、やはりforeach文に回帰すべきなのだろうか?

 そうではない。Findメソッドを前提に考えるなら、Findメソッドを検索という目的に特化してやれば、コードはすっきりと明快になる。つまり、検索とは関係ない結果の出力機能を第2引数の匿名メソッドの外に出すのである。これがリスト4.4である。

using System;

class Program
{
  static void Main(string[] args)
  {
    string[] 玉電駅名 = {
      "下高井戸","七軒町","六所神社前","山下","豪徳寺前","宮ノ坂"
    };

    // 検索機能
    string 前の付く駅名 = Array.Find(玉電駅名, (駅名) => 駅名.Contains("前"));

    // 出力機能
    Console.WriteLine(
      "前の付く駅名としては、たとえば{0}があります。", 前の付く駅名);

    // 出力:前の付く駅名としては、たとえば六所神社前があります。
  }
}
リスト4.4 出力は検索と分ける

 これでコードはかなりすっきりした。特に、ラムダ式が単に1つの式を計算するだけでよくなったため、中カッコ抜きで直接式を書けるようになった点は大きい。

 だが、実はこのソースにはもう1つ大きなメリットがある。検索機能と出力機能がソースコード上で完全に分離したのである。Array.Findメソッド呼び出し部分が検索機能であり、続くConsole.WriteLineメソッドが出力機能である。一方、foreach文を使ったリスト4.1では、この2つの機能は明瞭に分離されていない。

 では、なぜ分離されていると良いのだろうか?

 分離されていれば、バラバラに切り離して開発することが容易になるからである。特に、リスト4.4のコードは、Visual Studio 2008が持つリファクタリング支援機能の「メソッドの抽出」を使って、検索機能と出力機能を別々に切り出せることに注目しよう。

 それに対して、foreach文を使ったリスト4.1は、出力機能だけを切り出すことはできるが、出力機能抜きの検索機能をメソッドに抽出することはできない。

 実際に、リファクタリング支援機能で切り分けた例をリスト4.5に示す。

using System;

class Program
{
  static void Main(string[] args)
  {
    string[] 玉電駅名 = {
      "下高井戸","七軒町","六所神社前","山下","豪徳寺前","宮ノ坂"
    };

    string 前の付く駅名 = Get前の付く駅名(玉電駅名);
    Write前の付く駅名(前の付く駅名);

    // 出力:前の付く駅名としては、たとえば六所神社前があります。
  }

  // 出力機能
  private static void Write前の付く駅名(string 前の付く駅名)
  {
    Console.WriteLine(
      "前の付く駅名としては、たとえば{0}があります。", 前の付く駅名);
  }

  // 検索機能
  private static string Get前の付く駅名(string[] 玉電駅名)
  {
    string 前の付く駅名 = Array.Find(玉電駅名, (駅名) => 駅名.Contains("前"));
    return 前の付く駅名;
  }
}
リスト4.5 「メソッドの抽出」で検索機能と出力機能を分けた例

 このソースには、明らかに冗長な変数がいくつも見られる。

 そこで、リファクタリングカタログの1つである「ローカル変数の除去」を適用してみよう*6リスト4.6参照)。

using System;

class Program
{
  static void Main(string[] args)
  {
    string[] 玉電駅名 = {
      "下高井戸","七軒町","六所神社前","山下","豪徳寺前","宮ノ坂"
    };

    Write前の付く駅名(Get前の付く駅名(玉電駅名));

    // 出力:前の付く駅名としては、たとえば六所神社前があります。
  }

  // 出力機能
  private static void Write前の付く駅名(string 前の付く駅名)
  {
    Console.WriteLine(
      "前の付く駅名としては、たとえば{0}があります。", 前の付く駅名);
  }

  // 検索機能
  private static string Get前の付く駅名(string[] 玉電駅名)
  {
    return Array.Find(玉電駅名, (駅名)=>駅名.Contains("前"));
  }
}
リスト4.6 冗長なローカル変数を除去した例

 このとおり、役割ごとのメソッドに分けられた非常にすっきりしたソースコードを得ることができた。後は、メソッドごとに分けてバラバラに改良することも容易だろう。

*6 念のために補足すると、リファクタリングは、ツールで実行するものではなく、人間が行うものである。ツールは、リファクタリングの書き換えの一部を自動化して支援するにすぎない。ローカル変数の除去は、Visual Studioが支援してくれるわけではないので、手動で書き換えて行う。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter4 Findメソッド
    1.4.1 MATステートメントの思い出
    2.4.2 ForEachメソッドのbreak問題
  3.4.3 ForEachだけではない繰り返しメソッド
    4.4.4 複数の結果がほしい場合
    5.4.5 偉大なる前進とは何か?
    6.4.6 そして、LINQへ続く/【Exercise】練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


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 記事ランキング

本日 月間