目標
目標は
MyItem1,MyItem2をMyItemの派生クラスとしたとき、
MyClass<MyItem1>,MyClass<MyItem2>を同じ型(例 MyClass<MyItem>)にキャストすることです。
なかなか情報が見つからなくて苦労したのでシェアしておきます。
失敗例
abstract class MyItem
{}
class MyClass<T>
where T:MyItem //TはMyItemの派生クラスであるという制限を加える。
{}
class MyItem1:MyItem
{}
class MyItem2:MyItem
{}
//テスト
public void GenericTest(){
MyClass<MyItem1> test1=new MyClass<MyItem1>();
MyClass<MyItem2> test2=new MyClass<MyItem2>();
//Debug.Log((test1 as MyClass<MyItem>)); //Compile Error!
//Debug.Log((test2 as MyClass<MyItem>)); //Compile Error!
}
自分が最初に書いたコードですが、残念ながら上手く機能しませんでした。
ちなみにDebug.Logで出力しているのはUnityを使ってるからです。適宜Cosole.WriteLineで読み替えてください。
解決策
この問題の解決策はinterfaceを用いることです。
//インターフェイスの追加
interface IMyItem
{}
interface IMyClass<out T> //out修飾子は必須!
where T:IMyItem
{}
//クラスの定義
abstract class MyItem:IMyItem
{}
class MyClass<T>:IMyClass<T>
where T:MyItem //T:IMyItemでもok
{}
class MyItem1:MyItem
{}
class MyItem2:MyItem
{}
//テスト
public void GenericTest(){
MyClass<MyItem1> test1=new MyClass<MyItem1>();
MyClass<MyItem2> test2=new MyClass<MyItem2>();
//Debug.Log((test1 as MyClass<MyItem>)); //Compile Error!
Debug.Log((test1 as IMyClass<MyItem>)); //MyClass'1[MyItem1]
Debug.Log(test2 as IMyClass<IMyItem>); //MyClass'1[MyItem2]
}
だいぶ複雑というか面倒くさくなってしまいましたがこれでうまく動きます。
covariance(共変性)という仕組みが関わっていて、
公式のドキュメントがとても参考になりました。というか、ぶっちゃけ上のコードは公式の二個目の記事を読めばいらないまである。
最後にMyClassをリストにしてキャストしてみましょう。
public void GenericTest(){
List<IMyClass<IMyItem>> list=new List<IMyClass<IMyItem>>(){
test1,
test2
};
//Debug.Log((list as List<MyClass<MyItem>>)); //Compile Error!
Debug.Log((list as IList<MyClass<MyItem>>)); //Null
Debug.Log((list as IList<IMyClass<MyItem>>)); //Null
Debug.Log(list as IList<IMyClass<IMyItem>>); //System.Collections.Generic.List`1[IMyClass`1[IMyItem]]
var castList=(IList<IMyClass<IMyItem>>)list;
foreach(var ele in castList){
Debug.Log(ele);
}
//foreach result
//MyClass`1[MyItem1]
//MyClass`1[MyItem2]
}
前のコードではIMyClass<MyItem>、IMyClass<IMyItem>両方とも正しくキャストされていましたが、
今回はIMyClassの場合もNullになってしまいました。
結論
MyClass<MyItem>の型を上手くキャストさせたいなら、
それぞれMyItemとMyClassのインターフェイスを作成して
IMyClass<IMyItem>でやりとりしよう。