読者です 読者をやめる 読者になる 読者になる

C# 3.0 による .NET 2.0 アプリケーション開発 - XNA で LINQ を使おう

.NET XNA

Visual Studio 2008 の新機能のひとつに「Multi-Targeting」があります.これは,Visual Studio 2008,つまり C# 3.0 や Visual Basic 9 を用いつつ,ターゲット環境を .NET 2.0/3.0/3.5 から選択できる,というものです.

C# 3.0 や Visual Basic 9 の新機能は,基本的にコンパイル時のシンタックスシュガーであり,そのうちいくつかの機能は System.Core.dll に定義された型に依存しています.
var キーワード,匿名関数としてのラムダ式などは,ターゲットとして .NET 2.0 を選んだ場合にも使うことができる機能の一例です.
一方,System.Core.dll に依存する言語新機能は,.NET 2.0 をターゲットとするプロジェクトでは使うことができません.表向きは.

.NET 2.0 アプリケーションで LINQ を使う

LINQ は System.Core.dll に定義された型を使用します.例えば System.Func delegate,LINQ のクエリ演算子を提供する System.Linq.Enumerable クラス,拡張メソッドであることを示す System.Runtime.CompilerServices.ExtensionAttribute などがそれにあたります.これらの型は,C# コンパイラがコンパイル時に行うコード変換に用いられて,コンパイル時に見つからないとコンパイルエラーになります.
さてここで問題です.ターゲットとして .NET 2.0 を選びつつ,これらの足りない型を自前で用意してみたらどうなるでしょうか?

using System;
using System.Collections.Generic;
using System.Text;

namespace System
{
    public delegate TResult Func<TResult>();
    public delegate TResult Func<T, TResult>(T arg);
    public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
    public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
    public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
}
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
    public sealed class ExtensionAttribute : Attribute
    {
    }
}
namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource, TResult>(
            this IEnumerable<TSource> source, Func<TSource, TResult> selector)
        {
            foreach (var item in source)
            {
                yield return selector(item);
            }
        }
        public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
        {
            return new List<TSource>(source);
        }
    }
}

namespace ConsoleApplication1
{
    using System.Linq;

    class Program
    {
        static void Main(string[] args)
        {
            var array = new []{1, 2, 3, 4};
            var seq = from x in array
                      select x;
            seq.ToList().ForEach(Console.WriteLine);
        }
    }
}

答えはなんと「コンパイルは成功する」が csc.exe の挙動としては正解です.(規格がこの辺についてためになる一言を含んでいるかは未調査)
この例では Enumerable.Select のみを実装していますが,他の拡張メソッドも順次再実装することで,LINQ to Object のそれなりの部分はカバーできるでしょう*1.昔の sequence.cs を保存していた人はおめでとう,ですな.あとはまあビルドプロセスをちょこっと工夫すれば,LINQ を使って XNA 開発なんてのもありでしょうね.
もっとも,本来 System.Core.dll に定義されているはずの型を詐称するわけで,.NET 3.5 のアセンブリと混ぜるな危険の暗黒面.ご利用は計画的に.

追記 (2008年8月6日)

2008年8月6日現在,Microsoft 以外によるいくつかの Linq to Object 実装がいくつか存在します.
ひとつは『Enumerableならあります - ものがたり』にて紹介頂いた Mono の実装です.
他のものについては『.NET Framework 2.0 で LINQ を使う方法 - NyaRuRuの日記』にてまとめて紹介してあります.

*1:Expression Trees はちょっとつらそう