LINQクエリ中で使用出来る拡張メソッドを実装するとき、その引数シグネチャはIEnumerable<T>で受ける事になる。
public static class LinqExtensions { // パブリックなデフォルトコンストラクタを持つ型を抽出する public static IEnumerable<Type> OfCreatable(this IEnumerable<Type> enumerable) { return from type in enumerable where (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null) // パブリックなデフォルトコンストラクタがある select type; } }
この拡張メソッドを使ってみる。
// mscorlib.dllのすべての型から、生成可能なクラスを抽出する var types = typeof(object).Assembly.GetTypes(); var creatables = types.OfCreatable();
この時、typesはType[]なので、引数のIEnumerable<Type>に合致する。OfCreatableに実装したLINQクエリは以下のように拡張メソッドで書き直せる。
public static class LinqExtensions { // パブリックなデフォルトコンストラクタを持つ型を抽出する public static IEnumerable<Type> OfCreatable(this IEnumerable<Type> enumerable) { return enumerable.Where(type => (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null)); // パブリックなデフォルトコンストラクタがある } }
ここで、typesを並列化したらどうなるだろうか。
// mscorlibのすべての型から、生成可能なクラスを抽出する var types = typeof(object).Assembly.GetTypes().AsParallel(); // 並列化 var creatables = types.OfCreatable();
varを書き直すと、以下のようになる。
// mscorlib.dllのすべての型から、生成可能なクラスを抽出する ParallelQuery<Type> types = typeof(object).Assembly.GetTypes().AsParallel(); // 並列化 IEnumerable<Type> creatables = types.OfCreatable();
AsParallel()によって、列挙子の方がParallelQuery<Type>となる。しかし、OfCreatableの引数はIEnumerable<Type>なので、暗黙のキャストが発生し、結局OfCreatable内のLINQクエリは並列化されないまま実行される(以前の連載で述べたように、これは暗黙のゲートだ)。念のため、こういうことだ:
// mscorlib.dllのすべての型から、生成可能なクラスを抽出する ParallelQuery<Type> types = typeof(object).Assembly.GetTypes().AsParallel(); // 並列化 IEnumerable<Type> creatables = LinqExtensions.OfCreatable((IEnumerable<Type>)types); // IEnumerable<Type>に戻してから渡される
では、OfCreatable内のLINQクエリを並列化したい場合はどうすれば良いだろうか?
public static class LinqExtensions { // パブリックなデフォルトコンストラクタを持つ型を抽出する public static IEnumerable<Type> OfCreatable(this IEnumerable<Type> enumerable) { return from type in enumerable.AsParallel() // <-- ここで並列化 where (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null) // パブリックなデフォルトコンストラクタがある select type; } }
並列化したいのだから、内部のLINQクエリでもAsParallel()で並列化すればよいと思うかもしれないが、これは微妙だ。引数のところでゲートが出来ていることは解消されない(IEnumerable<Type>になる)ため、OfCreatableを呼び出す直前のクエリが並列クエリであったとしても、一旦並列性が解除されてしまう。
また、戻り値として返される列挙子もIEnumerable<Type>なので、ここでもゲートを作ってしまう。そして、AsParallelがハードコードされたことで、わざと非並列状態で実行させたくても出来ないという問題もある。
これらを解消するには、以下のようにすればよい。
public static class LinqExtensions { // パブリックなデフォルトコンストラクタを持つ型を抽出する(非並列化) public static IEnumerable<Type> OfCreatable(this IEnumerable<Type> enumerable) { return from type in enumerable where (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null) // パブリックなデフォルトコンストラクタがある select type; } // パブリックなデフォルトコンストラクタを持つ型を抽出する(並列化) public static ParallelQuery<Type> OfCreatable(this ParallelQuery<Type> parallelEnumerable) { return from type in parallelEnumerable where (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null) // パブリックなデフォルトコンストラクタがある select type; } }
要するに、引数(と戻り値)の列挙子の型が、ParallelQuery<T>となるようなオーバーロードを用意する。すると、中のLINQクエリの記述が全く同一であったとしても、それらの拡張メソッド(WhereやSelect等)は、Enumerable.WhereとParallelEnumerable.Whereのように呼び分けが行われる。結果として、並列バージョンではクエリ全体が並列化され、IEnumerable<T>のような通常の列挙子から呼び出される場合は、非並列クエリとなる。
これで目的を達したのだが、最後にこのメソッドを、非並列バージョンと並列バージョンのクラスに分ける。
// LINQ拡張メソッド群(非並列バージョン) public static class LinqExtensions { // パブリックなデフォルトコンストラクタを持つ型を抽出する public static IEnumerable<Type> OfCreatable(this IEnumerable<Type> enumerable) { return from type in enumerable where (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null) // パブリックなデフォルトコンストラクタがある select type; } } // LINQ拡張メソッド群(並列バージョン) public static class ParallelLinqExtensions { // パブリックなデフォルトコンストラクタを持つ型を抽出する public static ParallelQuery<Type> OfCreatable(this ParallelQuery<Type> parallelEnumerable) { return from type in parallelEnumerable where (type.IsPublic == true) && // パブリックであり、 (type.IsClass == true) && // クラスであり、 (type.IsAbstract == false) && // 抽象クラスではなく (type.GetConstructor(Type.EmptyTypes) != null) // パブリックなデフォルトコンストラクタがある select type; } }
このように、非並列バージョンと並列バージョンの拡張メソッドを分けることで、丁度EnumerableクラスとParallelEnumerableクラスが分けられているのと同じ構造となる。