C# のジェネリックで Enum/Delegate 制約を使う


C# のジェネリック型制約には大きな制限があります。それは制約する型に System.DelegateSystem.Enum が使えないということです。 F# や Nemerle といった,ジェネリックの制約型に制限がない別の言語を用いるという選択肢も当然考えられますが,可能な限り C# だけで解決したいということもあるかもしれません。

とは言っても C# でできないから困ってるのであって,本当に C# だけで解決するのは難しいです。そこでもう少し低レベルな MSIL を使って解決しましょう。

準備

IL Support という Visual Studio の拡張を利用します。この拡張は, C# や F# のプロジェクトで MSIL を利用するための補助機能が含まれます。

IL Support は,具体的には一部のコードを宣言のみで実装せずにコンパイルし,それを逆コンパイルした後に IL コードとマージして再度コンパイルをする,という手法をとっているようです。 Visual Studio の無償版ではおそらく拡張機能は使えないので IL Support は利用できませんが,同様の考え方で同じことが実現できるはずです[A]

プロジェクトの作成

IL Support をインストールして再起動すると,プロジェクトテンプレートに IL Support プロジェクトが追加されています。例えばクラスライブラリプロジェクトを作成すると,次のようなファイルが追加されます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;

namespace Library1
{
   public class Class1
   {
      [MethodImpl(MethodImplOptions.ForwardRef)]
      public extern int Square(int number);
   }
}
.class public Library1.Class1
{
   .method public int32 Square(int32 number) cil managed
   {
      .maxstack 2
      ldarg.1
      dup
      mul
      ret
   }
}

見て想像できるように, Square というメソッドの中身を IL で記述することができます。 IL レベルではジェネリック型制約に System.DelegateSystem.Enum が使えるので,このような制約が必要な部分を IL で書いてやれば良いわけです。ちなみに extern で宣言する部分は IL の宣言とマッチしている必要はありません。 IL の方が優先されるので, C# でジェネリック型制約が書けないので宣言ができない,という心配はありません。というか extern の宣言は無くても良いみたいです。

IL を直接書ける人は少数派だと思うので,いったん制約を甘くしたプロトタイプのコードを記述してコンパイルし, ildasm で逆アセンブルした IL コードを修正するか,あるいは最初に述べたような制限のない言語で記述してコンパイル,逆コンパイルした IL コードをコピペする,という方法が容易だと思います。

他の使い方

IL Support は F# プロジェクトサポートもあるので, F# で family のアクセシビリティ (C# の protected) を使うこともできます。これで C# と F# がもっと仲良しになれる気がします。

脚注

  1. 各言語のコンパイラに加え, IL アセンブラ (ilasm) と IL 逆アセンブラ (ildasm) を使います。 []