9. ラムダ式を使った汎用的なアルゴリズムの適用
前節では、匿名メソッドを使うことにより、Mainメソッド内の環境を汎用アルゴリズムであるFilterメソッドとForEachメソッドに渡すことができました。
ラムダ式
C# 3.0で採用されたラムダ式を使えば、さらに簡潔に書くことができます。
C#のラムダ式については、次のページを参照してください。
- 『ラムダ式(C#プログラミング ガイド)』(MSDN)
書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(ラムダ式版)
では、先のプログラムをラムダ式を使って書いてみましょう。FilterとForEachの引数に渡している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 = "リ";
// searchWord や publisherList がスコープ内なので問題なく使える
bookList.Filter (book => book.Title.Contains(searchWord) || // ラムダ式
publisherList[book.PublisherIndex].Name
.Contains(searchWord))
.ForEach(book => Console.WriteLine( // ラムダ式
"コード: {0}, タイトル: {1}, 出版社: {2}",
book.Code, book.Title,
publisherList[book.PublisherIndex].Name
));
}
}
実行結果は、元々のプログラムと同じです。
コード: 4774149489, タイトル: C# ポケットリファレンス, 出版社: 技術評論社 コード: 4873116503, タイトル: プログラミングC# 第7版, 出版社: オライリー・ジャパン コード: 4797361344, タイトル: 猫でもわかるC#プログラミング 第2版, 出版社: SBクリエイティブ
ラムダ式を使うことで、余計な記述(本来のプログラムのロジックでない部分)が減り、簡潔な記述となりました。
Ildasmを使って仕組みを調べる
では、匿名メソッドのときと同様、今度もビルドして作られたアセンブリを Ildasm.exe で開いて中の仕組みを見てみましょう。
匿名メソッドのときとまったく同じ構造になっています。
そして、匿名メソッドのときと同じように、ラムダ式にあたる「<Main>b__2 : book(class Book)」をダブルクリックして開いてみると、こちらもやはり同じです。
.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# 3.0以降では匿名メソッドを使わずラムダ式の方を使うことをお勧めします。
10. まとめ
C#における汎用的なアルゴリズムの分離について考察しました。
汎用的なアルゴリズムがどのように分離されると良いか、そして分離されたアルゴリズムがどのように使われると良いか、を述べました。
今回は汎用的なアルゴリズムを自作しましたが、このようなアルゴリズムはC#のライブラリの中にすでにたくさん存在し、同様に使うことができます。LINQがその代表で、今回作成したFilterのような便利で汎用的なメソッドを多数利用することができます。
ラムダ式やLINQの便利さは、次のページでも紹介されています。
- 『C# 1.1からC# 3.0まで~言語仕様の進化 C# 1.1からLINQまで』(CodeZine)
ラムダ式を活用すれば、簡潔な記述で既存のアルゴリズムを使うことができます。
C#で汎用的なアルゴリズムを利用したり書いたりするために、ここで述べたことが参考になれば幸いです。
