C#におけるオーバーライド(override)は、”継承”を活用して親クラス(基底クラス:元となるクラス)が持つメソッドの振る舞いを派生クラスで書き換え、クラスをまたいだ多態性(ポリモーフィズム)を実現する仕組みです。ここでは基礎から応用まで、自身のプロジェクトで安全かつ効果的にオーバーライドを使えるよう、サンプルコードを交えつつ丁寧に解説します。
オーバーライドとは
オーバーライドは、基底クラスで宣言したメソッドやプロパティに対し、派生クラスで同じ入出力を持つメンバーを再定義する仕組みです。これにより、共通の処理を基底でまとめつつ、部分的に振る舞いを変えたい場合に便利です。例えば、動物クラスの MakeSound()
を定義し、派生クラスの犬クラスや猫クラスで異なる鳴き声を実装するようなケースが代表的です。
virtual と override の基本
C#でオーバーライドを行うには、基底クラス側で該当メソッドを virtual
(または abstract
)として宣言し、派生クラス側で override
キーワードを付けて再実装します。※abstract
は後で説明します。
class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("何か音を出します");
}
}
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("ワンワン");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("ニャー");
}
}
virtual
:基底クラスでオーバーライド可能にするキーワードoverride
:派生クラスでの再定義を示すキーワード
overrideの書き方と注意点
- 戻り値の型、メソッド名、パラメータ リストは完全に一致させる
- アクセス修飾子(public等)は基底クラスと同等か、それよりアクセス範囲を狭められない
override
キーワードの付け忘れや余計なvirtual
の組み合わせはコンパイルエラーを招く
class Parent
{
protected virtual int Calculate(int x) => x * 2;
}
class Child : Parent
{
public override int Calculate(int x) // × protected → public に広げるのはOK
{
return base.Calculate(x) + 5;
}
}
abstract と override
基底クラスを抽象クラス(abstract class
)とし、abstract
メソッドを定義すれば、必ず派生クラスでの実装を強制できます。これにより、共通のインターフェイスを保ちつつ、必須の実装漏れを防げます。
abstract
クラスはインスタンス化不可(「new XXX
」ができない)で、継承して処理を書くことで初めて利用できる。(virtual
の場合は基底クラスも利用可能)abstract
関数は、定義のみ記述可能で中身は継承先で記述する。(派生クラスでoverride
しないとコンパイルエラーになる)- 必ず
override
したい処理がある場合に派生クラスで書き忘れることが防げる。
abstract class Shape
{
public abstract double Area(); // 実装未定義
}
class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
sealed override と隠蔽
派生クラスでオーバーライドしたメソッドをさらに上書きされたくない場合、sealed
キーワードを使って封印できます。また、基底クラスのメンバー(フィールド、メソッド等)と同名のメンバーを派生クラスで再定義してしまうと、基底クラスのメンバーが新しく追加されたメンバーに隠れてしまいます。派生クラスでも警告が表示されますが、全く別の動作をさせるなど意図的に基底クラスのメンバーを隠したい場合は派生クラスで new
キーワードを使うと警告が表示されなくなります。これはオーバーライドではなく隠蔽と呼ばれます。
class Base
{
public virtual void Display() { Console.WriteLine("Base"); }
}
class Mid : Base
{
public sealed override void Display() { Console.WriteLine("Mid"); }
}
class Leaf : Mid
{
// public override void Display() { }
// Midはsealedのためコンパイルエラー
}
class HideExample : Base
{
public new void Display() { Console.WriteLine("Hide"); }
// newキーワードのため"隠蔽"
}
sealed override
:これ以上の派生クラスでの上書きを禁止new
:基底メンバーを隠し、別物として定義
baseによる基底クラス処理の呼び出し
派生クラスでオーバーライドしつつ、基底クラスの処理を一部流用したい場合は base.メソッド名()
を呼び出します。たとえばログ出力や共通処理を維持しつつ追加機能を実装するときに有効です。
class Logger
{
public virtual void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
}
class TimestampLogger : Logger
{
public override void Log(string message)
{
var now = DateTime.Now.ToString("u");
// 基底クラス(base)のLogメソッドを呼び出し
base.Log($"[{now}] {message}");
}
}
オーバーライドの活用例
- UIフレームワークで描画処理を共通化しながら各コントロールで独自描画
- ビジネスロジック層で基本的なDBアクセスをまとめつつ、特定テーブル用に処理を拡張
- テスト時にモッククラスで動作を差し替え、多様なシナリオをシミュレート
オーバーライドの落とし穴と回避策
- 基底クラスの変更が派生クラスに波及し、大規模な修正が必要になることがある
- 過度なオーバーライドはクラス設計を複雑化させる
new
とoverride
の混同によるバグ
これらの回避策としては、インターフェイス設計や依存性注入で振る舞いを切り替える方法も検討することになります。インターフェイスと仮想/抽象(virtual
/abstract
)クラスの違いは以下の通りです。
項目 | 仮想/抽象クラス | インターフェイス |
---|---|---|
実装の有無 | 実装のないメンバーと実装のあるメンバーの両方を持つことができる。 | メンバーの宣言のみを記述、具体的な実装を持たない。 ※C# 8.0以降はデフォルト実装が可能。 |
多重継承 | クラスは1つの仮想/抽象クラスしか継承できない。 | クラスは複数のインターフェースを実装できる。多重継承の柔軟性がある。 |
目的 | 基底クラスとして共通の実装を定義しながら、派生クラスに個別のメンバーを実装する。 | クラスや構造体が実装すべき共通の形を定義する。 |
利用例 | 共通の実装を共有しつつ、一部の機能を派生クラスで個別に実装する場合に適している。 | 異なるクラスで共通の動作を定義する場合に適している。 |
まとめ
C#のオーバーライドは、クラス継承を活かした強力なポリモーフィズムの要です。基底クラス側で基本的な処理をまとめ、派生クラスで必要な部分だけをオーバーライドすれば、重複の少ない柔軟な設計が可能となります。ただし乱用は設計の複雑化を招くため、役割を明確化しながら適切に使いましょう!