6. 汎用的なアルゴリズムを適用しようとしてみる
では、いよいよ元のプログラムでこれらを使ってみます。
次のようにしたいと思います。
要素の型 | Book |
---|---|
コレクション | List |
抽出条件 | IsMatch(タイトルか出版社名に "リ" を含む) |
各要素に対してやること | Show(コンソールに表示、ただし Book の出版社名は出版社名リスト内にあるものを表示) |
ところが、単純にIsMatchやShowを作って、汎用的なアルゴリズムであるFilterメソッドやForEachメソッドに渡そうとしてもうまくいきません。
例えば、次のソースコードの場合、IsMatchメソッドではsearchWordとpublisherListがスコープ外、ShowメソッドではpublisherListがスコープ外となり、コンパイルできません。
書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(コンパイル不可)
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メソッド)。
そして、そのオブジェクトのメソッドを渡してやれば、一応この問題は解決します。
書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(オブジェクトを使ったクロージャー版)
先のプログラムをこのようなオブジェクトを作って渡す形で書いてみると、例えば次のようになり、無事コンパイルと実行ができるようになります。
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#ではクロージャーとして匿名メソッドやラムダ式を利用することができます。