Visual Basic 初級講座 [改訂版]
VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

第20-2回 クラスの継承

2021/11/21

この記事が対象とする製品・バージョン

Visual Studio 2022 Visual Studio 2022 対象です。
VB2019 Visual Basic 2019 対象です。
VB2017 Visual Basic 2017 対象です。
VB2015 Visual Basic 2015 対象です。
VB2013 Visual Basic 2013 対象です。
VB2012 Visual Basic 2012 対象です。
VB2010 Visual Basic 2010 対象です。
VB2008 Visual Basic 2008 対象です。
VB2005 Visual Basic 2005 対象です。
VB.NET 2003 Visual Basic.NET 2003 対象です。
VB.NET 2002 Visual Basic.NET (2002) 対象です。
VB6対応 Visual Basic 6.0 × 対象外です。

 

この記事は 後から追加したため、第20-2回という、中途半端な名称になってしまいました。

 

目次

1.クラスの継承

クラスの継承(けいしょう)は Visual Basic の非常に強力な機能の1つです。

クラスを作成するときに1つだけ親クラスを指定することができます。新しいクラスは親クラスの機能をすべて引き継ぎます。これが継承という機能です。

たとえば、Windowsフォームアプリケーションで使用する TextBox は様々な文字を入力できる便利なコントロールです。コントロールとは視覚的表現を持つクラスの一種と考えることもできます。

.NET以前のVisual Basicには継承機能がなかったため、TextBoxにほんとちょっとだけ機能を追加したい場合でも、TextBoxの全機能を自分でプログラムして、そのうえで機能を追加する必要がありました。これは本当に大変なことです。

しかし、.NET以降の Visual Basic では継承機能を使って簡単に実現できます。親クラスを指定するにはキーワード Inherits (読み方:Inherits=インヘリツ)を使用します。たったこれだけで TextBox と同じ機能を持つ自作のクラスが作れるというわけです。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class TextBoxEx
    Inherits TextBox

End Class

後は追加したい機能だけ、記述します。後で説明しますが、単純に機能を追加するだけではなく、既存の機能を変更することもできます。

 

ここでは入力された内容をファイルに保存する Save メソッドを追加してみましょう。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class TextBoxEx
    Inherits TextBox

    Public Sub Save(fileName As String)
        IO.File.WriteAllText(fileName, Me.Text)
    End Sub

End Class

ちょっと注目してほしい部分が2つあります。1つ目は Me です。Me とは自分自身のインスタンスを表すキーワードなので、このTextBoxExクラスのプログラムの中では Me は TextBoxEx クラスのインスタンスを指しています。つまり、自分のTextプロパティの値を Fileクラスの WriteAllTextメソッドを使って保存するというプログラムです。

2つ目は Save メソッドの アクセスレベルに Public を指定している点です。Public にしないとせっかく作ったメソッドが他の多くのクラスから呼び出せなくなってしまうからです。

 

コントロールの場合、フォームに貼り付けて使いたくなりますね。この点は他の多くのクラスとは異なる特別な要請です。Visual Studioのフォームデザイナーは非常によくできており、[ビルド] メニューの[プロジェクトのリビルド]を実行すると、新しいコントロール TextBoxEx をフォームデザイナーで使えるようにすべての準備を整えてくれます。

このようにツールボックスの一番上に新しいコントロールが追加されます。

ここから他のButtonやTextBoxと同様にフォームにこのコントロールを配置できます。

そして、たとえばButtonのClickイベントなどで先ほど追加したSaveメソッドを呼び出すだけで付けるというわけです。すばらしいですね。実際に試してみる場合は存在するフォルダーを指定してください。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    TextBoxEx1.Save("C:\temp\textboxex1.txt")
End Sub

 

2.継承の基本的な考え方

2-1.基底クラスと派生クラス

既に説明したように、継承では、既に存在するクラスを親にしてして、子クラスを作ることができます。

子クラスはそれだけで親クラスと同じ機能になります。

「親クラス」・「子クラス」という言葉は通用しますが、公式ドキュメントでは、親クラスのことを「基底クラス」(基底=きてい)、子クラスのことを「派生クラス」(派生=はせい)と表現しており、継承の話をするときはこちらの言葉を使う方が多いです。この記事でもここからは基底クラス・派生クラスという言葉を使います。

 

2-2.継承できないクラス

すべてのクラスが継承可能ではなく、継承できないように定義されているクラスもたくさんあります。たとえば、Stringクラスは継承できません。

継承できないクラスから継承しようとすると、Visual Studio は赤い波線でエラーを表示するのですぐに気が付けます。

 

クラスを継承できないようにするのは簡単で、クラスを定義するときに NotInheritable (読み方:NotInheritable=ノットインヘリタブル)を付けるだけです。

次のように自作のクラスを定義すると、このクラスは継承できなくなります。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public NotInheritable Class SpecialClass

End Class

なぜ、せっかくの継承機能を使えなくするのでしょうか?

継承のことがわかってくると理解できるようになってくるのですが、継承はうまく使うととても強力なのでですが、下手に使うと、かえってプログラムを難しくしてしまいます。継承が濫用されておかしなことにならないように、継承しない方が良いクラスを設計者が継承不可能とあらかじめ定義して多くわけです。

たとえば、Stringような基本的なクラスはフレームワーク内外あらゆるところで使用されています。もし、みんながStringを継承して敬能を追加したり、ちょっと変更したクラスをStringの代わりに使いだしたら、フレームワークや既存のプログラムはうまく動作するでしょうか?注意深く使っていればうまく動作するかもしれませんが、よほど情報収集してしっかりテストしないと怖くて使えたものではありません。

また、クラスの設計者はそのクラスが継承して使われることを想定していないことが多く、継承して何かを変更するとうまくうごかない可能性があります。継承された場合もうまく動くように、設計したり、テストしたりするのはなかなか大変なものです。そこで、動作する保証ができない場合、継承できないように定義するということもあります。

発展 発展学習  -  MustInherit

発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。

NotInheritable の代わりに MustInherit を使うと必ず継承する必要があるクラスを作ることができます。MustInheritを付けるとそのクラスを直接インスタンス化して使用することはできなくなります。派生クラスは通常のクラスのように使用できます。

このようなクラスを抽象クラスと呼びます。かなり上級レベルの使い方です。

 

 

3.オーバーライド

3-1.オーバーライドの基本

オーバーライドを使うと、基底クラスのメンバーを上書きすることができます。

ランダムな数値を生成する Randomクラスを例に説明しましょう。

まず、通常のRandomクラスを使って、ランダムな数値を生成するには Next メソッドを使用します。

次のようにします。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim generator As New Random

'ランダムな数値を2つ生成します。
Dim number1 As Integer = generator.Next
Dim number2 As Integer = generator.Next

Debug.WriteLine(number1 & ", " & number2)

Debug.WriteLineで出力される場所

 

次に Random クラスを単純に継承した RandomEx クラスを作ってみましょう。

次のようにしてみます。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class RandomEx
    Inherits Random

End Class

この時点で、RandomEx クラスは Random クラスとまったく同じ機能になりますから、さきほどのプログラムの Random の部分を RandomEx に置き換えてみましょう。変わらずに機能します。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim generator As New RandomEx

'ランダムな数値を2つ生成します。
Dim number1 As Integer = generator.Next
Dim number2 As Integer = generator.Next

Debug.WriteLine(number1 & ", " & number2)

 

それでは、RandomEx クラスで、Nextメソッドの機能を上書きしてみましょう。練習として、単純に 123 を返す機能にしてしまいます。これだとランダムな数として使い物にならないので、あくまで継承機能の練習用です。

派生クラスで基底クラスのメソッドを上書きするには、Overrides (読み方:Overrides=オーバーライズ)を付けて、メソッドを宣言します。これをメソッドのオーバーライドと呼びます。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class RandomEx
    Inherits Random

    Public Overrides Function [Next]() As Integer
        Return 123
    End Function

End Class

Visual Studioにはオーバーライドのプログラムを簡単に記述できる機能があります。プログラマーは、まず、新しい行に Overrides と記述してスペースを入力します。そうすると、その場所にオーバーライドを記述できるメソッドの一覧が表示されるので選択するだけです。

これで簡単にオーバーライドのプログラムを定義できます。これで実行すると、生成する値は 必ず 123 になります。

このプログラムで メソッド名の Next が [ ] で囲まれているのは、VBの予約語のNext(For ~ Nextの構文のNext)と区別するためです。オーバーライドとは関係ありません。

 

オーバーライドは既存のメソッドの中身を上書きする機能であって、メソッドの定義自体は変更できません。たとえば、引数の数や型・戻り値の型などをオーバーライド時に変更することはできません。名前が同じで定義が異なるメソッドが必要な場合は、別のメソッドとしてオーバーロードを作成します。 → 初級講座 第19回で説明しています。

基底クラスのメソッドのオーバーロードを作成する場合、キーワード Overloads が必須になる点にご注意ください。

 

 

3-2.基底クラスのメンバーの呼び出し

これだとつまらないので、ランダムな偶数を返すように改造してみましょう。

これには、基底クラスのNextメソッドを呼び出して、結果が奇数なら -1 して返せば良いだけです。基底クラスのメンバーのアクセスするにはキーワード MyBase (読み方:MyBase=マイベース)を使用します。

次のようになります。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class RandomEx
    Inherits Random

    Public Overrides Function [Next]() As Integer
       
        Dim result As Integer = MyBase.Next

        If result Mod 2 = 0 Then
            Return result
        End If

        Return result - 1

    End Function

End Class
メモ メモ  -  +1 じゃだめなんです
Integer の最大値 2147483647 が奇数なので、最大値が生成された場合 +1 すると OverflowException になってしまいます。最小値は偶数なので、奇数の最小値を -1 することはできるというわけです。
Integer の最大値・最小値を確認するには、 Integer.MaxValue および Integer.MinValue フィールドが使用できます。

 

3-3.オーバーライドできる場合・できない場合

すべてのメソッドがオーバーライドできるわけではなく、オーバーライドできるように定義されているメソッドだけがオーバーライド可能です。Visual Basic で定義する場合は、Overrides または Overridable キーワード(読み方:Overridable=オーバーライダブル)を使って定義されているメソッドだけがオーバーライド可能です。

Overrides は、前述したように基底クラスのメソッドをオーバーライドする機能で、これを使ってオーバーライドしたメソッドはさらにその派生クラス(つまり、基底クラスから見ると孫クラス)でもオーバーライド可能です。

それ以外の場合、オーバーライド可能にするには Overridalbe キーワードを使用します。

Overrides の前に NotOverridable(読み方:NotOverridable=ノットオーバーライダブル)を付けるとオーバーライド不可能にもできます。

定義 定義例 オーバーライド可能?
Overridableだけ付ける Public Overridable Sub Xxxx() オーバーライド可能
Overridesだけ付ける Public Overrides Sub Xxxx() オーバーライド可能
何もつけない Public Sub Xxxx() オーバーライド不可能
NotOverridable + Overrides Public NotOverridable Overrides Sub Xxxx オーバーライド不可能

 

 

4.派生クラスと基底クラスの関係

4-1.基底クラスのプログラムから派生クラスのプログラムが呼び出される

ちょっと長いですが、興味深いプログラムを体験しておきましょう。

次のPredictorクラスは予言を行います。予言を取得するには Foretellメソッドを使用します。といっても、実際には、あらかじめ用意している言葉をランダムに選んで予言風につなぎ合わせるだけです。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class Predictor

    ''' <summary>予言します。</summary>
    Public Overridable Function Foretell() As String

        Dim datetime As String = ChooseRandomOne(GenerateDateTime())
        Dim nominative As String = ChooseRandomOne(GenerateNominative())
        Dim predicate As String = ChooseRandomOne(GeneratePredicate())

        Return datetime & "、" & nominative & "は" & predicate & "でしょう。"

    End Function

    ''' <summary>時を表す言葉を取得します。</summary>
    Protected Overridable Function GenerateDateTime() As String()
        Return {"明日", "来週", "月曜日", "雨の日の昼頃", "カラスが多い日", "ラーメンを食べるとき"}
    End Function

    ''' <summary>主語を表す言葉を取得します。</summary>
    Protected Overridable Function GenerateNominative() As String()
        Return {"あなた", "身近な人", "家族", "クラスメート", "男友達", "身近な女性", "小さな子", "タモリ"}
    End Function

    ''' <summary>述語を表す言葉を取得します。</summary>
    Protected Overridable Function GeneratePredicate() As String()
        Return {"良いことがある", "探し物が見つかる", "喧嘩をする", "ほめられる", "拾い物をする"}
    End Function

    Protected Indexer As New Random
    ''' <summary>言葉をランダムに選択します。</summary>
    Protected Function ChooseRandomOne(words As String()) As String
        Return words(Indexer.Next(0, words.Length))
    End Function

End Class

このクラスを呼び出すにはたとえば、次のようにします。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim teller As New Predictor
Debug.WriteLine(teller.Foretell)

Debug.WriteLineで出力される場所

これを実行すると、出力ウィンドウのデバッグに次のように表示されます。表示内容はランダムなので場合によって異なります。

 
来週、タモリは喧嘩をするでしょう。

 

 

さて、このPreditctorクラスには5つのメソッドが定義されています。Foretell, GenerateDateTime, GenerateNominative, GeneratePredicate, ChooseRandomOne の5つです。この中で Foretell だけが Public で宣言されているので外部のクラスから呼び出すことができます。

また、ChooseRandomOne以外には定義に Overridable がついているので、ChooseRandomOne以外は派生クラスでオーバーライドできます。

メソッド アクセスレベル 継承の制御
Foretell Public Overridable
GenerateDateTime Protected Overridable
GenrateNominative Protected Overridable
GeneratePrdicate Protected Overridable
ChooseRandomOne Protected (なし)
 

 

Predictorクラスを継承して予言のバリエーションを増やしてみましょう。

※Predictorクラスのプログラムを直接修正したほうが良いのですが、ここでは継承の練習のため継承を使います。他の人が作ったクラスやフレームワーク内のクラスの機能を変更する場合は、継承を使う以外の選択肢がない場合もあります。

まずは、Predictorクラスを継承したPredictorEx クラスを定義します。Inherits を使うだけです。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class PredictorEx
    Inherits Predictor

End Class

 

今回は予言に「旅に出る」という言葉が出現するように GeneratePredicateメソッドを修正しましょう。

Visual Studio で overrides に続けて半角スペースを入力するとオーバーライド可能なメソッドの一覧が表示されます。

Overridable を付けて定義したメソッドだけが一覧に出てきているのがわかります。一覧の中で Equals, GetHashCode, ToStringの3つは、Objectクラスで定義されているメソッドです。Predictorクラスのさらに親クラスで定義されているオーバーライド可能なメソッドもこのようにまとめて表示されます。

一覧から、GeneratePredicateメソッドをマウスでダブルクリックするか、キーボードの↑↓矢印などを使ってGeneratePredicateまで移動してEnterを押すと次のようにオーバーライドの定義が自動的に生成されます。   

 

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class PredictorEx
    Inherits Predictor

    Protected Overrides Function GeneratePredicate() As String()
        Return MyBase.GeneratePredicate()
    End Function

End Class

この自動生成されるプログラムでは、内容は基底クラスのGeneratePredicateを呼び出してその結果を返すものが生成されます。

つまり、この状態では基底クラスのGeneratePrediateメソッドと同じ機能です。

そこで、少しプログラムして、候補に「旅に出る」を追加しましょう。

たとえば、次のようにします。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class PredictorEx
    Inherits Predictor

    Protected Overrides Function GeneratePredicate() As String()

        Dim baseResults As String() = MyBase.GeneratePredicate()
        baseResults = baseResults.Append("旅に出る").ToArray
        Return baseResults

    End Function

End Class

この例では拡張メソッド Append (読み方:Append=アペンド)を使って配列に値を追加しています。拡張メソッドについては、初級講座の第32回で説明します。このAppendメソッドは古い環境では使用できず.NET Framework 4.7.1以上(または.NET Core/.NET 5以上)でのみ使用できます。Appendメソッドは配列に値を追加してくれますが、戻り値は配列ではなくなるので、ToArrayメソッドを使って、戻り値を配列に変換しています。

 

これで、PreditorExクラスのForetellメソッドを呼び出してみましょう。

Predictorクラスではなく、PredictorEx の方を使うように気を付けてください。結果がランダムで確認しにくいので私は1度に10個の結果を表示するようにしてみました。

VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim teller As New PredictorEx

For i As Integer = 0 To 9
    Debug.WriteLine(teller.Foretell)
Next

Debug.WriteLineで出力される場所

何回かやると次のように「旅に出る」という結果が生成されることを確認できると思います。

 
雨の日の昼頃、男友達は旅に出るでしょう。

 

何が起こっているのでしょうか?

Foretellメソッドは基底クラスで定義されており、「旅に出る」などの述語を取得するプログラムは次のようになっています。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022


Dim predicate As String = ChooseRandomOne(GeneratePredicate()))

これはGeneratePredicateメソッドが返す配列からランダムに1つ選ぶという機能です。

ここからが最大のポイントです。このGeneratePredicateメソッドは、基底クラスのものが呼び出されるのか、派生クラスのものが呼び出されるのか。答えは派生クラスのものが呼び出されます。そのため、派生クラス側では GeneratePredicateメソッドしかオーバーライドしていないのにちゃんと基底クラスから呼び出されて、基底クラスのForetellメソッドの結果が変わるというわけです。

この性質があるので、基底クラスの機能のほんの一部を修正するだけで、基底クラスの他の機能にも影響するような変更が可能になります。

 

4-2.Me とは誰か?

継承を考えない場合、キーワード Me は常に自分自身のインスタンスを表していました。

次のプログラムは GetTypeメソッドから取得できるTypeオブジェクトのNameプロパティを使って自分自身の名前を返します。

つまり、ここではGetMyNameメソッドの戻り値は「InheritsTest」になります。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class InheritsTest

    Public Function GetMyName() As String
        Return Me.GetType.Name
    End Function

End Class

Me.GetType のように Me. を使っているから、自分自身のクラス名を取得できるわけです。

Me. は省略可能なので、省略しても同じです。

さて、これを次のようにシンプルに継承すると、GetMyNameメソッドは何を返すでしょうか?

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class InheritsTestEx
    Inherits InheritsTest

End Class

実験してみましょう。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim test1 As New InheritsTest
Debug.WriteLine(test1.GetMyName) '← InheritsTest を出力します。

Dim test2 As New InheritsTestEx
Debug.WriteLine(test2.GetMyName) '← InheritsTestEx を出力します。

Debug.WriteLineで出力される場所

この結果を見るとわかるように、 キーワード Me とは、そのプログラムが記述されているクラスを表しているわけではなく、そのプログラムを実行しているインスタンスを表しています。

そのため、基底クラスで Me.xxxxx と記述した場合、その xxxxx が派生クラスでオーバーライドされると、それは派生クラスでプログラムした機能を呼び出すということになります。

そして、 Me. は常に省略可能なので、単に xxxxx というメソッドの呼び出しを記述した場合でも、同じです。

メモ メモ  -  MyClass

そのプログラムが記述されているクラスのインスタンスを表す MyClass (読み方:MyClass=マイクラス)というキーワードもありますが、このキーワードは癖があって扱いにくいので使用しないことを強くお勧めします。

すべての呼び出しがMe.になっていれば1つのルールに従っていればよいだけなのに、MyClassが混在するとルールが2つになって複雑になります。それに、このMyClassに相当する機能は、C#やPythonなどの他のオブジェクト指向言語には存在しない機能なので、複数の言語を使用するときに混乱を招きます。

ついでに、このキーワードがあるせいで、サンプルプログラムで MyClass という名前のクラスが記述できないのがまたちょっとやりにくいです…。

 

4-3.Shadows

オーバーライドを使用しないで、同じ名前・シグネチャのメソッドやプロパティを定義すると、それはオーバーライドではないので、基底クラスからは認識されません。

このような同じ定義でオーバーライドしないことを「シャドウする」と表現します。シャドウイングと呼ぶ場合もあります。

プログラムが複雑になるのでシャドウイングは極力使用すべきではありません。私の20年の.NETの人生の中でシャドウイングが必要になったことは1回もなかったように思います。

シャドウイングを使用するとVisual Studioはその個所に緑の波線を表示して、次のようなメッセージで警告します。

BC40005 function 'GeneratePredicate' は、ベース class 'Predictor' のオーバーライド可能なメソッドをシャドウします。ベース メソッドをオーバーライドするには、このメソッドは 'Overrides' に宣言されていなければなりません。

 

それでもシャドウイングをしたい場合、Overrides の代わりに明示的に Shadows キーワード(読み方:Shadows=シャドウズ)を使用するという手があります。Shadowsキーワードを使うと、そのプログラムは何かの間違いではなく、プログラマーが明確にシャドウイングを使用するという意思がはっきりしますからVisual Studioは警告を表示しなくなります。

でも、繰り返し言っておきますが、シャドウイングが必要になることはほとんどないので、この警告が表示される場合は、安易に Shadows を付けて解決しないで、なぜ Overrides ではダメなのか考えてください。Overrides でダメな理由がないなら Overrides を付けます。ダメな理由があるなら、その理由の方をなんとかして、Shadowsを使わずに解決しましょう。

 

5.コンストラクター

派生クラスのコンストラクターが呼び出されるとき、基底クラスのコンストラクターも呼び出す必要があります。多くの場合、これは自動的に行われており意識しなくて済みますが、基底クラスのコンストラクターに何か引数が必須の場合、自動的には呼び出せないので、自分で呼び出しを記述するなどいくつか注意することがあります。

順を追って説明しましょう。

まず、クラスを定義するときにコンストラクターの定義を省略すると、引数なしの空のコンストラクターが自動的に生成されるというルールを覚えているでしょうか?

たとえば、次のクラスは一見なにも定義されていないクラスですが、見えない空のコンストラクターが自動的に生成されます。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class Empty

End Class

あえて、コンストラクターを明示的に書くと、次のプログラムのようになります。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class Empty

    Public Sub New()

    End Sub

End Class

この空のコンストラクターがあるおかげでクラスのインスタンスが作成できるわけです。

 

継承で、派生クラスのコンストラクターが呼び出される場合、基底クラスのコンストラクターも呼び出されます。

次のように継承関係にあるClassA、ClassBを例に考えてみましょう。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class ClassA
    Public Sub New()
        Debug.WriteLine("ClassA")
    End Sub
End Class

Public Class ClassB
    Inherits ClassA

    Public Sub New()
        Debug.WriteLine("ClassB")
    End Sub
End Class

この例で、次のようにClassBのコンストラクターを呼び出してみます

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022


Dim b As New ClassB()

出力ウィンドウのデバッグには次のように出力されます。

ClassA
ClassB

プログラムからはClassBのコンストラクターしか呼び出していないのに、ClassA のコンストラクターも呼び出されています。自動的に基底クラスのコンストラクターが呼び出されているということがわかります。

基底クラスであるClassAのコンストラクターの方が先に呼び出されていることにも注意してください。

 

それでは、次のようにClassAのコンストラクターを改造して引数を必須にしてみましょう。自動的に呼び出せなくなります。どうなるでしょうか。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class ClassA
    Public Sub New(value As String) '←引数追加
        Debug.WriteLine("ClassA with " & value)
    End Sub
End Class

Public Class ClassB
    Inherits ClassA

    Public Sub New()
        'この例はこのNewでビルドエラーが発生します。
        Debug.WriteLine("ClassB")
    End Sub
End Class

この変更を行うと、ClassB の New のところにエラーを示す赤い波線が表示され、ビルドエラーになります。

エラーメッセージは次の通りです。

エラー BC30148 'ClassB' の基底クラス 'ClassA' には、引数なしで呼び出される、アクセス可能な 'Sub New' がないため、この 'Sub New' の最初のステートメントは、'MyBase.New' または 'MyClass.New' に対して呼び出さなければなりません。

 

つまり、自動的に呼び出せるコンストラクターがないので、明示的に基底クラスのコンストラクターを呼び出せということです。

派生クラスから基底クラスのコンストラクターを呼び出すには MyBase.New を使用します。たとえば、次のようにすると解決できます。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class ClassA
    Public Sub New(value As String) 
        Debug.WriteLine("ClassA with " & value)
    End Sub
End Class

Public Class ClassB
    Inherits ClassA

    Public Sub New()
        MyBase.New("Hello")
        Debug.WriteLine("ClassB")
    End Sub
End Class

この明示的な基底クラスのコンストラクターの呼び出しは、派生クラスのコンストラクター内で最初の行に書く必要があります。これはかなり特殊なルールです。基底クラスのコンストラクターが先に呼び出されるべきであることからこのようなルールになっています。

基底クラスのコンストラクター呼び出しを最初の行に書かなかった場合は次のエラーが発生します。

エラー BC30282 コンストラクターの呼び出しは、インスタンス コンストラクター内の最初のステートメントとしてのみ有効です。

 

派生クラスに明示的なコンストラクターを記述しない場合でも事情は同じなので、同じエラーが発生します。これは初心者によくあるケースで、自分が書いていない部分でエラーになるのでどうすればよいかわかりにくいもののように思います。

 

たとえば、URLなどを表す Uri クラスを単純に継承する次の例では、上述したアクセス可能な Sub New がないというエラーが発生します。これで慣れていない人は困惑してしまうというわけです。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class MyUri
    Inherits Uri

End Class

ここでの説明を理解していれば解決方法は、明示的にコンストラクターを用意して、その先頭でMyBase.New を使って基底クラスのコンストラクターを呼び出すことということがわかると思います。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Public Class MyUri
    Inherits Uri

    Public Sub New(uriString As String)
        MyBase.New(uriString)
    End Sub

End Class

 

6.型

クラスには型としての側面もあります。

型について簡単に復習しておくと、つまり、 As xxxx と指定するときに xxxx に記述できるものは型でもあります。

変数は型が一致するものしか代入できませんが、「一致する」の意味は少し難しく、厳密に同じ名前の型でなければいけないという意味ではありません。詳しくは初級講座 第28回 で説明します。

ここで知っておいてほしいのは、基底クラスの型に派生クラスのインスタンスを代入することはできるが、その逆はできないということです。

さきほどの InheritsTest クラスと、その派生クラスの InheritsTestEx クラスを例に挙げると次のプログラムはOKです。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim test1 As New InheritsTest
Dim test2 As New InheritsTestEx

test1 = test2

InheritsTest型の変数 test1 からは、InheritsTest で定義されているメソッドやプロパティを呼び出せます。

そして、InheritsTestExクラスはInheritsTestクラスを継承しているため、InheritsTestで定義されているメソッドやプロパティをやはり呼び出すことができるわけです。そういうわけでこの代入は許可されます。

ただし、これをやると、変数 test1 は実体は InheritsTestEx でありながら、型が InheritsTest になるため、Visual Studio でのインテリセンスのヒントはすべて InheritsTest のものになりますし、InheritsTestEx で独自に追加したメソッドやプロパティを直接呼び出すことはできません。(CTypeやDirectCastを使って型変換して呼び出すことはできます。)

 

次のプログラムは NG です。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim test1 As New InheritsTest
Dim test2 As New InheritsTestEx

test2 = test1 '←ここでエラー。

これの考え方としては、InheritsTestExでメソッドやプロパティが追加されている可能性があり、変数 test2 からそれらを呼び出すことができます。ところが、実体が InheritsTest の場合、追加されたメソッドやプロパティが存在しないので呼び出すことができません。そのためこの代入は許可されません。

 

このようなルールなので、すべての型の基底クラスである Object 型の変数にはなんでも代入できるということです。

 

このような代入が役に立つのは変数よりも引数です。たとえば、.NET のフレームワークの FileInfo クラスと DirectoryInfoクラスはどちらもFileSystemInfoクラスを継承しているので、FileSystemInfo型に代入できます。

そこで、これらに共通の機能はFileSystemInfo型で定義した処理を書くことで共通化できます。

次の関数はその例で、引数を FileSystemInfo型で定義し、そのCreattionTimeプロパティを使って作成日付を調べて今日作成されたものかどうかを返します。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

''' <summary>今日作成されたものかどうか確認します。</summary>
Private Function IsNew(item As IO.FileSystemInfo) As Boolean

    Return item.CreationTime.Date = Now.Date

End Function

この関数は次のようにFileInfo型の変数を引数にして呼び出すこともできますし、DirectoryInfo型の変数を引数にして呼び出すこともできます。

VB.NET 2002 VB.NET 2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019 Visual Studio 2022

Dim file As New IO.FileInfo("C:\temp\test.txt")
Debug.WriteLine(IsNew(file))

Dim folder As New IO.DirectoryInfo("C:\temp\samplefolder")
Debug.WriteLine(IsNew(folder))

 

7.継承の活用

今回は、継承の主だった機能を中心に説明しました。

これらの機能をプログラミングにどう役立てていくかにはあまり触れませんでした。

継承はオブジェクト指向プログラミングの中心的な存在で、使い方は奥が深く、さまざまな活用方法があります。それを理解するにはある程度の経験や知識が必要なので、当初私は継承機能そのものを初級講座で扱うつもりがありませんでした。しかし、Visual Basicやフレームワークの機能の中には継承が前提となっているものがいろいろあり、単純な使い方レベルでも説明しておいた方が良いと考えが変わり、この回を後から追加した次第です。

現段階で1つだけ注意してほしいのは、「継承を無計画にやたらと使わない」ということです。

初心者がやりがちな間違いは、いろいろなクラスで共通で使用する機能をプログラムするために継承を使用することです。つまり、共通機能を持った基底クラスをまず作成し、個別の機能をそれぞれの派生クラスで実装していくというやり方です。これはプログラムを複雑にするだけです。共通の機能が必要であれば、単にその共通機能を持ったクラスを作成すればよく、継承を使って親子関係にする必要はありません。

 

改訂履歴

2021/9/20

2021/11/21