記事のタイトルどう書けばいいか悩みました。どういう状況かイメージしにくいと思います。下記「やりたいこと」をご参照ください。
いろいろ試行錯誤しているうちに、まあまあベターなやり方じゃないかなと思う方法に行き当たったので記しておきます。
より良い方法があればご教授いただけると幸いです。
やりたいこと
ジェネリクスをとるインターフェースがあったとします
interfaceMyInterface<T>{TValue{get;set;}stringGetValueAsString();}<T>の型に制約はないため、好きな型をとることができます。
classMyObjectClass:MyInterface<object>{publicobjectValue{get;set;}publicstringGetValueAsString()=>Value.ToString();}classMyDateTimeClass:MyInterface<DateTime>{publicDateTimeValue{get;set;}publicstringGetValueAsString()=>Value.ToString("yyyy/MM/dd");}ここで、MyInterface<T>の一覧が欲しくなったとします。<T>の型は問いません。
varlist=newList<MyInterface</* ? */>>();ところが、この変数の型の定義でつまづきます。
varlist=newList<MyInterface<object>>();// あらゆる型はobjectの派生系だからこれでいいでしょと思っても…list.Add(newMyIntClass());// コンパイルエラー!!list.Add(newMyObjectClass());// これは通る。つまり T はobjectの派生型では駄目で、本当にobject型でなければならないさあどうしたものかというのがここでの課題です。
限定的な状況下で使える正しい解決法
<T>が引数または戻り値のいずれかのみに使われている場合、in Tや out Tという定義方法が可能です(詳細は「C# ジェネリック 共変 反変」で調べてみてください…)
しかし、この方法は上記の例では使えません。in, out どちらも使われる変数には
// T を in T にしてみるinterfaceMyInterface<inT>{TValue{get;set;}// 戻り値(プロパティのgetter)にも T 型が使われているため、コンパイルエラーstringGetValueAsString();}// T を out T にしてみるinterfaceMyInterface<outT>{TValue{get;set;}// 引数(プロパティのsetter)にも T 型が使われているため、コンパイルエラーstringGetValueAsString();}// in, out の併用は不可interfaceMyInterface<inoutT>{// こんな書き方はできないTValue{get;set;}stringGetValueAsString();}結論
上記の例でもジェネリクスを使うにはどうしたらいいかと試行錯誤した結果「インターフェースのうちジェネリクスに関係ないメンバーだけ抽出し、ジェネリクスなしのインターフェースを別途定義する」のがベターかなと感じました。
// ジェネリクスに関係ないメンバーのみのインターフェースinterfaceMyInterface{stringGetValueAsString();}// ジェネリクスに関係あるメンバー(ジェネリクスなしの方を継承する)interfaceMyInterface<T>:MyInterface{TValue{get;set;}}すると最初の例のlistはこのようになります。
// ジェネリクスなしの方のインターフェースで型引数をとるvarlist=newList<MyInterface>();list.Add(newMyIntClass());// OK!list.Add(newMyObjectClass());// OK!// 型引数なしの方で定義されたメンバーのみ参照可能list.ForEach(item=>Console.WriteLine(item.GetValueAsString()));より良い方法があればご教授いただけると幸いです。