CodeZine(コードジン)

特集ページ一覧

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

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2014/10/14 14:00

目次

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

 前節では、クロージャーのためのオブジェクトを作ることにより、Mainメソッド内の環境を汎用アルゴリズムであるFilterメソッドとForEachメソッドに渡すことができました。

 しかし、関数オブジェクトを作るためには、一々クラスを用意する必要があります。面倒ですし、プログラムが複雑になります。

 ここまでしないと、汎用的なアルゴリズムを使えないのでしょうか?

匿名メソッド

 C# 2.0から追加された匿名メソッドを使うと、より簡潔な記述でクロージャーを実現することができます。

 C#の匿名メソッドについては、次のページを参照してください。

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

 では、先のプログラムを匿名メソッド版を使って書いてみましょう。FilterとForEachの引数に渡しているdelegate (Book book) { ... }の部分が、それぞれ匿名メソッドです。オブジェクトを使う先の例と異なり、クラスを作る必要がない分ぐっとシンプルです。

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 (
            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についての詳細は次を参照してください。

 ビルドして作られたアセンブリ(この場合はexeファイル)をIldasm.exeで開いてみると、次のようにProgramクラスの中が実際にどのような構造になったかを見ることができます。

Ildasmで開いたアセンブリ
Ildasmで開いたアセンブリ

 よく見ると、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:中間言語)を見ることができます。

1つ目の匿名メソッドの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にアクセスしていることを意味しています。

 つまり、前節での「オブジェクトを使ったクロージャー」と似た仕組みのコードが自動生成されているわけです。匿名メソッドを使うことで、自分でクロージャーの仕組みをクラスで書かなくても、同様の仕組みが使えるようになっています。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 小島 富治雄(フジヲ)

    Microsoft MVP for C# (2005.07~)。 - 注目の MVP - Blog: プログラミング C# - 翔ソフトウェア (Sho's) - Web Site: 翔ソフトウェア (Sho's) - Twitter: Fujiwo...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5