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クラスが分けられているのと同じ構造となる。