継承の深淵:`MyBase`と`MyClass`の使い分けが分かつ「システムの堅牢性」
業務システムにおいて、継承は強力な武器であると同時に、扱いを誤れば「追跡困難なバグ」を生産する劇薬だ。VB.NETの設計思想において、`MyBase`と`MyClass`は単なるキーワードではない。これらは、オブジェクトの仮想関数テーブル(vtable)の参照先をプログラム的に制御するための、極めて重要なインターフェースである。
レガシーなVBAから.NETへ移行した現場で、しばしば「なぜオーバーライドしたはずの処理が動かないのか?」という問いに直面する。その回答は、この二つのキーワードの挙動を理解しているか否かに帰結する。
—
1. 概念の解剖:vtableと「自己」の所在
まず、アーキテクトとしての視点で整理しよう。
- `MyBase`: インスタンスを「基底クラスの型」として振る舞わせるためのポインタ。`base`(C#)と同義。
- `MyClass`: インスタンスを「現在のクラス(最終派生クラス)」として固定し、vtableのルックアップを抑制する。これはVB.NET特有の強力な仕様である。
なぜ`MyClass`が必要なのか?
`Overridable`(仮想関数)は、実行時の実体(Runtime Type)によって呼び出し先が決まる。しかし、「基底クラスの内部で、派生クラスにオーバーライドされたメソッドではなく、あえて自分のメソッドを呼びたい」というケースは、設計の初期段階で頻出する。
これを`MyBase`で行うと、意図せぬ無限ループや、多層継承における呼び出し順序の崩壊を招く。`MyClass`はそのリスクを根底から断つための「呼び出しの固定化」ツールだ。
—
2. 実装の極意:コードで見る「意図」の明示
以下のコードは、Windows APIを叩く際や、複雑なビジネスロジックのパイプライン処理において、どのようにメソッドが呼び出されるかを定義したものである。
Public Class BaseProcessor
‘ 基底の処理
Public Overridable Sub Execute()
‘ MyBase.Execute() と書くと、ここが再帰的に呼ばれStackOverflowを引き起こす
‘ MyClass.Execute() と書くと、現在のインスタンスの型が何であれ、
‘ このクラスのExecuteを強制的に呼び出す(仮想関数テーブルを無視する)
Console.WriteLine(“Base: Initializing resource…”)
MyClass.ProcessInternal()
End Sub
Public Overridable Sub ProcessInternal()
Console.WriteLine(“Base: Processing…”)
End Sub
End Class
Public Class DerivedProcessor
Inherits BaseProcessor
Public Overrides Sub ProcessInternal()
‘ 基底の処理を保持しつつ、独自の拡張を行う
MyBase.ProcessInternal()
Console.WriteLine(“Derived: Adding extra logic (e.g., API call, Cleanup)…”)
End Sub
End Class
この設計が意味すること
- `MyBase.ProcessInternal()`: 継承の連鎖を維持する。レガシー環境のデータバリデーションや、メモリ解放処理(`Dispose`パターン)の呼び出しで多用する。
- `MyClass.ProcessInternal()`: 派生クラスによるオーバーライドを「許可しない」呼び出しを強制する。基底クラスの内部整合性を守るための「隠れたガードレール」である。
—
3. シニアエンジニアの現場的知見:メモリとAPI呼び出し
オブジェクトのライフサイクルと解放
レガシーシステムとの連携では、P/InvokeによるAPI呼び出しとメモリ管理が避けられない。`Dispose`メソッドを実装する際、`MyBase.Dispose(disposing)`を呼ぶのは定石だが、ここに`MyClass`を持ち込むことはない。なぜなら、`Dispose`は階層を下から順に(あるいは上から順に)解きほぐす必要があるからだ。
ここで犯しがちなミスは、`MyBase`の呼び出しを忘れることによる「ハンドルリーク」である。
Protected Overrides Sub Dispose(disposing As Boolean)
Try
If disposing Then
‘ 管理リソースの解放
End If
‘ 非管理リソースの解放 (API経由の解放など)
‘ ここでMyBaseを呼ぶことで、継承階層全体のクリーンアップを保証する
MyBase.Dispose(disposing)
Catch ex As Exception
‘ ログ記録
End Try
End Sub
システム間連携の要諦
外部システムとの通信を行う際、プロキシクラスやファクトリを介した継承設計を行うことが多いだろう。その時、継承の深さが深くなりすぎると、`MyBase`の追跡は困難を極める。
極限の知見:
「継承は2階層まで」という黄金律を守れないならば、`MyClass`を使って「どこで処理が完結するか」を明示的に記述せよ。多重継承に近い複雑さがある場合、あえて`MyClass`でメソッドを固定し、vtableのルックアップコスト(極めて微小だが)を削り、かつ動作の予測可能性を高めるのが、パフォーマンスと保守性を両立させる唯一の道である。
—
結びに代えて
`MyBase`と`MyClass`の使い分けは、単なる構文の習得ではない。それは、あなたが書くコードが「実行時にどのようにメモリ上の命令列を辿るか」を指揮する指揮者の腕前そのものだ。
レガシーなシステムを保守し、次世代へ繋ぐ我々にとって、最も恐ろしいのは「なぜ動くのかわからないコード」である。意図をコードに刻み込め。`MyBase`で継承の鎖を繋ぎ、`MyClass`で自らの正当性を主張せよ。それが、システムを支配するエンジニアの矜持である。
