C# の継承 - sealed クラスと sealed メソッドで継承を制限する
継承は強力な機能ですが、すべてのクラスやメソッドが自由に継承・オーバーライドされることが望ましいとは限りません。設計意図に反した拡張を防ぐために、C# には sealed キーワードが用意されています。
sealed クラスの基本
クラス宣言に sealed を付けると、そのクラスを親クラスとして継承することができなくなります。
sealed class MathHelper
{
public static double Square(double x)
{
return x * x;
}
public static double Cube(double x)
{
return x * x * x;
}
}この MathHelper を継承しようとするとコンパイルエラーが発生します。
// コンパイルエラー: sealed クラスから派生できない
class AdvancedMathHelper : MathHelper
{
}ユーティリティ系のクラスやデータ転送用のクラスなど、継承される想定がないものに sealed を付けておくと、意図しない拡張によるバグを未然に防げます。
.NET 標準ライブラリでの sealed
実は .NET の標準ライブラリにも sealed クラスは数多く存在します。string クラスはその代表例で、継承できない設計になっています。
状態を持たないユーティリティクラス、不変オブジェクト(イミュータブル)、セキュリティ上の理由で動作を固定したいクラス。
フレームワークの基底クラスなど、利用者が拡張することを前提に設計されたクラス。
sealed にするかどうかは「このクラスは継承されるべきか」という設計判断そのものです。迷ったときは sealed を付けておき、必要が生じた時点で外すという方針を採るチームも少なくありません。
sealed override でメソッド単位の制限
クラス全体ではなく、特定のメソッドだけ追加のオーバーライドを禁止したい場合は sealed override を使います。
class Animal
{
public virtual string GetSound()
{
return "...";
}
}
class Dog : Animal
{
public sealed override string GetSound()
{
return "ワン";
}
}
class Shiba : Dog
{
// コンパイルエラー: sealed されたメソッドはオーバーライドできない
// public override string GetSound() { return "ワンワン"; }
}Dog の GetSound に sealed override を付けているため、Shiba がさらにオーバーライドしようとするとコンパイルエラーになります。Dog クラス自体は sealed ではないので、Shiba のように継承すること自体は問題ありません。あくまでメソッド単位での制限です。
注意点として、sealed はオーバーライドされたメソッドにしか付けられません。最初の virtual 宣言に直接 sealed を付けることはできず、必ず sealed override の形で使用します。
親クラスの virtual メソッドを一度 override してから sealed を付ける必要がある。virtual sealed という組み合わせは不正。
sealed クラスとパフォーマンス
sealed には設計上の意味だけでなく、パフォーマンス面でのメリットもあります。JIT コンパイラは sealed クラスのメソッド呼び出しに対して、仮想メソッドテーブルを経由しない直接呼び出し(デバーチャライゼーション)を適用できる場合があります。
sealed class Logger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
}Logger が sealed であれば、Log メソッドの呼び出し時にオーバーライドの可能性を考慮する必要がないため、JIT が最適化しやすくなります。パフォーマンスが重要なホットパス上にあるクラスでは、sealed を付けることで実行速度が改善するケースもあります。
sealed を使う判断基準
sealed を付けるかどうかは、クラスの設計意図を明確に伝えるという観点で考えると整理しやすくなります。
公開 API として提供するクラスは、継承を想定しないなら sealed を付ける。後から外すことはできるが、後から付けると既存の派生クラスが壊れる。
チーム内で使うクラスは、現時点で継承の必要がなければ sealed を付けておくのが安全。YAGNI(You Aren’t Gonna Need It)の原則に従い、不要な拡張ポイントを作らない。
継承を許可することは「このクラスの振る舞いを外部から変更してよい」という契約を結ぶことと同義です。その契約が不要であれば、sealed で閉じておくほうが保守性の高いコードになります。