余談3: C#の配列は共変
C#の配列には共変性があります。つまり、以下のコードがコンパイルできます。
string[] derivedItems = { "Aleph", "Beth", "Gimel" };
object[] baseItems = derivedItems;
// 読み出し(戻り値側、out、共変)は常に安全
for (int i = 0; i < baseItems.Length; i++)
{
Console.WriteLine(baseItems[i]);
}
逆向き(反変な代入)はできません。
object[] baseItems = { 1, 2, 3 };
string[] derivedItems = baseItems; // コンパイル エラー
C#の配列が共変なのは、ジェネリックがなかった時代(C# 1.0の頃)の名残です。 本当は認めるべきではありません。
共変性は、本来、出力(読み出し)になる型にしか認められません。 しかし、配列は、同じ型に対して入力(書き込み)もできます。 配列に対して特別に共変性を認めてしまっているので、以下のような問題が起きます。
string[] derivedItems = { "Aleph", "Beth", "Gimel" };
object[] baseItems = derivedItems;
// 書き込み(引数側、in、反変)は本当はやっちゃいけない
// でも、コンパイルが成功する。実行時エラーが出る
baseItems[1] = 100;
本当はコンパイル自体できてはいけないコードですが、実行してみるまでエラーになりません。
IEnumerable<T>
やIReadOnlyCollection<T>
などのジェネリックなインターフェイスを介してのアクセスであれば、こういう問題のあるコードは書けません。
引数でインターフェイスやデリゲートを受け取る場合
ジェネリックなインターフェイスやデリゲートを引数として渡す場合、in/outの向きが逆転します。 (戻り値の場合は逆転しません。) 例えば以下のようになります。
// 標準ライブラリの System.Func
public delegate TResult Func<in T, out TResult>(T arg);
// 引数の Func の TIn と TOut が逆
delegate Func<TIn, TOut> F<in TIn, out TOut>(Func<TOut, TIn> x);
in/out 注釈は、値を受け取る(in)か渡す(out)かの区別です。 引数で受け取ったインターフェイスやデリゲートの場合、「戻り値から値を受け取る」、「引数に値を渡す」ということになるので、こういう逆転が起きます。
interface INestedVariance<in TIn, out TOut>
{
TOut F(TIn x, Func<TOut, TIn> f);
}
class NestedVariance<TIn, TOut> : INestedVariance<TIn, TOut>
{
public TOut F(TIn x, Func<TOut, TIn> f)
{
// f の戻り値から値を受け取る = in
TIn in1 = f(default(TOut));
// f の引数にはこちらから値を渡す = out
TOut out1 = default(TOut);
var r = f(out1);
// 引数から受け取る = in
TIn in2 = x;
// 戻り値を返す = out
TOut out2 = default(TOut);
return out2;
}
}
実用例の代表は、IObserver<T>
インターフェイスとIObservable<T>
インターフェイス(どちらも標準ライブラリのSystem
名前空間に含まれるインターフェイス)でしょう。
以下のようなインターフェイスになっています。
public interface IObserver<in T>
{
void OnCompleted();
void OnError(Exception error);
void OnNext(T value);
}
public interface IObservable<out T>
{
IDisposable Subscribe(IObserver<T> observer);
}