項目22 ジェネリックの共変性と反変性をサポートする
独自にインターフェイスやデリゲートを作成する場合、できる限り反変性in
あるいは共変性out
を設定する。
共変性と反変性をサポートするメリット
作成したインターフェイスやデリゲートの変性*1が誤用されたとしても、コンパイラが検出してくれる。
ジェネリック型C<T>における共変性と反変性
型Xが型Yに変換できる(Y:基底、X:派生)ことがわかっている場合
共変性
C<X>をC<Y>に変換できる(C<Y>←C<X>)
反変性
C<Y>をC<X>に変換できる(C<X>←C<Y>)
配列の共変性にある問題
基底クラスの配列に派生クラスの配列を割り当てることができるが、その要素に別の派生クラスを入れようとすると実行時エラーになる。
static void Main(string[] args) { var planets = new CelestialBody[] { new Planet { Name = "mar", Mass = 1000 } }; CoVariantArray(planets); UnsafeVariantArray(planets); CelestialBody[] spaceJunk = new Moon[2] { new Moon(), new Moon { Name="ast",Mass=10000} }; UnsafeVariantArray(spaceJunk);//System.ArrayTypeMismatchException: '配列と互換性のない型の要素にアクセスしようとしました' spaceJunk[0] = new Planet();//System.ArrayTypeMismatchException: '配列と互換性のない型の要素にアクセスしようとしました' } abstract public class CelestialBody :IComparable<CelestialBody> { public double Mass { get; set; } public string Name { get; set; } public int CompareTo(CelestialBody other) { throw new NotImplementedException(); } } public class Planet : CelestialBody { } public class Moon : CelestialBody { } public class Asteroid : CelestialBody { } //これはタイプセーフ public static void CoVariantArray(CelestialBody[] baseItems) { foreach (var thing in baseItems) Console.WriteLine($"{thing.Name}の質量は{thing.Mass}Kgです"); } //これはタイプセーフではない public static void UnsafeVariantArray(CelestialBody[] baseItems) { baseItems[0] = new Asteroid { Name = "Hygiea", Mass = 8.85e19 }; }
※基底クラスの配列の要素に派生クラスの配列を入れるのはエラー出ない。
CelestialBody[] spaceJunk = new CelestialBody[2] { new Moon(), new Moon { Name="ast",Mass=10000} }; spaceJunk[0] = new Planet();
C#における共変性、反変性
共変性:out
修飾子がつく
出力位置((限定された場所のこと。関数の戻り値、プロパティのget
アクセサ、デリゲートのシグネチャの一部))にある型がT
に制限されるようコンパイラに強制させることができる。
反変性:in
修飾子がつく
T
が入力位置にのみ表れるようコンパイラに強制させることができる。
デリゲートでは共変性と反変性が逆転してしまうことがあることに注意
Derived
クラスをBase
クラスからの派生とした場合、
ICovariantDelegate<out T>
に対して*2
ICovariantDelegate<Base> ib = new CovariantDelegate<Derived>();
Func<T> GetAnItemLater();
使用者:返り値がFunc<Base>
であり、このデリゲートを使用すると、Base
が返ってくると期待する。
実際:Func<Derived>
が返り値で、このデリゲートを使用するとDerived
が返ってくる。しかしBase
に変換できるので問題なし。
Func<Base> fb = ib.GetAnItemLater(); Base b = fb(); > fb.GetType() [System.Func`1[Submission#0+Derived]] > b.GetType() [Submission#0+Derived]
void GiveAnItemLater(Action<T> WhatToDo);
使用者:Action<Base>
を設定する。
実際:GiveAnItemLater
は引数がAction<Derived>
と考えているのでAction<Base>
にDerived
を入れる。しかしBase
に変換できるので問題なし。
> ib.GiveAnItemLater((Base b) => { Console.WriteLine(b.GetType().Name); }) Derived
IContravariantDelegate<in T>
に対して
IContravariantDelegate<Derived> id = new ContravariantDelegate<Base>();
void GetAnItemLater(Func<T> item);
使用者:Func<Derived>
を設定する。
実際:GetAnItemLater
はFunc
からの返り値がBase
であるとして処理をする。Derived
が返ってくるがBase
に変換できるので問題なし
> id.GetAnItemLater(() => new Derived() );
Derived
Action<T> ActOnAnItemLater();
使用者:返り値がAction<Derived>
であることを期待し、このデリゲートにDerived
を入れる。
実際:Action<Base>
が返り値で、このデリゲートにDerived
を入れる。しかしBase
に変換できるので問題なし。
> Action<Derived> ad = id.ActOnAnItemLater(); > ad.GetType() [System.Action`1[Submission#0+Base]] > ad(new Derived()); Derived
結局どういうことなの
派生クラスは基底クラスに変換できるということ。
- 引数として基底クラスを期待してるメソッドには派生クラスを入れることができる。
Action<Derived>
にAction<Base>
を代入(反変性)。
ab(new Derived())
が可能であるからad
に代入しても問題ない。
> Action<Base> ab = (target) => Console.WriteLine(target.GetType().Name);
> Action<Derived> ad = ab;
> ad(new Derived());
Derived
Action<Derived>
の引数は中身がAction<Base>
でもDerived
じゃないとだめ。
> ad(new Base()); (1,4): error CS1503: 引数 1: は 'Base' から 'Derived' へ変換することはできません。
- 返り値として基底クラスを期待している型には派生クラスを返すことができる。
Func<Base>
にFunc<Derived>
を代入(共変性)。
> Func<Derived> fd = () => new Derived();
> Func<Base> fb = fd;
> Base br = fb();
> br.GetType().Name
返り値はBase
(に変換されたDerived
)なのでDerived
で受けるには型変換が必要。
> Derived dr = fb(); (1,14): error CS0266: 型 'Base' を 'Derived' に暗黙的に変換できません。明示的な変換が存在します (キャストが不足していないかどうかを確認してください) Derived dr = (Derived)fb();
デリゲートでの逆転のとこコード
public class Base { public Base() { } } public class Derived : Base { public Derived() { } } public interface ICovariantDelegate<out T> { T GetItem(); Func<T> GetAnItemLater(); void GiveAnItemLater(Action<T> whatToDo); } public class CovariantDelegate<T> : ICovariantDelegate<T> where T:new() { public T GetItem() { return new T(); } public Func<T> GetAnItemLater() { return () => new T(); } public void GiveAnItemLater(Action<T> whatToDo) { whatToDo(new T()); } } ICovariantDelegate<Base> ib = new CovariantDelegate<Derived>(); Func<Base> fb = ib.GetAnItemLater(); Base b = fb(); > fb.GetType() [System.Func`1[Submission#0+Derived]] > b.GetType() [Submission#0+Derived] > ib.GiveAnItemLater((Base b) => { Console.WriteLine(b.GetType().Name); }) Derived public interface IContravariantDelegate<in T> { void ActOnAnItem(T item); void GetAnItemLater(Func<T> item); Action<T> ActOnAnItemLater(); } public class ContravariantDelegate<T> : IContravariantDelegate<T> where T : new() { public void ActOnAnItem(T item) { } public void GetAnItemLater(Func<T> item) { var t = item(); Console.WriteLine(t.GetType().Name); } public Action<T> ActOnAnItemLater() { return (T t) => { Console.WriteLine(t.GetType().Name); }; } } IContravariantDelegate<Derived> id = new ContravariantDelegate<Base>(); > id.GetAnItemLater(() => new Derived() ); Derived > Action<Derived> ad = id.ActOnAnItemLater(); > ad.GetType() [System.Action`1[Submission#0+Base]] > ad(new Derived()); Derived