概要

前回(『プログラミング C#』 その5 コレクション)のつづき。流し読みノートを書くぞ。この章には継承に関するクラスのアレコレが書かれていた。インタフェースとか、キャストとか、アクセシビリティとか。

継承は Python 本でもかならず章が割かれているテーマだ。当時のノートも振り返っておこう。

 

流し読みノート

基底と派生の基本的なところ

  • 基底クラス、派生クラスあたりの用語は Python と同じだね。
  • ただ Python と違って、多重継承ができるのはインタフェースクラスだけ。
// インタフェースクラスひとつめ。
interface IBase1
{
    void Method1();
}

// ふたつめ。
interface IBase2
{
    void Method2();
}

// インタフェースを複数継承。
interface IBoth : IBase1, IBase2
{
    void Method3();
}

// インタフェースではメソッドを空っぽで書いたけど、
// クラスでは実装を書く。
public class Impl : IBoth
{
    public void Method1() { ... }
    public void Method2() { ... }
    public void Method3() { ... }
}
  • 基底クラスを Base あるいは B、派生クラスを Derived あるいは D と表記する。
    • 個人的には親クラス子クラスっていう言い方がイメージしやすい。基底、派生のほうが実態に近いとは思う。

 

キャスト、ダウンキャスト、ポリモーフィズム

  • 基底クラスから派生したクラスは、暗黙的に基底クラスへキャスト(変換)できる。
    • 子クラスは親クラスのメンバを全部もってるのだから、当然だね。
  • 基底クラスで実行できるように書かれたコードが、派生クラスでも実行できることをポリモーフィズムという。
    • 基底クラスで実行できるということは、基底クラスのメンバだけ使うコードということだ。基底クラスのメンバをもっている派生クラスでも実行できるのは当然だ。

このポリモーフィズムの説明は、なるほどって感じだ。ところで Python にもポリモーフィズム(多態性)がある。昔のノート(2016-01-08)Mark Lutz『初めてのPython』によれば

  • ポリモーフィズムというのは多態性のことで、たとえば + が数値についていれば足し算を意味し、文字列についていれば結合を意味すること。

とのことなんだけれど、これはちょっと違うポリモーフィズムか……?

  • 派生 -> 基底 の変換は上述のように普通にキャストというけれど、 基底 -> 派生 の変換をダウンキャストという。
    • あ、キャストは親クラスへの変換だけを指すものではもちろん無いはずだ。文字列から数値への変換もキャストっていうもんね。
  • ダウンキャストは成功するとは限らない。やり方はいくつかあり、成功しなかったときの挙動は次のとおり。
    • 失敗でエラーが起こるキャスト構文。 (Derived)base。失敗すると InvalidCastException が発生する。
    • 失敗しても OK なときは as 演算子。 base as Derived。失敗すると null が返ってくる。

 

ジェネリッククラスの派生とキャストはどうやんの

  • ジェネリック型の型引数 T はそのままで派生してもいいし特定の型にしてもいい。
  • ジェネリック型のキャストは、その型引数同士が変換可能ならできる。
// ジェネリックの基底クラス。どんな型でも Item に設定できる。
public class GenericBase<T>
{
    public T Item { set; get; }
}

// ジェネリックなまま派生するとき。どんな型でも Item に設定できるまま。
public class GenericDerived<T> : GenericBase<T>
{
}

// 特定の型にするとき。 Item の型は string に限定された。
public class NonGenericDerived : GenericBase<string>
{
}

 

読み飛ばした共変性と反変性

ここはサッと読み飛ばしたけど、ちょろっとノートだけしておく。

  • ジェネリック型の変換が、 T と同じ向きに行えることを共変性という。
  • その逆が反変性。はんぺん。
  • これらの用語は圏論 category theory からきてる。
  • 共変性には out キーワードを使う。参照渡しの項目で出てきたやつだけど、それとは違う。
    • こんなふうに、関連のない機能をキーワードに複数もたせるのは C の伝統なんだって。ややこしいな……と思ったけれど、 Python にもあるかな? in とかそうかも?
  • 反変性には in キーワードを使う。
  • この変換は C# バージョン4より前では動かないかもしれない。 .NET2.0 でジェネリックが導入されたのに、 C# はバージョン4までそれにちゃんと対応していなかったから。裏話ゲットだ。

 

全クラスの基底クラス object

  • クラス書くとき、基底クラスなしにすることはできるけど、そのときは自動で System.Object が基底クラスになっている。
    • C# では object だ。
    • Python の object と同じだね。
    • こういう「自動で補完されているもの」を知ると一気にコードの理解度が上がる気がする!
  • object クラスが ToString, Equals, GetHashCode, GetType をもっているから、どの型でもこれが使えるのだ。
  • int とか bool の値型では、基底型は System.ValueType だ。
  • びっくりだけど ValueType は値型じゃなくて参照型だ。しかも object から派生している。
  • ValueType は派生を基本的に許してないけれど、 struct キーワードを使えば可能らしい。

 

メソッドのアクセシビリティ、とくに virtual、 abstract、 sealed

  • protected は派生クラスからのアクセスを許可する。
  • internal は同じアセンブリ内からのアクセスを許可する。アセンブリについては12章らしい。
  • protected internal(順不同)は両方のアクセスを許可する。

ここまではいい。

  • abstract メソッドは、実装を書けなくて、派生型で中身を書くやつ。
  • virtual メソッドは、実装を書けるし、派生型でオーバーライドすることもできるやつ。
  • sealedvirtual の逆で、オーバーライドを禁じるやつ。

このみっつ、よくわからんので、軽く書いてみた。まずは abstract。これはまだわかる。

// abstract メソッドは abstract クラスにしか書けない。
abstract class AbstractBase
{
    protected abstract void Method1();

    protected void Method2()
    {
        // abstract クラスの中には abstract ではないやつも書ける。
    }
}

次に virtual。ちとややこしい。

// 基底クラス。
class Base
{
    // virtual メソッド。このままでも使える。
    public virtual void Method1()
    {
        Console.WriteLine("base");
    }
}

// 派生クラス。
class Derived : Base
{
    // override キーワードをつけると上書きできる。
    public override void Method1()
    {
        Console.WriteLine("derived");
    }
}

// 使用箇所。
// Derived のインスタンスを作ってるんだけど、型は基底クラスの Base。
Base instance = new Derived();
instance.Method1();  // derived って出る。型が Derived じゃなくて Base なのに!

virtualoverride はセットで使わないとダメ。 virtual なしで override は出来ないし、 override だけ消すと「上書きしたいんか? そんなら override せーや。それとも新規でメソッド定義したいんか? そんなら new をつけーや。」と言われる。でも何も書かなくても勝手に new としてビルドされる。仕様はわかったと思うけど、使い所がわからん。てかメソッドの上書きってこんなに面倒くさかったっけ? Python はもっとシンプルだったと思うんだけれど。

だからディスるのやめなさい。

最後に sealed はかんたん。継承禁止ってだけ。

sealed class Foo
{
}

// Cannot derive from sealed type 'Foo' って怒られる。
class Bar : Foo
{
}

sealed を使う目的はおもにふたつ。

  • 不変性を求めるもの。たとえばディクショナリのキーにオブジェクトを使うとき、値が変化したら困るやろ?
  • 拡張可能なクラスを設計するのはむつかしーからもういっそ拡張不可にするもの。

てかクラスつくるときは基本全部 sealed にすればよくない? ダメなの? 意図して抽象クラスとか作るときだけ外せばいいじゃないか。

 

小休憩

章が進んできて、なかなか「Python と比べてコイツは……」とシンプルにディスることがむつかしくなってきた。アクセシビリティなんかは Python では希薄な概念なので、ちっとおもしろい。いいぞ、こういう感覚を求めてたんだぜ。

ところで、こんな記事を見つけた。 Python プログラマの人が、 C# は学ぶ価値のある言語かどうかを検討している記事だ。

(C#は)「Windows限定でVisualStudioがないと開発できないJavaみたいな言語」という、 汎用的じゃないイメージがあって避けていたのだけど、 調べてみると.NET CoreとかLinuxでも動作するCUIアプリを開発する仕組みもあって、 興味が湧いてきた。

「プログラミング言語に期待すること」としていくつか項目を挙げ、それを C# が満たすかどうかを検討している記事だ。そのリストの中にこんなものが。

  • サクッと作って実行可能なスクリプトが書けること -> ありました。C#スクリプト(.csx)
  • リスト内包表記的なリスト処理ができること -> 期待してなかったのだけど、できるっぽい。LINQのクエリ構文

えっ、あんの?! 期待!