2014年9月11日

配列のなんちゃって共変性

共変性

共変性とは、要は暗黙の型変換を容易にするための機能である。
参照型の型 A が型 B へ暗黙の型変換が可能な場合に、ジェネリックインターフェースの共変な型パラメータ(out 付き型パラメータ)に型 A を指定した If<A> は同じインタフェースの If<B> へ暗黙の型変換が可能になる。
※out が付いた型パラメータは出力(メソッドの戻り値、読取専用プロパティの型)にしか使えなくなる。
   出力用の out 引数には使えない。→理由

インタフェースの他に、デリゲートの型パラメータも共変にすることができる。

[共変性のサンプル]

class A : B { } // B はクラスまたはインターフェース

static class TestCovariance
{
    static void Main(string[] args)
    {
        // 暗黙の型変換
        A a = new A();
        B b = a;

        // 共変のインターフェース IEnumerable<out T>
        IEnumerable<A> aList = new List<A>();
        IEnumerable<B> bList = aList;

        // 共変のデリゲート Func<out TResult>
        Func<A> aFunc = () => new A();
        Func<B> bFunc = aFunc;
    }
}

配列の共変性

C# 及び VB の配列は共変性がある。

共変性があるのに、不変な型パラメータを持つ ICollection<T>IList<T> を実装している。
※普通のコードでは実装できないため、実行時に提供するという特殊なやり方で。
これはジェネリック(.NET 2.0~)が配列(.NET 1.0~)より後に導入されたためでもあるのだが、そもそもイミュータブルでない配列を共変にしているのがおかしな話なのである。

MSDN にも書いてある話なのだが、配列を暗黙の型変換することでタイプセーフではなくなる。
例えば、次のコードはコンパイルできるが、実行時エラー ArrayTypeMismatchException が発生する。

static class TestCovariance
{
    static void Main(string[] args)
    {
        string[] strings = {"1"};
        object[] objects = strings;
        objects[0] = 1; // ここで例外発生
    }
}

IReadOnlyList<T>

上記の気持ち悪さを解決するためかは分からないが、.NET 4.5 からイミュータブルな配列を扱える IReadOnlyList<T> が導入されている。

static class TestCovariance
{
    static void Main(string[] args)
    {
        // Array.AsReadOnly の戻り値は ReadOnlyCollection<T>
        // このメソッドは .NET 2.0 からあったが IReadOnlyList<T> が存在しなかった
        IReadOnlyList<string> strings = Array.AsReadOnly(new[]{"1"});
        IReadOnlyList<object> objects = strings;
        objects[0] = 1; // コンパイル NG
    }
}

.NET 4.0 までの資産は IList<T> や ICollection<T> 前提で作ってあることが多いので、今更導入されてもという気がするのだが、よりタイプセーフに実装できるということで・・・

参考URL

0 件のコメント:

コメントを投稿