Advent LINQ (10): .NET 2.0でLINQを使う

LINQは、正式には.NET 3.5 / C# 3.0でサポートされた。しかし、強力な柔軟性と拡張性の高さを、.NET 2.0のプロジェクトでも使いたいと思う事がある。実は、工夫さえすれば、.NET 2.0環境でもLINQを使う事が出来る。
LINQを使うためには、2つの要件を満たす必要がある。

  • C#コンパイラが、LINQクエリ構文を解釈出来るようにする。
    これを満たすためには、C# 3.0以上のコンパイラを使用すればよい。つまり、Visual Studio 2008以上の環境を使えば、コンパイラはfrom・where・select・orderbyなどのLINQクエリ構文や、ラムダ式を解釈可能になる。例え、コンパイラのターゲットが.NET 2.0に設定されていても、これらの構文は解釈可能だ。
  • LINQの拡張メソッド群(Enumerableクラスなど)が必要。
    これは当然、.NET 2.0のmscorlib.dllやSystem.dllには含まれていない。従って、何とかして用意する必要がある。

前者の要件に絡む、分かりにくい問題がある。「拡張メソッド」の扱いだ。C# 3.0以上のコンパイラを使えば、拡張メソッド構文は解釈可能だ。但し、拡張メソッドが定義されたクラスは特殊な扱いを受ける。

// 独自実装のEnumerable
[Extension] // <-- Extension属性
public static class Enumerable
{
    // 独自実装のWhere
    [Extension] // <-- Extension属性
    public static IEnumerable<T> Where<T>(this IEnumerable<T> enumerable, Func<T, bool> predict)
    {
        foreach (var value in enumerable)
        {
            if (predict(value) == true)
            {
                yield return value;
            }
        }
    }
}

C# 3.0以上のコンパイラで拡張メソッドを定義すると、暗黙にExtensionAttribute属性がクラスとメソッドに適用される(上の例ではわざと書いた)。そのため、アセンブリを生成する際にこの拡張メソッドが見つからないと、ビルドに失敗する。当然、.NET 2.0のmscorlib.dllやSystem.dllには存在しないため、以下のようなコードも盛り込んでおく必要がある。

namespace System.Runtime.CompilerServices
{
    // オレオレExtension属性
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
    public sealed class ExtensionAttribute : Attribute
    {
        public ExtensionAttribute()
        {
        }
    }
}

そして、こまごまとしたクラス(例えば、Func<T>やAction<T>など)と、あなたが使いたい標準的なLINQ拡張メソッド群(Where<T>やSelect<T>やOrderBy<T>等)を用意する必要がある。これらを用意すれば、「ほぼ」C# 3.5以降のLINQを模倣できる。
ほぼ、と書いたのは、どうしても回避出来ない制限が一つだけあるためだ。それは、IEnumerable<T>が、ジェネリック共変性をサポートしていない事による。これによって発生する問題は別の機会に譲るとして、実際問題、大量のLINQ拡張メソッドを自前で準備するのはなかなか難しい。

そして、同じような事を考える人は世の中にも大勢いて、NuGetでパッケージ化されていたりもするので、特に理由が無ければ、このようなパッケージを利用したほうが良いだろう。以下の「linqbridge」は、単一ソースコードのバージョンもあるので、自分のライブラリに容易に組み込みやすいだろう。

linqbridge – Re-implementation of LINQ to Objects for .NET Framework 2.0
NuGet LINQBridge
NuGet LINQBridge (Embedded)

なお、linqbridgeはPLINQをサポートしていない。AsParallel()から始まるPLINQを用意するのは複雑すぎる。自前で実装するのは不可能ではないが、そんな事を考えるならC# 3.0に移行する事を真剣に考えた方が良い。どうしても並列実行を諦めることが出来ないのなら、TPLの自前実装で我慢しよう。Palallel.ForEach()なら、自力でも実装できるはずだ。