あの後、実は指定できないこともないということに気づきました。ごめんなさい。
まず、なぜうまく指定できないと書いたかですが、先日の記事であげたケースと物品の例で説明します。
ケース クラスには格納する物品を指定するためのジェネリックパラメータを用意します。
物品 クラスには親となるケースを指定するためのジェネリックパラメータを用意します。
つまり、ケース<TArticle> クラスと 物品<TCase> クラス となるわけです。
この時、ケース クラスのジェネリックパラメータ TArticle の型制約には、物品<TCase> を指定するわけですが、物品<TCase> の型パラメータ TCase を指定しなければなりません。ここで不都合が生じるわけです。TCase に対して ケース<TArticle> と指定することはできますが、これではダメなんです。
なぜ不都合かというと、ジェネリック クラスは、型パラメータが完全一致していない場合、変換不可だからです。例えば、List<object> と List<int> では、object と int の間には継承関係がありますが、そんなの関係なく変換不可です。List<int> 型の変数を List<object> 型にキャストすることはできないのです。
すると、TCase が指定できないことがわかります。例えば、ケース クラスを継承した CDケース クラスでは、TCase は、CDケース にならなければなりません。ケース<CD> ではダメなのです。 ( 物品<ケース<CD> は 物品<ケース> への変換が不可ということです。 )
と、ちょっとややこしい話になってしまいましたが、要はこのままでは型制約の指定は無理だということです。で、先日の記事には無理だよ~と書いたわけです。
では、どうすればいいか、というのが今回の記事の本題です。
答えは、「自身のクラス階層を含め、ミラー階層において関連するクラス ( 階層 ) 全てを型パラメータに持たせる」 となります。
つまり、ケースには TCase と TArticle の2つの型パラメータを持たせ、物品にも TCase と TArticle の2つの型パラメータを持たせるわけです。
こうすることによって、先ほどの問題が解決できます。先ほど指定できなかった TCase には、ケースに新しく追加した型パラメータ TCase を指定すればいいわけですから。
コードにすると以下のようになります。
public abstract class ケース<TCase, TArticle>
where TCase : ケース<TCase, TArticle>
where TArticle : 物品<TCase, TArticle>
{
}
public abstract class 物品<TCase, TArticle>
where TCase : ケース<TCase, TArticle>
where TArticle : 物品<TCase, TArticle>
{
}
これらを継承した CDケース クラスと CD クラスは以下のようになります。
public sealed class CDケース : ケース<CDケース, CD>
{
}
public sealed class CD : 物品<CDケース, CD>
{
}
TCase と TArticle に関連しない型を指定した場合 ( 例えば ケース<CDケース, ペン> ) 、コンパイルエラーとなりますので、変なバグが入り込んだりすることはないかと思います。
これで問題は解決しましたが、一つ気を付けなければならないことがあります。ケース クラスに TCase という型パラメータ、そして 物品 クラスに TArticle という型パラメータがあることは、他人から見ておかしなことをやっているように見えてしまう ( つまり理解し難い ) ということです。こればかりは仕方ありませんので、理由や使い方をしっかりとドキュメントに書き記すようにしましょう。( いや、理由はしっかり書くとややこしくなるからざっくりとの方がいいかもw )
[ 余談 ]
ちなみに、これ以外にも方法はあります。非ジェネリックな基本クラスを用意するという方法です。つまり、「ケース クラス」 とこれを継承した 「ケース<TArticle> クラス」、「物品 クラス」 とこれを継承した 「物品<TCase> クラス」 を用意するわけです。こうすれば、型制約には非ジェネリックな基本クラスを指定することができます。
ミラー階層
初めまして、三輪の牛と申します。
以前からミラー階層のエントリは拝見して私の引っかかっているところを明確に書いてくださっていると感じておりました。この問題の解法を何度となく考えてみるのですが現状C#の言語仕様ではミラー階層をすっきり記述することはできなさそうです。
NetFrameworkで言うと、DataTable→TypedDataTable, DataColumn→TypedDataColumnの関係がまさに余談に書いておられる例になります。
逃げの手になるのですがdynamicを使うのも1つの方法かと考えています。
ダウンキャストを無くすためには新しい発想で言語仕様そのものを設計し直す必要があるのではないかと考えています。
トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/133-089884b9