| Visual Basic 初級講座 [改訂版] |
|
|
Visual Basic 中学校 > 初級講座[改訂版] >
2021/11/21
この記事が対象とする製品・バージョン
|
|
Visual Studio 2022 | ◎ | 対象です。 |
|
|
Visual Basic 2019 | ◎ | 対象です。 |
|
|
Visual Basic 2017 | ◎ | 対象です。 |
|
|
Visual Basic 2015 | ◎ | 対象です。 |
|
|
Visual Basic 2013 | ◎ | 対象です。 |
|
|
Visual Basic 2012 | ◎ | 対象です。 |
|
|
Visual Basic 2010 | ◎ | 対象です。 |
|
|
Visual Basic 2008 | ◎ | 対象です。 |
|
|
Visual Basic 2005 | ◎ | 対象です。 |
|
|
Visual Basic.NET 2003 | ◎ | 対象です。 |
|
|
Visual Basic.NET (2002) | ◎ | 対象です。 |
|
|
Visual Basic 6.0 | × | 対象外です。 |
この記事は 後から追加したため、第20-2回という、中途半端な名称になってしまいました。
目次
クラスの継承(けいしょう)は Visual Basic の非常に強力な機能の1つです。
クラスを作成するときに1つだけ親クラスを指定することができます。新しいクラスは親クラスの機能をすべて引き継ぎます。これが継承という機能です。
たとえば、Windowsフォームアプリケーションで使用する TextBox は様々な文字を入力できる便利なコントロールです。コントロールとは視覚的表現を持つクラスの一種と考えることもできます。
.NET以前のVisual Basicには継承機能がなかったため、TextBoxにほんとちょっとだけ機能を追加したい場合でも、TextBoxの全機能を自分でプログラムして、そのうえで機能を追加する必要がありました。これは本当に大変なことです。
しかし、.NET以降の Visual Basic では継承機能を使って簡単に実現できます。親クラスを指定するにはキーワード Inherits (読み方:Inherits=インヘリツ)を使用します。たったこれだけで TextBox と同じ機能を持つ自作のクラスが作れるというわけです。
![]()
Public Class TextBoxEx
Inherits TextBox
End Class
後は追加したい機能だけ、記述します。後で説明しますが、単純に機能を追加するだけではなく、既存の機能を変更することもできます。
ここでは入力された内容をファイルに保存する Save メソッドを追加してみましょう。
![]()
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メソッドを呼び出すだけで付けるというわけです。すばらしいですね。実際に試してみる場合は存在するフォルダーを指定してください。
![]()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TextBoxEx1.Save("C:\temp\textboxex1.txt")
End Sub
既に説明したように、継承では、既に存在するクラスを親にしてして、子クラスを作ることができます。
子クラスはそれだけで親クラスと同じ機能になります。
「親クラス」・「子クラス」という言葉は通用しますが、公式ドキュメントでは、親クラスのことを「基底クラス」(基底=きてい)、子クラスのことを「派生クラス」(派生=はせい)と表現しており、継承の話をするときはこちらの言葉を使う方が多いです。この記事でもここからは基底クラス・派生クラスという言葉を使います。
すべてのクラスが継承可能ではなく、継承できないように定義されているクラスもたくさんあります。たとえば、Stringクラスは継承できません。
継承できないクラスから継承しようとすると、Visual Studio は赤い波線でエラーを表示するのですぐに気が付けます。

クラスを継承できないようにするのは簡単で、クラスを定義するときに NotInheritable (読み方:NotInheritable=ノットインヘリタブル)を付けるだけです。
次のように自作のクラスを定義すると、このクラスは継承できなくなります。
![]()
Public NotInheritable Class SpecialClass
End Class
なぜ、せっかくの継承機能を使えなくするのでしょうか?
継承のことがわかってくると理解できるようになってくるのですが、継承はうまく使うととても強力なのでですが、下手に使うと、かえってプログラムを難しくしてしまいます。継承が濫用されておかしなことにならないように、継承しない方が良いクラスを設計者が継承不可能とあらかじめ定義して多くわけです。
たとえば、Stringような基本的なクラスはフレームワーク内外あらゆるところで使用されています。もし、みんながStringを継承して敬能を追加したり、ちょっと変更したクラスをStringの代わりに使いだしたら、フレームワークや既存のプログラムはうまく動作するでしょうか?注意深く使っていればうまく動作するかもしれませんが、よほど情報収集してしっかりテストしないと怖くて使えたものではありません。
また、クラスの設計者はそのクラスが継承して使われることを想定していないことが多く、継承して何かを変更するとうまくうごかない可能性があります。継承された場合もうまく動くように、設計したり、テストしたりするのはなかなか大変なものです。そこで、動作する保証ができない場合、継承できないように定義するということもあります。
発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 NotInheritable の代わりに MustInherit を使うと必ず継承する必要があるクラスを作ることができます。MustInheritを付けるとそのクラスを直接インスタンス化して使用することはできなくなります。派生クラスは通常のクラスのように使用できます。 このようなクラスを抽象クラスと呼びます。かなり上級レベルの使い方です。 |
オーバーライドを使うと、基底クラスのメンバーを上書きすることができます。
ランダムな数値を生成する Randomクラスを例に説明しましょう。
まず、通常のRandomクラスを使って、ランダムな数値を生成するには Next メソッドを使用します。
次のようにします。
![]()
Dim generator As New Random
'ランダムな数値を2つ生成します。
Dim number1 As Integer = generator.Next
Dim number2 As Integer = generator.Next
Debug.WriteLine(number1 & ", " & number2)
次に Random クラスを単純に継承した RandomEx クラスを作ってみましょう。
次のようにしてみます。
![]()
Public Class RandomEx
Inherits Random
End Class
この時点で、RandomEx クラスは Random クラスとまったく同じ機能になりますから、さきほどのプログラムの Random の部分を RandomEx に置き換えてみましょう。変わらずに機能します。
![]()
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=オーバーライズ)を付けて、メソッドを宣言します。これをメソッドのオーバーライドと呼びます。
![]()
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 が必須になる点にご注意ください。
これだとつまらないので、ランダムな偶数を返すように改造してみましょう。
これには、基底クラスのNextメソッドを呼び出して、結果が奇数なら -1 して返せば良いだけです。基底クラスのメンバーのアクセスするにはキーワード MyBase (読み方:MyBase=マイベース)を使用します。
次のようになります。
![]()
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
Integer の最大値 2147483647 が奇数なので、最大値が生成された場合 +1 すると OverflowException
になってしまいます。最小値は偶数なので、奇数の最小値を -1 することはできるというわけです。
Integer の最大値・最小値を確認するには、 Integer.MaxValue および Integer.MinValue フィールドが使用できます。 |
すべてのメソッドがオーバーライドできるわけではなく、オーバーライドできるように定義されているメソッドだけがオーバーライド可能です。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 | オーバーライド不可能 |
ちょっと長いですが、興味深いプログラムを体験しておきましょう。
次のPredictorクラスは予言を行います。予言を取得するには Foretellメソッドを使用します。といっても、実際には、あらかじめ用意している言葉をランダムに選んで予言風につなぎ合わせるだけです。
![]()
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
このクラスを呼び出すにはたとえば、次のようにします。
![]()
Dim teller As New Predictor
Debug.WriteLine(teller.Foretell)
これを実行すると、出力ウィンドウのデバッグに次のように表示されます。表示内容はランダムなので場合によって異なります。
来週、タモリは喧嘩をするでしょう。
さて、この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 を使うだけです。
![]()
Public Class PredictorEx
Inherits Predictor
End Class
今回は予言に「旅に出る」という言葉が出現するように GeneratePredicateメソッドを修正しましょう。
Visual Studio で overrides に続けて半角スペースを入力するとオーバーライド可能なメソッドの一覧が表示されます。

Overridable を付けて定義したメソッドだけが一覧に出てきているのがわかります。一覧の中で Equals, GetHashCode, ToStringの3つは、Objectクラスで定義されているメソッドです。Predictorクラスのさらに親クラスで定義されているオーバーライド可能なメソッドもこのようにまとめて表示されます。
一覧から、GeneratePredicateメソッドをマウスでダブルクリックするか、キーボードの↑↓矢印などを使ってGeneratePredicateまで移動してEnterを押すと次のようにオーバーライドの定義が自動的に生成されます。
![]()
Public Class PredictorEx
Inherits Predictor
Protected Overrides Function GeneratePredicate() As String()
Return MyBase.GeneratePredicate()
End Function
End Class
この自動生成されるプログラムでは、内容は基底クラスのGeneratePredicateを呼び出してその結果を返すものが生成されます。
つまり、この状態では基底クラスのGeneratePrediateメソッドと同じ機能です。
そこで、少しプログラムして、候補に「旅に出る」を追加しましょう。
たとえば、次のようにします。
![]()
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個の結果を表示するようにしてみました。
![]()
Dim teller As New PredictorEx
For i As Integer = 0 To 9
Debug.WriteLine(teller.Foretell)
Next
何回かやると次のように「旅に出る」という結果が生成されることを確認できると思います。
雨の日の昼頃、男友達は旅に出るでしょう。
何が起こっているのでしょうか?
Foretellメソッドは基底クラスで定義されており、「旅に出る」などの述語を取得するプログラムは次のようになっています。
![]()
Dim predicate As String = ChooseRandomOne(GeneratePredicate()))
これはGeneratePredicateメソッドが返す配列からランダムに1つ選ぶという機能です。
ここからが最大のポイントです。このGeneratePredicateメソッドは、基底クラスのものが呼び出されるのか、派生クラスのものが呼び出されるのか。答えは派生クラスのものが呼び出されます。そのため、派生クラス側では GeneratePredicateメソッドしかオーバーライドしていないのにちゃんと基底クラスから呼び出されて、基底クラスのForetellメソッドの結果が変わるというわけです。
この性質があるので、基底クラスの機能のほんの一部を修正するだけで、基底クラスの他の機能にも影響するような変更が可能になります。
継承を考えない場合、キーワード Me は常に自分自身のインスタンスを表していました。
次のプログラムは GetTypeメソッドから取得できるTypeオブジェクトのNameプロパティを使って自分自身の名前を返します。
つまり、ここではGetMyNameメソッドの戻り値は「InheritsTest」になります。
![]()
Public Class InheritsTest
Public Function GetMyName() As String
Return Me.GetType.Name
End Function
End Class
Me.GetType のように Me. を使っているから、自分自身のクラス名を取得できるわけです。
Me. は省略可能なので、省略しても同じです。
さて、これを次のようにシンプルに継承すると、GetMyNameメソッドは何を返すでしょうか?
![]()
Public Class InheritsTestEx
Inherits InheritsTest
End Class
実験してみましょう。
![]()
Dim test1 As New InheritsTest
Debug.WriteLine(test1.GetMyName) '← InheritsTest を出力します。
Dim test2 As New InheritsTestEx
Debug.WriteLine(test2.GetMyName) '← InheritsTestEx を出力します。
この結果を見るとわかるように、 キーワード Me とは、そのプログラムが記述されているクラスを表しているわけではなく、そのプログラムを実行しているインスタンスを表しています。
そのため、基底クラスで Me.xxxxx と記述した場合、その xxxxx が派生クラスでオーバーライドされると、それは派生クラスでプログラムした機能を呼び出すということになります。
そして、 Me. は常に省略可能なので、単に xxxxx というメソッドの呼び出しを記述した場合でも、同じです。
そのプログラムが記述されているクラスのインスタンスを表す MyClass (読み方:MyClass=マイクラス)というキーワードもありますが、このキーワードは癖があって扱いにくいので使用しないことを強くお勧めします。 すべての呼び出しがMe.になっていれば1つのルールに従っていればよいだけなのに、MyClassが混在するとルールが2つになって複雑になります。それに、このMyClassに相当する機能は、C#やPythonなどの他のオブジェクト指向言語には存在しない機能なので、複数の言語を使用するときに混乱を招きます。 ついでに、このキーワードがあるせいで、サンプルプログラムで MyClass という名前のクラスが記述できないのがまたちょっとやりにくいです…。 |
オーバーライドを使用しないで、同じ名前・シグネチャのメソッドやプロパティを定義すると、それはオーバーライドではないので、基底クラスからは認識されません。
このような同じ定義でオーバーライドしないことを「シャドウする」と表現します。シャドウイングと呼ぶ場合もあります。
プログラムが複雑になるのでシャドウイングは極力使用すべきではありません。私の20年の.NETの人生の中でシャドウイングが必要になったことは1回もなかったように思います。
シャドウイングを使用するとVisual Studioはその個所に緑の波線を表示して、次のようなメッセージで警告します。
BC40005 function 'GeneratePredicate' は、ベース class 'Predictor' のオーバーライド可能なメソッドをシャドウします。ベース メソッドをオーバーライドするには、このメソッドは 'Overrides' に宣言されていなければなりません。
それでもシャドウイングをしたい場合、Overrides の代わりに明示的に Shadows キーワード(読み方:Shadows=シャドウズ)を使用するという手があります。Shadowsキーワードを使うと、そのプログラムは何かの間違いではなく、プログラマーが明確にシャドウイングを使用するという意思がはっきりしますからVisual Studioは警告を表示しなくなります。
でも、繰り返し言っておきますが、シャドウイングが必要になることはほとんどないので、この警告が表示される場合は、安易に Shadows を付けて解決しないで、なぜ Overrides ではダメなのか考えてください。Overrides でダメな理由がないなら Overrides を付けます。ダメな理由があるなら、その理由の方をなんとかして、Shadowsを使わずに解決しましょう。
派生クラスのコンストラクターが呼び出されるとき、基底クラスのコンストラクターも呼び出す必要があります。多くの場合、これは自動的に行われており意識しなくて済みますが、基底クラスのコンストラクターに何か引数が必須の場合、自動的には呼び出せないので、自分で呼び出しを記述するなどいくつか注意することがあります。
順を追って説明しましょう。
まず、クラスを定義するときにコンストラクターの定義を省略すると、引数なしの空のコンストラクターが自動的に生成されるというルールを覚えているでしょうか?
たとえば、次のクラスは一見なにも定義されていないクラスですが、見えない空のコンストラクターが自動的に生成されます。
![]()
Public Class Empty
End Class
あえて、コンストラクターを明示的に書くと、次のプログラムのようになります。
![]()
Public Class Empty
Public Sub New()
End Sub
End Class
この空のコンストラクターがあるおかげでクラスのインスタンスが作成できるわけです。
継承で、派生クラスのコンストラクターが呼び出される場合、基底クラスのコンストラクターも呼び出されます。
次のように継承関係にあるClassA、ClassBを例に考えてみましょう。
![]()
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のコンストラクターを呼び出してみます
![]()
Dim b As New ClassB()
出力ウィンドウのデバッグには次のように出力されます。
ClassA
ClassB
プログラムからはClassBのコンストラクターしか呼び出していないのに、ClassA のコンストラクターも呼び出されています。自動的に基底クラスのコンストラクターが呼び出されているということがわかります。
基底クラスであるClassAのコンストラクターの方が先に呼び出されていることにも注意してください。
それでは、次のようにClassAのコンストラクターを改造して引数を必須にしてみましょう。自動的に呼び出せなくなります。どうなるでしょうか。
![]()
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 を使用します。たとえば、次のようにすると解決できます。
![]()
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 がないというエラーが発生します。これで慣れていない人は困惑してしまうというわけです。
![]()
Public Class MyUri
Inherits Uri
End Class
ここでの説明を理解していれば解決方法は、明示的にコンストラクターを用意して、その先頭でMyBase.New を使って基底クラスのコンストラクターを呼び出すことということがわかると思います。
![]()
Public Class MyUri
Inherits Uri
Public Sub New(uriString As String)
MyBase.New(uriString)
End Sub
End Class
クラスには型としての側面もあります。
型について簡単に復習しておくと、つまり、 As xxxx と指定するときに xxxx に記述できるものは型でもあります。
変数は型が一致するものしか代入できませんが、「一致する」の意味は少し難しく、厳密に同じ名前の型でなければいけないという意味ではありません。詳しくは初級講座 第28回 で説明します。
ここで知っておいてほしいのは、基底クラスの型に派生クラスのインスタンスを代入することはできるが、その逆はできないということです。
さきほどの InheritsTest クラスと、その派生クラスの InheritsTestEx クラスを例に挙げると次のプログラムはOKです。
![]()
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 です。
![]()
Dim test1 As New InheritsTest
Dim test2 As New InheritsTestEx
test2 = test1 '←ここでエラー。
これの考え方としては、InheritsTestExでメソッドやプロパティが追加されている可能性があり、変数 test2 からそれらを呼び出すことができます。ところが、実体が InheritsTest の場合、追加されたメソッドやプロパティが存在しないので呼び出すことができません。そのためこの代入は許可されません。
このようなルールなので、すべての型の基底クラスである Object 型の変数にはなんでも代入できるということです。
このような代入が役に立つのは変数よりも引数です。たとえば、.NET のフレームワークの FileInfo クラスと DirectoryInfoクラスはどちらもFileSystemInfoクラスを継承しているので、FileSystemInfo型に代入できます。
そこで、これらに共通の機能はFileSystemInfo型で定義した処理を書くことで共通化できます。
次の関数はその例で、引数を FileSystemInfo型で定義し、そのCreattionTimeプロパティを使って作成日付を調べて今日作成されたものかどうかを返します。
![]()
''' <summary>今日作成されたものかどうか確認します。</summary>
Private Function IsNew(item As IO.FileSystemInfo) As Boolean
Return item.CreationTime.Date = Now.Date
End Function
この関数は次のようにFileInfo型の変数を引数にして呼び出すこともできますし、DirectoryInfo型の変数を引数にして呼び出すこともできます。
![]()
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))
今回は、継承の主だった機能を中心に説明しました。
これらの機能をプログラミングにどう役立てていくかにはあまり触れませんでした。
継承はオブジェクト指向プログラミングの中心的な存在で、使い方は奥が深く、さまざまな活用方法があります。それを理解するにはある程度の経験や知識が必要なので、当初私は継承機能そのものを初級講座で扱うつもりがありませんでした。しかし、Visual Basicやフレームワークの機能の中には継承が前提となっているものがいろいろあり、単純な使い方レベルでも説明しておいた方が良いと考えが変わり、この回を後から追加した次第です。
現段階で1つだけ注意してほしいのは、「継承を無計画にやたらと使わない」ということです。
初心者がやりがちな間違いは、いろいろなクラスで共通で使用する機能をプログラムするために継承を使用することです。つまり、共通機能を持った基底クラスをまず作成し、個別の機能をそれぞれの派生クラスで実装していくというやり方です。これはプログラムを複雑にするだけです。共通の機能が必要であれば、単にその共通機能を持ったクラスを作成すればよく、継承を使って親子関係にする必要はありません。
2021/9/20
2021/11/21