8. 匿名メソッドを使った汎用的なアルゴリズムの適用
前節では、クロージャーのためのオブジェクトを作ることにより、Mainメソッド内の環境を汎用アルゴリズムであるFilterメソッドとForEachメソッドに渡すことができました。
しかし、関数オブジェクトを作るためには、一々クラスを用意する必要があります。面倒ですし、プログラムが複雑になります。
ここまでしないと、汎用的なアルゴリズムを使えないのでしょうか?
匿名メソッド
C# 2.0から追加された匿名メソッドを使うと、より簡潔な記述でクロージャーを実現することができます。
C#の匿名メソッドについては、次のページを参照してください。
- 『匿名メソッド(C# プログラミング ガイド)』(MSDN)
書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(匿名メソッド版)
では、先のプログラムを匿名メソッド版を使って書いてみましょう。FilterとForEachの引数に渡しているdelegate (Book book) { ... }
の部分が、それぞれ匿名メソッドです。オブジェクトを使う先の例と異なり、クラスを作る必要がない分ぐっとシンプルです。
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 ( delegate (Book book) { // 匿名メソッド // searchWord と publisherList が // スコープ内なので問題なく使える return book.Title.Contains(searchWord) || publisherList[book.PublisherIndex].Name .Contains(searchWord); } ) .ForEach( delegate (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クリエイティブ
匿名メソッドを使うことで、余計な記述(本来のプログラムのロジックでない部分)が減り、簡潔な記述となりました。
Ildasmを使って仕組みを調べる
ここで、匿名メソッドがどのような仕組みでクロージャーを実現しているか見てみましょう。
Visual Studioと共に自動的にインストールされるIldasm.exe(IL 逆アセンブラー)というツールを使うと、ビルド済みのアセンブリの中の構造を調べることができます。
Ildasm.exeは、Visual Studioの開発者コマンド プロンプトから起動できます。
Ildasm.exeについての詳細は次を参照してください。
- 『Ildasm.exe(IL 逆アセンブラー)』(MSDN)
ビルドして作られたアセンブリ(この場合はexeファイル)をIldasm.exeで開いてみると、次のようにProgramクラスの中が実際にどのような構造になったかを見ることができます。
よく見ると、Programクラスの中に、<>c__DisplayClass0という作った覚えのないクラスが作られています。
そして、その<>c__DisplayClass0のメンバーを見ると、publisherListとsearchWordというフィールドができているのが分かります。
匿名メソッドを使ったことにより、Programクラスの中には、<>c__DisplayClass0というクラスが自動生成されているのです。そして、そのクラスの中に publisherListとsearchWordがカプセル化されています。
「<Main>b__2 : book(class Book)」と「<Main>b__2 : book(class Book)」の部分が2つの匿名メソッドです。 例えばb__2の方をダブルクリックして開いてみると、この匿名メソッドの中のIL(Intermediate Language:中間言語)を見ることができます。
.method assembly hidebysig instance bool '<Main>b__2'(class Book book) cil managed { // コード サイズ 55 (0x37) .maxstack 8 IL_0000: ldarg.1 IL_0001: callvirt instance string Book::get_Title() IL_0006: ldarg.0 IL_0007: ldfld string Program/'<>c__DisplayClass0'::searchWord IL_000c: callvirt instance bool [mscorlib]System.String::Contains(string) IL_0011: brtrue.s IL_0035 IL_0013: ldarg.0 IL_0014: ldfld class [mscorlib]System.Collections.Generic.List`1<class Publisher> Program/'<>c__DisplayClass0'::publisherList IL_0019: ldarg.1 IL_001a: callvirt instance int32 Book::get_PublisherIndex() IL_001f: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<class Publisher>::get_Item(int32) IL_0024: callvirt instance string Publisher::get_Name() IL_0029: ldarg.0 IL_002a: ldfld string Program/'<>c__DisplayClass0'::searchWord IL_002f: callvirt instance bool [mscorlib]System.String::Contains(string) IL_0034: ret IL_0035: ldc.i4.1 IL_0036: ret } // end of method '<>c__DisplayClass0'::'<Main>b__2'
中に、「'<>c__DisplayClass0'::searchWord」だとか「'<>c__DisplayClass0'::publisherList」といった記述があります。
これは、この匿名メソッドから先程のProgramクラスの中に自動生成された<>c__DisplayClass0というクラスのオブジェクト内のsearchWordやpublisherListにアクセスしていることを意味しています。
つまり、前節での「オブジェクトを使ったクロージャー」と似た仕組みのコードが自動生成されているわけです。匿名メソッドを使うことで、自分でクロージャーの仕組みをクラスで書かなくても、同様の仕組みが使えるようになっています。