反変性と共変性
ここ数日、こいつで引っかかったけど、ようやく理解できたので備忘録。
コトの発端
こんなコードを書いていた。
interface ISample<in T> { void InvokeAction(Action<T> action); }
そしたら、コンパイラに怒られた。
無効な分散: 型パラメーター 'T' は、'ConsoleApplication1.ISample<T>.InvokeAction(System.Action<T>)' で有効な 共変的 である必要があります。'T' は 反変 です。
これが直感的に理解できなかったってのがコトの発端でして。
何故これがダメなのか?
これが許容されると、こんなワケのわからないコードがコンパイル可能と言うことに帰結する。
internal class Program { private static void Main(string[] args) { ISample<string> sample = new Sample<object>(200); //ちょっとマテ、元はobjectだし、実際入ってるのはintだし、これは拙くなイカ? sample.InvokeAction((string value) => Console.WriteLine(value.Substring(2))); } } internal interface ISample<in T> { void InvokeAction(Action<T> action); } class Sample<T> : ISample<T> { public Sample(T value) { Value = value; } public T Value { get; set; } public void InvokeAction(Action<T> action) { action(Value); } }
Actionの型パラメタは反変だから、インターフェースの型パラメタも反変なら、このように書けるだろうと、直感的に思いがちだけど、さに非ず、実際は反変の型パラメタを持つActionを使いたいのなら、インターフェースの型パラメタは以下のように共変である必要がある。
internal class Program { private static void Main(string[] args) { ISample<object> sample = new Sample<string>("hello world"); //これなら、常にアップキャストが保障されるので、大丈夫 sample.InvokeAction((object value) => Console.WriteLine(value)); } } internal interface ISample<out T> { void InvokeAction(Action<T> action); } internal class Sample<T> : ISample<T> { public Sample(T value) { Value = value; } public T Value { get; set; } public void InvokeAction(Action<T> action) { action(Value); } }
よくよく考えれば当たり前だけど、引っかかったので、備忘録的に。