概要
前回(『プログラミング C#』 その5 コレクション)のつづき。流し読みノートを書くぞ。この章には継承に関するクラスのアレコレが書かれていた。インタフェースとか、キャストとか、アクセシビリティとか。
継承は Python 本でもかならず章が割かれているテーマだ。当時のノートも振り返っておこう。
- (2016-02-03)Mark Lutz『初めてのPython』つづきのつづき - クラス、オブジェクト指向の章
- (2018-02-19)Brett Slatkin『Effective Python』その3 クラスと継承
流し読みノート
基底と派生の基本的なところ
- 基底クラス、派生クラスあたりの用語は 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
とかそうかも?
- こんなふうに、関連のない機能をキーワードに複数もたせるのは C の伝統なんだって。ややこしいな……と思ったけれど、 Python にもあるかな?
- 反変性には
in
キーワードを使う。 - この変換は C# バージョン4より前では動かないかもしれない。 .NET2.0 でジェネリックが導入されたのに、 C# はバージョン4までそれにちゃんと対応していなかったから。裏話ゲットだ。
全クラスの基底クラス object
- クラス書くとき、基底クラスなしにすることはできるけど、そのときは自動で
System.Object
が基底クラスになっている。- C# では
object
だ。 - Python の
object
と同じだね。 - こういう「自動で補完されているもの」を知ると一気にコードの理解度が上がる気がする!
- C# では
object
クラスがToString
,Equals
,GetHashCode
,GetType
をもっているから、どの型でもこれが使えるのだ。int
とかbool
の値型では、基底型はSystem.ValueType
だ。- びっくりだけど
ValueType
は値型じゃなくて参照型だ。しかもobject
から派生している。 ValueType
は派生を基本的に許してないけれど、struct
キーワードを使えば可能らしい。
メソッドのアクセシビリティ、とくに virtual、 abstract、 sealed
protected
は派生クラスからのアクセスを許可する。internal
は同じアセンブリ内からのアクセスを許可する。アセンブリについては12章らしい。protected internal
(順不同)は両方のアクセスを許可する。
ここまではいい。
abstract
メソッドは、実装を書けなくて、派生型で中身を書くやつ。virtual
メソッドは、実装を書けるし、派生型でオーバーライドすることもできるやつ。sealed
はvirtual
の逆で、オーバーライドを禁じるやつ。
このみっつ、よくわからんので、軽く書いてみた。まずは 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 なのに!
virtual
と override
はセットで使わないとダメ。 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のクエリ構文
えっ、あんの?! 期待!