まず、前置きとして、以下のようなコードを考えよう。
ごく単純に、コンテナの中の要素のうち、指定された条件を満たすものの数を数えるコードだ。
public static int Count( IEnumerable container, Predicate predicate )
{
int count = 0;
foreach( T element in container )
{
if( predicate( element ) )
{
++count;
}
}
return count;
}
このように使う。結果は予想がつくと思う。
class X
{
private readonly int _value;
public X( int value )
{
_value = value;
}
public int Value
{
get
{
return _value;
}
}
}
public static bool GreaterThan100( X x )
{
return ( x.Value > 100 );
}
void Main()
{
List xs = new List( 10 );
Random random = new Random();
for( int i = 0; i < 10; ++i )
{
int value = random.Next( 150 );
Console.WriteLine( "new X( {0} )", value );
xs.Add( new X( value ) );
}
int count = Count( xs, GreaterThan100 );
Console.WriteLine( count );
}
さて、次のようなクラスを考えたらどうだろうか?
class Y
{
private readonly X _x;
public Y( int value )
{
_x = new X( value );
}
public static implicit operator X( Y y )
{
return y._x;
}
}
YはXに暗黙の型変換が可能だ。YのインスタンスをGreaterThan100に渡すのは問題ない。
こいつを先程のCount関数で扱えるだろうか?
コードを以下のように書き換える。
void Main()
{
List ys = new List( 10 );
Random random = new Random();
for( int i = 0; i < 10; ++i )
{
int value = random.Next( 150 );
Console.WriteLine( "new Y( {0} )", value );
ys.Add( new Y( value ) );
}
int count = Count( ys, GreaterThan100 );
Console.WriteLine( count );
}
結論から言おう。このコードはコンパイルエラーになる。
Countはcontainerとpredicateの型引数が同じであることを要求する。YはXに変換可能であるが、Xと同じではないし、Xの派生クラスでもない。
そこで次に、こんなものを考えた。
interface IConvertible
{
static implicit operator To( IConvertible from );
}
class Y : IConvertible
{
private readonly X _x;
public Y( int value )
{
_x = new X( value );
}
public static implicit operator X( Y y )
{
return y._x;
}
}
public static int Count( IEnumerable container, Predicate predicate ) where T : IConvertible
{
int count = 0;
foreach( T element in container )
{
if( predicate( element ) )
{
++count;
}
}
return count;
}
「TはUに暗黙の型変換が可能でなければならない」と指定したつもりだったのだが、これも撃沈した。
考えてみれば、C#では演算子はstaticでなければならない。そして、インターフェイスにはstaticメンバを定義できない。まぁ、できたとしても、実際のクラスで実装することができないが。
仕方がないので、明示的に変換メソッドを用意した。
interface IConvertible
{
To Convert();
}
class Y : IConvertible
{
private readonly X _x;
public Y( int value )
{
_x = new X( value );
}
public X Convert()
{
return _x;
}
}
public static int Count( IEnumerable container, Predicate predicate ) where T : IConvertible
{
int count = 0;
foreach( T element in container )
{
if( predicate( element.Convert() ) )
{
++count;
}
}
return count;
}
これなら成功した。
C++の次世代規格で、C++0xというのがある。
その中では「コンセプト」と呼ばれる、テンプレート引数に制限を課す仕組みが検討されている。
詳しくはD&Eを参照して頂きたいが、あの冒頭文で、「assignable」というキーワードが登場していた。
今回の例は、紛れもなく「Y is assignable to X」であるが、C#のGenericsは最後のケース以外受け付けなかった。
ひょっとしたら、assignableでは、自動型変換とテンプレートの型推論が同居できるかと思ったのだが…無理なんだっけ?