配列の共変性はちょっと壊れてる

元ネタ http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx


配列の共変性はちょっと壊れてるというお話。試してみました。
このコードは正常に実行できるでしょうか?コンパイルエラーでしょうか?それとも例外発生でしょうか?例外だとしたらいつ発生するでしょうか? Student代入時?表示の際?答えはコードの後に。

using System;

namespace Variance
{
  class Program
  {
    static void Main()
    {
      Person[] persons = new Employee[] {
        new Employee { Name = "Aramaki", ID = 1 },
        new Employee { Name = "Ishikawa", ID = 2 },
        new Employee { Name = "Saito", ID = 3 } };

      persons[ 0 ] = new Student { Name = "Yasako", Grade = 5 };

      Array.ForEach( persons, Console.WriteLine );
      Console.ReadKey();
    }
  }

  class Person
  {
    public string Name;

    public override string ToString()
    {
      return this.GetType().Name + ": " + this.Name;
    }
  }

  class Employee : Person { public int ID; }

  class Student : Person { public int Grade; }
}

正解は、実行時、Student 代入の際に例外が発生します。
Ericさんは、コンパイラがエラーを検出できないことを「壊れている」と言ってます。いい感覚ですね。

なんでそうしたのかってのは、CLRJava 互換の道を選んだからだそうです。MS は .NETJava って道を捨ててないのかな。MS が手を出せなくてもほかの誰かが…とか考えているのかな。

ちなみに、デリゲートの共変性と反変性のほうはコンパイル時にエラーを検出してくれます。これはまたそのうち、って年越しそうw

(追記) せっかくなので、配列の共変性についてもうちょっとメモ。きっと使わない機能だし(^^;

  • 共変性は参照型の配列だけにある。値型の配列にはない。
  • 「Person[ ] persons = new Employee[ ] {...」は、EmployeeをPersonに暗黙に変換できるので、Employee[ ]をPerson[ ]に変換できる。これが共変性。
  • Person[ ]は実際にはEmployee[ ]のこともある、ということ。
  • それはつまり、Person[ ]にPerson型を入れたら実行時例外が起きる場合があるということ。
  • 暗黙の参照変換だけでなく、明示的な参照変換がある場合でもOK。当然だけど、要素の変換には明示的な変換が必要。