継承 -極める Part.1-

ホームC#プログラミング応用講義 > 極める Part.1

目次

protected

今回と次回の2回で、継承という分野を極めたいと思います。
なまじっか理解するのではなく、極めるのですから難しいところも非常に多いでしょう。

しかし、やはり継承は極めておいたほうがいいでしょう。なぜなら、オブジェクト指向の根本的な概念がそこに眠っているからです。
実際、オブジェクト指向の3本柱というのは「カプセル化」「ポリモーフィズム(多様性)」「継承」で、その中心となるのが「継承」だからです。継承はいかにも多様性を実現するものです。

前置きはこのくらいにして、下のソースを見てください。
using System;

class BaseClass
{
    protected string dorama = "夏クール「東京湾景」に注目";
}

class Dorama : BaseClass
{
    new string dorama = "仲間由紀恵主演!!";
    
    public static void Main(string[] args)
    {
        Dorama d = new Dorama();
        d.Show();
    }
    
    private void Show()
    {
        Console.WriteLine(dorama);
        Console.WriteLine(base.dorama);
    }
}
前回のソースから唯一変わったのは「protected」だけです。前は「public」だったはず...。

このソースはコンパイルし、実行することができます。
アクセス修飾子が「public」ではないのに実行できるということは「protected」アクセス修飾子がアクセスを許可しているということが考えられます。

と、ここまで推理していくと大体わかるかもしれません。「protected」アクセス修飾子は、派生クラスからのアクセスのみ許可するものです。
ここでは BaseClass クラスの派生クラス Dorama からアクセスしているので、アクセスできるわけです。

これまで public と private しか扱ってきませんでしたが、この protected はまあまあ利用頻度も高いのでよく理解しておくべきです。
他にもアクセス修飾子はありますが、この3つで大半は事足ります。

仮想メソッド

protected は極めるというほど難しくはありませんが、余っていたので説明しました。しかし今後はかなり応用的な内容です。

メソッドというのは「動作をまとめた関数」と簡単に説明できます。
メソッドといえば「オーバーロード」を思い出していただけるでしょうか。思い出せなければ復習しましょう。

その中で同名のメソッドを区別するのに「シグニチャ」を判別要素としました。
このオーバーロードは継承とは関係がありません。オーバーロードは同一クラス内での話です。

しかし、継承を利用するとシグニチャが同じであるにも関わらず、同名のメソッドを定義することができます。(メソッド名もシグニチャに含まれます)
とはいっても、シグニチャが同じなので、やはりある程度の手続きは必要でしょう。でなければ判別の仕様がありません。

ここで重要なのは、継承を利用することで、親子関係が生まれていることです。人間の親子は、子は親の所有物ではない、とされていますが、コンピュータでは親に当たるものと子に当たるものには大きな違いがあります。

まずは、あるメソッドを親で、すなわち基本クラスで定義するとします。これと同じシグニチャのメソッドを子で、すなわち派生クラスでも定義したいのです。
このような状態で、名前の衝突を起こさないようにする、それが「仮想メソッド」の役割です。

基本クラスで定義する際は「これはバーチャルなメソッドだよ」と書いておき、派生クラスで「バーチャルなメソッドを再定義するよ」と書きます。
基本クラスのバーチャルなメソッドにアクセスしたからといって、夢のように消えてなくなるわけではなく、「再定義されてないからそのままいくよ」と、メソッドを動作させることができます。

バーチャルなメソッドを定義する際に「virtual」と、再定義する際に「override」と修飾します。
using System;

class Base_Drama
{
    public virtual void Show()
    {
        Console.WriteLine("ドラマベース");
    }
}

class TRICK : Base_Drama
{
    public override void Show()
    {
        Console.WriteLine("ドラマといえば、トリック");
    }
}

class Drama : TRICK
{
    public static void Main(string[] args)
    {
        Base_Drama b = new Base_Drama();
        b.Show(); // ドラマベース
        
        b = new TRICK();
        b.Show(); // ドラマといえば、トリック
        
        b = new Drama();
        b.Show(); // ドラマといえば、トリック
        
        
        TRICK t = new TRICK();
        t.Show(); // ドラマといえば、トリック
        
        t = new Drama();
        t.Show(); // ドラマといえば、トリック
        
        
        Drama d = new Drama();
        d.Show(); // ドラマといえば、トリック
    }
}
えー、おそらくなんのこっちゃといったところでしょう。詳しく解説していきます。

class Base_Drama
{
    public virtual void Show()
    {
        Console.WriteLine("ドラマベース");
    }
}
この部分が基本クラスとなり、その中のメソッドは「virtual」が指定されているため、仮想クラスです。
これが付いている段階で、他の派生クラスで同じ Show() が再定義されるであろうことを予想しなければなりません。

class TRICK : Base_Drama
{
    public override void Show()
    {
        Console.WriteLine("ドラマといえば、トリック");
    }
}
予想通り、再定義されています。Base_Drama の派生クラスである「TRICK」クラスにシグニチャが同じの「Show()メソッド」を定義できるのは
・基本クラスは 「仮想メソッド」
・派生クラスは 「仮想メソッドを再定義したメソッド」
だからです。普通のメソッドではなく、仮想メソッドだからできるのです。

さて、Main() メソッドが含まれるのは Drama クラスですが、これは仮想クラスを再定義した TRICK クラスの派生クラスです。
派生クラスの原点に戻ると、「派生クラスは基本クラスの内容をがっぽり飲み込んでいる」ということがここで重要となります。

Drama は TRICK をがっぽり飲み込んだものの、Drama 内では Show() メソッドを再定義していないため、Drama の中の Show() というのは TRICK の中の Show() と一緒です。
Drama.Show() とアクセスすれば TRICK.Show() とアクセスすることと同じことです。(実際にはこの形式ではアクセスできません。)

これからは Main() メソッドの中を追っていきます。

Base_Drama 型の変数 b に Base_Drama クラスのオブジェクトを代入します(もちろん参照)。
ここで「b.Show();」とアクセスすれば仮想メソッドながらも Base_Drama の Show() が呼び出されます。

さて次は、Base_Drama 型の変数に TRICK クラスのオブジェクトを代入していますが、型とクラス名が違うのは初めてだと思います。
この場合、基本クラスの型に、派生クラスのオブジェクトを代入することは可能なため、エラーなく代入できます。

ただし、ここでは暗黙的なキャスト、型変換が行われています。TRICK のオブジェクトを Base_Drama の型に入れるには変換が必要です。
ここでは「from "int" to "double"」のように Base_Drama のほうが幅が広いため、当然ながら何も削られません。

これ以降のオブジェクトはすべて「TRICK」か「Drama」です。
しかし、実質的にはさっき述べたように「TRICK」だけになるため TRICK だろうが Drama だろうが、この2つから Show() を呼び出すと、TRICK に定義された、再定義されたメソッドを呼び出すことになります。

表示結果も全部「ドラマといえば、トリック」になっています。

仮想メソッドと再定義メソッドを利用することで、すべてオブジェクトの中のメソッドが呼び出されます。
Show() メソッドは2つあり、それらは別物であるにもかかわらず、幅広い対応ができます。

このように仮想メソッドを定義し、それを派生クラスで再定義することを「オーバーライド」といいます。
これは「オーバーロード」と混乱しがちですから、キーワードの「override」で覚えておくとよいでしょう。オーバーロードではキーワードは使いませんから。

メソッドの隠蔽

using System;

class Base_Drama
{
    public void Show()
    {
        Console.WriteLine("ドラマベース");
    }
}

class TRICK : Base_Drama
{
    new public void Show()
    {
        Console.WriteLine("ドラマといえば、トリック");
    }
}

class Drama : TRICK
{
    public static void Main(string[] args)
    {
        Base_Drama b = new Base_Drama();
        b.Show(); // ドラマベース
        
        b = new TRICK();
        b.Show(); // ドラマベース
        
        b = new Drama();
        b.Show(); // ドラマベース
        
        
        TRICK t = new TRICK();
        t.Show(); // ドラマといえば、トリック
        
        t = new Drama();
        t.Show(); // ドラマといえば、トリック
        
        
        Drama d = new Drama();
        d.Show(); // ドラマといえば、トリック
    }
}
この項目で扱うのは復習です。何の復習かというと、「new」の復習です。

先ほどのソースと違うのは「仮想メソッド」を使わずに「new」によってメソッドを隠蔽しているという点です。
基本クラスとなる「Base_Drama」クラスに Show() が定義されていますが、これは普通のメソッドであり、なんら特別な点はありません。

ところが、派生クラスである「TRICK」クラスの Show() は「new」キーワードがつけられています。
これにより、TRICK.Show() は Base_Show() を隠蔽し、全く新しいメソッドを定義していることとなります。

それ以外は同じですが、表示結果は大きく違います。Base_Drama 型の変数に Base_Drama のオブジェクトを入れると「ドラマベース」が表示されるのは当然です。

しかし、次の2つは前回と表示結果が違います。これはまさしく隠蔽の結果です。
とはいっても、なぜ「ドラマベース」が表示されるのか、難しいでしょう。これを理解するのに苦労します。

まずポイントは「new のキャストは切られる」ということです。ちょっと視点を変えてみましょう。
変数 b は Base_Drama 型です。よって、そこに TRICK 型のオブジェクトや、Drama 型のオブジェクトを代入する際はキャストが行われます。もちろん、暗黙的に。

ここで、new が付いているメンバは切られると考えてみてはどうでしょう。カットされてしまうのです。Base_Drama 型とは合わない、といって消えてしまうのです。
消えてしまえば残るのは Base_Drama.Show() だけ。よって「ベースドラマ」が表示されます。

それ以降は TRICK 型か Drama 型ですから、切られることはありません。いや、今度切られる(隠蔽される)のは「Base_Drama」のほうです。
new キーワードによって全く新しい Show() が定義されますから、Base_Drama の Show() はゴミ箱行きです。

この項目は new の復習でしたが、型とオブジェクトが違うとキャストが発生するという点を見ると、やはり応用的といえます。
次回はさらに拡張していきますから、仮想クラスと隠蔽がわからなければ何もわかりません。このページをよく読んで、理解しておきましょう。

[ ステップアップC# ] [ C#プログラミング応用講義 ]