Visual Studio 2008から導入された標準クエリ演算子を利用するスタイルでは、IEnumerable<T>
を返すメソッドから始まり、中間ではIEnumerable<T>
を受け取ってIEnumerable<T>
を返すメソッド、最後はIEnumerable<T>
を受け取って最終処理するメソッドを数珠(じゅず)繋ぎにします。IEnumerable<T>
を受け取るとは引数で受け取ることを考えてしまいますが、そうではなくIEnumerable<T>のメソッドとして実装することでフィルタ処理を左から右に順にドットで結びつけて表現できるので、処理順序とプログラム記述順序が一致します。
各フィルタ内部ではyield
を使ってコンテキストスイッチを行い次のフィルタにデータを渡す処理を内部で繰り返しています。そのためフィルタを記述するのに繰り返しをその都度記述する必要はありません。
IEnumerableによるフィルタ
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
namespace ToUpper
{
static class EnumerableExtentions {
public static void Each<T>(this IEnumerable<T> source, Action<T> func)
{
foreach (var elm in source) {
func(elm);
}
}
public static IEnumerable<T> Map<T>(this IEnumerable<T> source, Func<T, T> func)
{
foreach (var elm in source) {
yield return func(elm);
}
yield break;
}
}
class Program
{
public static IEnumerable<string> FileReader()
{
string line;
while ((line = Console.ReadLine()) != null) {
yield return line;
}
yield break;
}
static void Main(string[] args)
{
FileReader().Map(line => line.ToUpper()).Each(line => Console.WriteLine(line));
}
}
}
NetFrameworkではSelect
,Where
などのメソッドが用意されていますがこれらはどのように作ればよいのでしょう。IEnumerable<T>
を利用するスタイルで使えるEach
とMap
というメソッドを作ってみます。Each
はIEnumerable<T>
を受け取って、引数で指定したメソッドに各要素を順に渡すメソッド、Map
は、指定したメソッドに各要素を順に渡すだけでなくそのメソッドが何らかの変換を行って値を返します。IEnumerable<T>
はNetframeworkに用意されている型ですが、作り付けの型に新たなメソッドを追加しなければならないことになります。また、IEnumerable<T>
はクラスではなくインタフェースですから通常は実装を持たないはずです。なおさらメソッドを実装することが難しく思えます。2008からは拡張メソッドによってそれが可能になっています。
Each
,Map
は共に拡張メソッドとして実装しました。Each
の第2引数func
は各要素を受け取って処理するメソッドを指定します。Action<T>
は引数を一つ受け取り、値を返さないメソッドを表します。一方Map
では値を返しますから、Func<T,
T>
となっています。Func<T1, T2>
とは一つの引数T1
を受け取り、T2
を返すメソッドを表します。ここでは両方T
ですから引数と同じ型の値を返すのです。
使用例は、標準入力を順に読み取り、大文字にして標準出力に書き込むコンソールプログラムです。英文を入力してEnterを押せば次の行に大文字に変換された文字列が表示されます。終了させるにはcontrol-Zに続いてEnterを押します。
FileReader
は標準入力を1行ずつ読み取るメソッドです。FileReader
は次のMap
に結果を渡します。Map
の引数には大文字変換を行うラムダ式が指定されています。Map
はさらにEach
に結果を渡します。Each
の引数には渡された文字列を表示するラムダ式が指定されています。
これを発展させれば多段階の処理が必要なフィルタを実現することもできます。
この例では頻繁にyield
が呼ばれます。yield
のパフォーマンスは下記ページを参考にしてください。