SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

ラムダ式でステップアップ! C#のプログラムから汎用的なアルゴリズムを切り出すことで、LINQについての理解を深めよう

  • X ポスト
  • このエントリーをはてなブックマークに追加

6. 汎用的なアルゴリズムを適用しようとしてみる

 では、いよいよ元のプログラムでこれらを使ってみます。

 次のようにしたいと思います。

要素の型 Book
コレクション List
抽出条件 IsMatch(タイトルか出版社名に "リ" を含む)
各要素に対してやること Show(コンソールに表示、ただし Book の出版社名は出版社名リスト内にあるものを表示)

 ところが、単純にIsMatchやShowを作って、汎用的なアルゴリズムであるFilterメソッドやForEachメソッドに渡そうとしてもうまくいきません。

 例えば、次のソースコードの場合、IsMatchメソッドではsearchWordとpublisherListがスコープ外、ShowメソッドではpublisherListがスコープ外となり、コンパイルできません。

書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(コンパイル不可)

Program.cs
using MyCollection;
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var publisherList = new List<Publisher> {
            new Publisher { Name = "技術評論社"          },
            new Publisher { Name = "翔泳社"              },
            new Publisher { Name = "オライリー・ジャパン"},
            new Publisher { Name = "SBクリエイティブ"    }
        };

        var bookList      = new List<Book> {
            new Book { Code           = "4774149489"                        ,
                       Title          = "C# ポケットリファレンス"           ,
                       PublisherIndex = 0                                   },
            new Book { Code           = "4798114618"                        ,
                       Title          = "C#の絵本"                          ,
                       PublisherIndex = 1                                   },
            new Book { Code           = "4798122203"                        ,
                       Title          = "独習C# 第3版"                      ,
                       PublisherIndex = 1                                   },
            new Book { Code           = "4873116503"                        ,
                       Title          = "プログラミングC# 第7版"            ,
                       PublisherIndex = 2                                   },
            new Book { Code           = "4797361344"                        ,
                       Title          = "猫でもわかるC#プログラミング 第2版",
                       PublisherIndex = 3                                   }
        };

        var searchWord = "リ";

        bookList.Filter (IsMatch)
                .ForEach(Show   );
    }

    static bool IsMatch(Book book)
    {
        // コンパイル エラー! searchWord と publisherList がスコープ外!
        return book.Title                             .Contains(searchWord) ||
               publisherList[book.PublisherIndex].Name.Contains(searchWord)
    }

    static void Show(Book book)
    {
        // コンパイル エラー! publisherList がスコープ外!
        Console.WriteLine("コード: {0}, タイトル: {1}, 出版社: {2}",
                          book.Code, book.Title,
                          publisherList[book.PublisherIndex].Name);
    }
}

 どうすれば汎用的なアルゴリズムを使って記述できるのでしょうか。

7. オブジェクトを使ったクロージャーによる汎用的なアルゴリズムの適用

オブジェクトの中にカプセル化

 上の例の場合、IsMatchやShowの中で、Mainメソッド内のsearchWordやpublisherListが参照できればよいわけです。

 そこで、searchWordやpublisherListをカプセル化したオブジェクトを作り、その中にFilterメソッドやForEachメソッドの中で呼ばれるメソッドを用意してやります(下の例ではInvokeメソッド)。

 そして、そのオブジェクトのメソッドを渡してやれば、一応この問題は解決します。

書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(オブジェクトを使ったクロージャー版)

 先のプログラムをこのようなオブジェクトを作って渡す形で書いてみると、例えば次のようになり、無事コンパイルと実行ができるようになります。

Program.cs
using MyCollection;
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var publisherList = new List<Publisher> {
            new Publisher { Name = "技術評論社"          },
            new Publisher { Name = "翔泳社"              },
            new Publisher { Name = "オライリー・ジャパン"},
            new Publisher { Name = "SBクリエイティブ"    }
        };

        var bookList      = new List<Book> {
            new Book { Code           = "4774149489"                        ,
                       Title          = "C# ポケットリファレンス"           ,
                       PublisherIndex = 0                                   },
            new Book { Code           = "4798114618"                        ,
                       Title          = "C#の絵本"                          ,
                       PublisherIndex = 1                                   },
            new Book { Code           = "4798122203"                        ,
                       Title          = "独習C# 第3版"                      ,
                       PublisherIndex = 1                                   },
            new Book { Code           = "4873116503"                        ,
                       Title          = "プログラミングC# 第7版"            ,
                       PublisherIndex = 2                                   },
            new Book { Code           = "4797361344"                        ,
                       Title          = "猫でもわかるC#プログラミング 第2版",
                       PublisherIndex = 3                                   }
        };

        var searchWord = "リ";

        bookList.Filter (new IsMatch(publisherList, searchWord).Invoke)
                .ForEach(new Show   (publisherList            ).Invoke);
    }

    // Filter関数に渡すオブジェクトのクラス
    // (publisherList と searchWord への参照を内包する)
    class IsMatch
    {
        readonly List<Publisher> publisherList;
        readonly string          searchWord   ;

        public IsMatch(List<Publisher> publisherList, string searchWord)
        {
            this.publisherList = publisherList;
            this.searchWord    = searchWord   ;
        }

        public bool Invoke(Book book)
        {
            // オブジェクトが publisherList と searchWord への
            // 参照を内包しているため、問題なく使える
            return book.Title                             .Contains(searchWord) ||
                   publisherList[book.PublisherIndex].Name.Contains(searchWord);
        }
    }

    // ForEach 関数に渡す関数オブジェクトのクラス
    // (publisherList への参照を内包する)
    class Show
    {
        readonly List<Publisher> publisherList;

        public Show(List<Publisher> publisherList)
        { this.publisherList = publisherList; }

        public void Invoke(Book book)
        {
            // オブジェクトが publisherList への
            // 参照を内包しているため、問題なく使える
            Console.WriteLine("コード: {0}, タイトル: {1}, 出版社: {2}",
                              book.Code, book.Title,
                              publisherList[book.PublisherIndex].Name);
        }
    }
}

 実行結果は、元々のプログラムと同じです。

実行結果
コード: 4774149489, タイトル: C# ポケットリファレンス, 出版社: 技術評論社
コード: 4873116503, タイトル: プログラミングC# 第7版, 出版社: オライリー・ジャパン
コード: 4797361344, タイトル: 猫でもわかるC#プログラミング 第2版, 出版社: SBクリエイティブ

クロージャー

 このソースコードでは、オブジェクトの中にMainメソッド内の変数への参照またはコピーを内包することによって、コールバックされたときに、それを利用できるようになっています。

 このように、用意されたところでの環境を、呼ばれたときにそのまま利用できる仕組みを、クロージャーと呼びます。

 クロージャーは、C#以外の言語にもよく見られる仕組みで、例えば、C++では関数オブジェクトやラムダ式、Javaでは無名クラスやラムダ式を、クロージャーとして利用できます。

 後述しますが、C#ではクロージャーとして匿名メソッドやラムダ式を利用することができます。

次のページ
8. 匿名メソッドを使った汎用的なアルゴリズムの適用

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

小島 富治雄(フジヲ)

Microsoft MVP for C# (2005.07~)。 - 注目の MVP - Blog: プログラミング C# - 翔ソフトウェア (Sho's) - Web Site: 翔ソフトウェア (Sho's) - Twitter: Fujiwo 著書: - Windows8〔業務アプリ〕開発読本 (共著)

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/8138 2014/10/14 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング