M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

C#のメソッド戻り値や可視性が共変(Covariance)でないのが地味ーに窮屈

近ごろお手製ライブラリのJava版C#版のあいだを行き来していて、その中で地味ーに窮屈だと感じているのが、「C#のメソッド戻り値や可視性が共変(Covariance)でない」ということです。

※一応註記しておくと、ここで「戻り値の共変」と述べているのはジェネリクスの型パラメータに関してではありません。戻り値の型そのものです。つまりList<Animal>List<Cat>にできないと言っているのではなく、List<Animal>を(例えば)LinkedList<Animal>にできないと言っているのです。

前提

まず前提として以下のようなクラス継承関係があるとします:

  • Objectを継承するSupplierAクラス
  • SupplierAクラスを継承するSupplierBクラス
  • Objectを継承するSuppliedAクラス
  • SuppliedAクラスを継承するSuppliedBクラス
  • SupplierA#supply()メソッドはSuppliedAクラス・インスタンスを戻り値とする
  • SupplierA#supply()メソッドの可視性(アクセシビリティ・レベル)はpackage private/internalである
  • SupplierB#supply()メソッドはSupplierA#supply()メソッドをオーバーライドする

問題

Javaではメソッド戻り値と可視性はいずれも共変なので、以下のようにSupplierB#supply()メソッドの戻り値をSuppliedBクラス・インスタンスにしたり、可視性をpublicに変更したりできます。そのようにしてもSupplierBは依然としてSupplierAとしても妥当であると考えられます:

public class SupplierA {
    SuppliedA supply() { /* ... */ };
}

public class SupplierB extends SupplierA {
    public SuppliedB supply() { /* ... */ };
}

SuppliedA sa = new SupplierB();

このようなことはC#の世界ではできません。以下のようなコードはSupplierB#Supply()メソッドの戻り値についても可視性についてもコンパイル・エラーとなります:

public class SupplierA {
    internal virtual SuppliedA Supply() { /* ... */ };
}

public class SupplierB : SupplierA {
    public override SuppliedB Supply() { /* ... */ };
}

SuppliedA sa = new SupplierB();

SupplierB#Supply()の宣言にnewキーワードをつければエラーはなくなりますが、これはもはやオーバーライドではなく、SupplierBインスタンスを(SuppliedA型で宣言された変数に代入するなどして)SupplierA型として参照するとそのSupply()メソッドはSuppliedA クラスで宣言されたものになってしまいます。